Implement struts

This commit is contained in:
elkowar 2021-02-17 16:39:45 +01:00
parent ab26c1ea81
commit 9238688774
10 changed files with 362 additions and 53 deletions

96
Cargo.lock generated
View file

@ -263,6 +263,7 @@ dependencies = [
"futures-util", "futures-util",
"gdk", "gdk",
"gdk-pixbuf", "gdk-pixbuf",
"gdkx11",
"gio", "gio",
"glib", "glib",
"grass", "grass",
@ -273,7 +274,7 @@ dependencies = [
"libc", "libc",
"log", "log",
"maplit", "maplit",
"nix", "nix 0.19.1",
"num", "num",
"pretty_assertions", "pretty_assertions",
"pretty_env_logger", "pretty_env_logger",
@ -287,6 +288,7 @@ dependencies = [
"tokio-stream", "tokio-stream",
"tokio-util", "tokio-util",
"unescape", "unescape",
"x11rb",
] ]
[[package]] [[package]]
@ -461,6 +463,55 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "gdkx11"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b89606baa221f9b8d8aa81924fd448c6b107d20de949f0fbf9a4ec203bb54b63"
dependencies = [
"bitflags",
"gdk",
"gdk-pixbuf",
"gdk-pixbuf-sys",
"gdk-sys",
"gdkx11-sys",
"gio",
"gio-sys",
"glib",
"glib-sys",
"gobject-sys",
"libc",
"pango",
"x11",
]
[[package]]
name = "gdkx11-sys"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6710388d530f3178ccbeb65cbafdf497a5772c4409eaf574ee9fa461af0a3d09"
dependencies = [
"gdk-pixbuf-sys",
"gdk-sys",
"gio-sys",
"glib-sys",
"gobject-sys",
"libc",
"pango-sys",
"system-deps",
"x11",
]
[[package]]
name = "gethostname"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.1.16" version = "0.1.16"
@ -804,6 +855,18 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "nix"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055"
dependencies = [
"bitflags",
"cc",
"cfg-if 0.1.10",
"libc",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.19.1" version = "0.19.1"
@ -1627,12 +1690,43 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "winapi-wsapoll"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "x11"
version = "2.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ecd092546cb16f25783a5451538e73afc8d32e242648d54f4ae5459ba1e773"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "x11rb"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "232bc353846d5350d7a815c249209f9545f1d5c725dca846a5ae383a70f30fa5"
dependencies = [
"gethostname",
"nix 0.18.0",
"winapi",
"winapi-wsapoll",
]
[[package]] [[package]]
name = "xmlparser" name = "xmlparser"
version = "0.13.3" version = "0.13.3"

View file

@ -9,6 +9,12 @@ repository = "https://github.com/elkowar/eww"
homepage = "https://github.com/elkowar/eww" homepage = "https://github.com/elkowar/eww"
[features]
default = ["x11"]
x11 = ["gdkx11", "x11rb"]
no-x11 = []
[dependencies] [dependencies]
gtk = { version = "0.9", features = [ "v3_16" ] } gtk = { version = "0.9", features = [ "v3_16" ] }
gdk = { version = "", features = ["v3_16"] } gdk = { version = "", features = ["v3_16"] }
@ -17,6 +23,9 @@ glib = { version = "", features = ["v2_44"] }
gdk-pixbuf = "0.9" gdk-pixbuf = "0.9"
gdkx11 = { version = "0.9", optional = true }
x11rb = { version = "0.7", features = ["randr"], optional = true }
regex = "1" regex = "1"
bincode = "1.3" bincode = "1.3"
anyhow = "1.0" anyhow = "1.0"
@ -47,5 +56,10 @@ futures-util = "0.3"
inotify = "0.9" inotify = "0.9"
tokio-util = "0.6" tokio-util = "0.6"
[target.'cfg(target_os="linux")'.dependencies]
inotify = "0.9"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"

View file

@ -93,8 +93,9 @@ contain are defined. -->
<windows> <windows>
<!-- These are the windows --> <!-- These are the windows -->
<window name="bar"> <window name="bar" screen="0">
<geometry x="0%" y="0%" width="100%" height="4%"/> <!--Specifies geometry--> <geometry x="0%" y="0%" width="100%" height="4%"/> <!--Specifies geometry-->
<reserve side="top" distance="4%"/>
<widget> <widget>
<bar/> <!--This is the def we created earlier--> <bar/> <!--This is the def we created earlier-->
</widget> </widget>

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
config, config,
config::{window_definition::WindowName, AnchorPoint, WindowStacking}, config::{window_definition::WindowName, AnchorPoint, WindowStacking},
eww_state, display_backend, eww_state,
script_var_handler::*, script_var_handler::*,
value::{AttrValue, Coords, NumWithUnit, PrimitiveValue, VarName}, value::{AttrValue, Coords, NumWithUnit, PrimitiveValue, VarName},
widgets, widgets,
@ -379,6 +379,8 @@ fn initialize_window(
window.set_keep_below(true); window.set_keep_below(true);
} }
display_backend::reserve_space_for(&window, monitor_geometry, window_def.struts)?;
Ok(EwwWindow { Ok(EwwWindow {
name: window_def.name.clone(), name: window_def.name.clone(),
definition: window_def, definition: window_def,

View file

@ -1,4 +1,4 @@
use crate::ensure_xml_tag_is; use crate::{ensure_xml_tag_is, value::NumWithUnit};
use anyhow::*; use anyhow::*;
use derive_more::*; use derive_more::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -6,6 +6,21 @@ use smart_default::SmartDefault;
use super::*; use super::*;
#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault)]
pub enum Side {
#[default]
Top,
Left,
Right,
Bottom,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub struct StrutDefinition {
pub side: Side,
pub dist: NumWithUnit,
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct EwwWindowDefinition { pub struct EwwWindowDefinition {
pub name: WindowName, pub name: WindowName,
@ -13,7 +28,7 @@ pub struct EwwWindowDefinition {
pub stacking: WindowStacking, pub stacking: WindowStacking,
pub screen_number: Option<i32>, pub screen_number: Option<i32>,
pub widget: WidgetUse, pub widget: WidgetUse,
pub struts: Struts, pub struts: StrutDefinition,
pub focusable: bool, pub focusable: bool,
} }
@ -21,10 +36,17 @@ impl EwwWindowDefinition {
pub fn from_xml_element(xml: &XmlElement) -> Result<Self> { pub fn from_xml_element(xml: &XmlElement) -> Result<Self> {
ensure_xml_tag_is!(xml, "window"); ensure_xml_tag_is!(xml, "window");
let stacking: WindowStacking = xml.parse_optional_attr("stacking")?.unwrap_or_default(); let stacking: WindowStacking = xml.parse_optional_attr("stacking")?.unwrap_or_default();
let screen_number = xml.parse_optional_attr("screen")?;
let focusable = xml.parse_optional_attr("focusable")?;
let struts = xml.child("struts").ok().map(Struts::from_xml_element).transpose()?; // TODO maybe rename this to monitor?
let focusable = xml.parse_optional_attr("focusable")?;
let screen_number = xml.parse_optional_attr("screen")?;
let struts: Option<StrutDefinition> = xml
.child("reserve")
.ok()
.map(parse_strut_definition)
.transpose()
.context("Failed to parse <reserve>")?;
Ok(EwwWindowDefinition { Ok(EwwWindowDefinition {
name: WindowName(xml.attr("name")?.to_owned()), name: WindowName(xml.attr("name")?.to_owned()),
@ -46,23 +68,23 @@ impl EwwWindowDefinition {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] fn parse_strut_definition(xml: XmlElement) -> Result<StrutDefinition> {
pub struct Struts { Ok(StrutDefinition {
left: i32, side: parse_side(xml.attr("side")?)?,
right: i32, dist: xml.attr("distance")?.parse()?,
top: i32, })
bottom: i32,
} }
impl Struts { fn parse_side(s: &str) -> Result<Side> {
pub fn from_xml_element(xml: XmlElement) -> Result<Self> { match s {
ensure_xml_tag_is!(xml, "struts"); "l" | "left" => Ok(Side::Left),
Ok(Struts { "r" | "right" => Ok(Side::Right),
left: xml.attr("left")?.parse()?, "t" | "top" => Ok(Side::Top),
right: xml.attr("right")?.parse()?, "b" | "bottom" => Ok(Side::Bottom),
top: xml.attr("top")?.parse()?, _ => Err(anyhow!(
bottom: xml.attr("bottom")?.parse()?, "Failed to parse {} as valid side. Must be one of \"left\", \"right\", \"top\", \"bottom\"",
}) s
)),
} }
} }

131
src/display_backend.rs Normal file
View file

@ -0,0 +1,131 @@
pub use platform::*;
#[cfg(feature = "no-x11")]
mod platform {
pub fn reserve_space_for(window: &gtk::Window, monitor: gdk::Rectangle, strut_def: StrutDefinition) -> Result<()> {
Err(anyhow!("Cannot reserve space on non-X11 backends"))
}
}
#[cfg(feature = "x11")]
mod platform {
use crate::config::{Side, StrutDefinition};
use anyhow::*;
use gdkx11;
use gtk::{self, prelude::*};
use x11rb::protocol::xproto::ConnectionExt;
use x11rb::{
self,
connection::Connection,
protocol::xproto::*,
rust_connection::{DefaultStream, RustConnection},
};
pub fn reserve_space_for(window: &gtk::Window, monitor: gdk::Rectangle, strut_def: StrutDefinition) -> Result<()> {
let backend = X11Backend::new()?;
backend.reserve_space_for(window, monitor, strut_def)?;
Ok(())
}
struct X11Backend {
conn: RustConnection<DefaultStream>,
root_window: u32,
atoms: AtomCollection,
}
impl X11Backend {
fn new() -> Result<Self> {
let (conn, screen_num) = RustConnection::connect(None)?;
let screen = conn.setup().roots[screen_num].clone();
let atoms = AtomCollection::new(&conn)?.reply()?;
Ok(X11Backend {
conn,
root_window: screen.root,
atoms,
})
}
fn reserve_space_for(
&self,
window: &gtk::Window,
monitor_rect: gdk::Rectangle,
strut_def: StrutDefinition,
) -> Result<()> {
let win_id = window
.get_window()
.context("Couldn't get gdk window from gtk window")?
.downcast::<gdkx11::X11Window>()
.ok()
.context("Failed to get x11 window for gtk window")?
.get_xid() as u32;
let root_window_geometry = self.conn.get_geometry(self.root_window)?.reply()?;
let mon_end_x = (monitor_rect.x + monitor_rect.width) as u32 - 1u32;
let mon_end_y = (monitor_rect.y + monitor_rect.height) as u32 - 1u32;
let dist = match strut_def.side {
Side::Left | Side::Right => strut_def.dist.relative_to(monitor_rect.width) as u32,
Side::Top | Side::Bottom => strut_def.dist.relative_to(monitor_rect.height) as u32,
};
// don't question it,.....
// it's how the X gods want it to be.
// left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x
#[rustfmt::skip]
let strut_list: Vec<u8> = match strut_def.side {
Side::Left => vec![dist + monitor_rect.x as u32, 0, 0, 0, monitor_rect.y as u32, mon_end_y, 0, 0, 0, 0, 0, 0],
Side::Right => vec![0, root_window_geometry.width as u32 - mon_end_x + dist, 0, 0, 0, 0, monitor_rect.y as u32, mon_end_y, 0, 0, 0, 0],
Side::Top => vec![0, 0, dist + monitor_rect.y as u32, 0, 0, 0, 0, 0, monitor_rect.x as u32, mon_end_x, 0, 0],
Side::Bottom => vec![0, 0, 0, root_window_geometry.height as u32 - mon_end_y + dist, 0, 0, 0, 0, 0, 0, monitor_rect.x as u32, mon_end_x]
}.iter().flat_map(|x| x.to_le_bytes().to_vec()).collect();
self.conn
.change_property(
PropMode::Replace,
win_id,
self.atoms._NET_WM_STRUT,
self.atoms.CARDINAL,
32,
4,
&strut_list[0..16],
)?
.check()?;
self.conn
.change_property(
PropMode::Replace,
win_id,
self.atoms._NET_WM_STRUT_PARTIAL,
self.atoms.CARDINAL,
32,
12,
&strut_list,
)?
.check()?;
self.conn.flush()?;
Ok(())
}
}
x11rb::atom_manager! {
pub AtomCollection: AtomCollectionCookie {
_NET_WM_WINDOW_TYPE,
_NET_WM_WINDOW_TYPE_DOCK,
_NET_WM_WINDOW_TYPE_DIALOG,
_NET_WM_STATE,
_NET_WM_STATE_STICKY,
_NET_WM_STATE_ABOVE,
_NET_WM_STATE_BELOW,
_NET_WM_NAME,
_NET_WM_STRUT,
_NET_WM_STRUT_PARTIAL,
WM_NAME,
UTF8_STRING,
COMPOUND_TEXT,
CARDINAL,
ATOM,
WM_CLASS,
STRING,
}
}
}

36
src/geometry.rs Normal file
View file

@ -0,0 +1,36 @@
use derive_more::*;
pub trait Rectangular {
fn get_rect(&self) -> Rect;
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Display)]
#[display(fmt = ".x*.y:.width*.height")]
pub struct Rect {
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
}
impl Rect {
pub fn of(x: i32, y: i32, width: i32, height: i32) -> Self {
Rect { x, y, width, height }
}
}
impl Rectangular for Rect {
fn get_rect(&self) -> Rect {
*self
}
}
impl Rectangular for gdk::Rectangle {
fn get_rect(&self) -> Rect {
Rect {
x: self.x,
y: self.y,
width: self.width,
height: self.height,
}
}
}

View file

@ -24,6 +24,8 @@ pub mod server;
pub mod util; pub mod util;
pub mod value; pub mod value;
pub mod widgets; pub mod widgets;
pub mod geometry;
pub mod display_backend;
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref IPC_SOCKET_PATH: std::path::PathBuf = std::env::var("XDG_RUNTIME_DIR") pub static ref IPC_SOCKET_PATH: std::path::PathBuf = std::env::var("XDG_RUNTIME_DIR")

View file

@ -28,7 +28,7 @@ struct RawOpt {
#[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)] #[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)]
pub enum Action { pub enum Action {
/// Start the Eww daemon. /// Start the Eww daemon.
#[structopt(name = "daemon")] #[structopt(name = "daemon", alias = "d")]
Daemon { Daemon {
/// Custom Config Path /// Custom Config Path
#[structopt(short, long)] #[structopt(short, long)]
@ -56,7 +56,7 @@ pub enum ActionWithServer {
Ping, Ping,
/// Update the value of a variable, in a running eww instance /// Update the value of a variable, in a running eww instance
#[structopt(name = "update")] #[structopt(name = "update", alias = "u")]
Update { Update {
/// variable_name="new_value"-pairs that will be updated /// variable_name="new_value"-pairs that will be updated
#[structopt(parse(try_from_str = parse_var_update_arg))] #[structopt(parse(try_from_str = parse_var_update_arg))]
@ -64,7 +64,7 @@ pub enum ActionWithServer {
}, },
/// open a window /// open a window
#[structopt(name = "open")] #[structopt(name = "open", alias = "o")]
OpenWindow { OpenWindow {
/// Name of the window you want to open. /// Name of the window you want to open.
window_name: WindowName, window_name: WindowName,
@ -88,19 +88,19 @@ pub enum ActionWithServer {
OpenMany { windows: Vec<WindowName> }, OpenMany { windows: Vec<WindowName> },
/// Close the window with the given name /// Close the window with the given name
#[structopt(name = "close")] #[structopt(name = "close", alias = "c")]
CloseWindow { window_name: WindowName }, CloseWindow { window_name: WindowName },
/// Reload the configuration /// Reload the configuration
#[structopt(name = "reload")] #[structopt(name = "reload", alias = "r")]
Reload, Reload,
/// kill the eww daemon /// kill the eww daemon
#[structopt(name = "kill")] #[structopt(name = "kill", alias = "k")]
KillServer, KillServer,
/// Close all windows, without killing the daemon /// Close all windows, without killing the daemon
#[structopt(name = "close-all")] #[structopt(name = "close-all", alias = "ca")]
CloseAll, CloseAll,
/// Print the current eww-state /// Print the current eww-state

View file

@ -15,6 +15,15 @@ pub enum NumWithUnit {
Pixels(i32), Pixels(i32),
} }
impl NumWithUnit {
pub fn relative_to(&self, max: i32) -> i32 {
match *self {
NumWithUnit::Percent(n) => ((max as f64 / 100.0) * n as f64) as i32,
NumWithUnit::Pixels(n) => n,
}
}
}
impl FromStr for NumWithUnit { impl FromStr for NumWithUnit {
type Err = anyhow::Error; type Err = anyhow::Error;
@ -35,7 +44,7 @@ impl FromStr for NumWithUnit {
} }
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, Default)] #[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, Default)]
#[display(fmt = "{}X{}", x, y)] #[display(fmt = "{}*{}", x, y)]
pub struct Coords { pub struct Coords {
pub x: NumWithUnit, pub x: NumWithUnit,
pub y: NumWithUnit, pub y: NumWithUnit,
@ -46,7 +55,7 @@ impl FromStr for Coords {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let (x, y) = s let (x, y) = s
.split_once(|x: char| x.to_ascii_lowercase() == 'x') .split_once(|x: char| x.to_ascii_lowercase() == 'x' || x.to_ascii_lowercase() == '*')
.ok_or_else(|| anyhow!("must be formatted like 200x500"))?; .ok_or_else(|| anyhow!("must be formatted like 200x500"))?;
Coords::from_strs(x, y) Coords::from_strs(x, y)
} }
@ -59,6 +68,13 @@ impl fmt::Debug for Coords {
} }
impl Coords { impl Coords {
pub fn from_pixels(x: i32, y: i32) -> Self {
Coords {
x: NumWithUnit::Pixels(x),
y: NumWithUnit::Pixels(y),
}
}
/// parse a string for x and a string for y into a [`Coords`] object. /// parse a string for x and a string for y into a [`Coords`] object.
pub fn from_strs(x: &str, y: &str) -> Result<Coords> { pub fn from_strs(x: &str, y: &str) -> Result<Coords> {
Ok(Coords { Ok(Coords {
@ -69,16 +85,7 @@ impl Coords {
/// resolve the possibly relative coordinates relative to a given containers size /// resolve the possibly relative coordinates relative to a given containers size
pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) { pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) {
( (self.x.relative_to(width), self.y.relative_to(height))
match self.x {
NumWithUnit::Percent(n) => ((width as f64 / 100.0) * n as f64) as i32,
NumWithUnit::Pixels(n) => n,
},
match self.y {
NumWithUnit::Percent(n) => ((height as f64 / 100.0) * n as f64) as i32,
NumWithUnit::Pixels(n) => n,
},
)
} }
} }