diff --git a/Cargo.lock b/Cargo.lock index f9a2653..ca5454d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,6 +263,7 @@ dependencies = [ "futures-util", "gdk", "gdk-pixbuf", + "gdkx11", "gio", "glib", "grass", @@ -273,7 +274,7 @@ dependencies = [ "libc", "log", "maplit", - "nix", + "nix 0.19.1", "num", "pretty_assertions", "pretty_env_logger", @@ -287,6 +288,7 @@ dependencies = [ "tokio-stream", "tokio-util", "unescape", + "x11rb", ] [[package]] @@ -461,6 +463,55 @@ dependencies = [ "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]] name = "getrandom" version = "0.1.16" @@ -804,6 +855,18 @@ dependencies = [ "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]] name = "nix" version = "0.19.1" @@ -1627,12 +1690,43 @@ dependencies = [ "winapi", ] +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "xmlparser" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index d5dd536..2f60722 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,12 @@ repository = "https://github.com/elkowar/eww" homepage = "https://github.com/elkowar/eww" +[features] +default = ["x11"] +x11 = ["gdkx11", "x11rb"] +no-x11 = [] + + [dependencies] gtk = { version = "0.9", features = [ "v3_16" ] } gdk = { version = "", features = ["v3_16"] } @@ -17,6 +23,9 @@ glib = { version = "", features = ["v2_44"] } gdk-pixbuf = "0.9" +gdkx11 = { version = "0.9", optional = true } +x11rb = { version = "0.7", features = ["randr"], optional = true } + regex = "1" bincode = "1.3" anyhow = "1.0" @@ -47,5 +56,10 @@ futures-util = "0.3" inotify = "0.9" tokio-util = "0.6" + +[target.'cfg(target_os="linux")'.dependencies] +inotify = "0.9" + + [dev-dependencies] pretty_assertions = "0.6.1" diff --git a/examples/eww-bar/eww.xml b/examples/eww-bar/eww.xml index eb70a81..47a1a08 100644 --- a/examples/eww-bar/eww.xml +++ b/examples/eww-bar/eww.xml @@ -22,15 +22,15 @@ contain are defined. --> - - + + - - - - - - + + + + + + @@ -45,7 +45,7 @@ contain are defined. --> - + @@ -66,11 +66,11 @@ contain are defined. --> - + playerctl metadata --format '{{ artist }} - {{ title }}' - + @@ -93,8 +93,9 @@ contain are defined. --> - + + diff --git a/src/app.rs b/src/app.rs index 7a0d8e5..045b1a7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,7 @@ use crate::{ config, config::{window_definition::WindowName, AnchorPoint, WindowStacking}, - eww_state, + display_backend, eww_state, script_var_handler::*, value::{AttrValue, Coords, NumWithUnit, PrimitiveValue, VarName}, widgets, @@ -379,6 +379,8 @@ fn initialize_window( window.set_keep_below(true); } + display_backend::reserve_space_for(&window, monitor_geometry, window_def.struts)?; + Ok(EwwWindow { name: window_def.name.clone(), definition: window_def, diff --git a/src/config/window_definition.rs b/src/config/window_definition.rs index 0863dc2..3a178c4 100644 --- a/src/config/window_definition.rs +++ b/src/config/window_definition.rs @@ -1,4 +1,4 @@ -use crate::ensure_xml_tag_is; +use crate::{ensure_xml_tag_is, value::NumWithUnit}; use anyhow::*; use derive_more::*; use serde::{Deserialize, Serialize}; @@ -6,6 +6,21 @@ use smart_default::SmartDefault; 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)] pub struct EwwWindowDefinition { pub name: WindowName, @@ -13,7 +28,7 @@ pub struct EwwWindowDefinition { pub stacking: WindowStacking, pub screen_number: Option, pub widget: WidgetUse, - pub struts: Struts, + pub struts: StrutDefinition, pub focusable: bool, } @@ -21,10 +36,17 @@ impl EwwWindowDefinition { pub fn from_xml_element(xml: &XmlElement) -> Result { ensure_xml_tag_is!(xml, "window"); 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 = xml + .child("reserve") + .ok() + .map(parse_strut_definition) + .transpose() + .context("Failed to parse ")?; Ok(EwwWindowDefinition { name: WindowName(xml.attr("name")?.to_owned()), @@ -46,23 +68,23 @@ impl EwwWindowDefinition { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct Struts { - left: i32, - right: i32, - top: i32, - bottom: i32, +fn parse_strut_definition(xml: XmlElement) -> Result { + Ok(StrutDefinition { + side: parse_side(xml.attr("side")?)?, + dist: xml.attr("distance")?.parse()?, + }) } -impl Struts { - pub fn from_xml_element(xml: XmlElement) -> Result { - ensure_xml_tag_is!(xml, "struts"); - Ok(Struts { - left: xml.attr("left")?.parse()?, - right: xml.attr("right")?.parse()?, - top: xml.attr("top")?.parse()?, - bottom: xml.attr("bottom")?.parse()?, - }) +fn parse_side(s: &str) -> Result { + match s { + "l" | "left" => Ok(Side::Left), + "r" | "right" => Ok(Side::Right), + "t" | "top" => Ok(Side::Top), + "b" | "bottom" => Ok(Side::Bottom), + _ => Err(anyhow!( + "Failed to parse {} as valid side. Must be one of \"left\", \"right\", \"top\", \"bottom\"", + s + )), } } diff --git a/src/display_backend.rs b/src/display_backend.rs new file mode 100644 index 0000000..1eecdb1 --- /dev/null +++ b/src/display_backend.rs @@ -0,0 +1,131 @@ +pub use platform::*; + +#[cfg(feature = "no-x11")] +mod platform { + pub fn reserve_space_for(window: >k::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: >k::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, + root_window: u32, + atoms: AtomCollection, + } + + impl X11Backend { + fn new() -> Result { + 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: >k::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::() + .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 = 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, + } + } +} diff --git a/src/geometry.rs b/src/geometry.rs new file mode 100644 index 0000000..d114234 --- /dev/null +++ b/src/geometry.rs @@ -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, + } + } +} diff --git a/src/main.rs b/src/main.rs index 50305b7..8760588 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,8 @@ pub mod server; pub mod util; pub mod value; pub mod widgets; +pub mod geometry; +pub mod display_backend; lazy_static::lazy_static! { pub static ref IPC_SOCKET_PATH: std::path::PathBuf = std::env::var("XDG_RUNTIME_DIR") diff --git a/src/opts.rs b/src/opts.rs index 09d62fc..e4004ef 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -28,7 +28,7 @@ struct RawOpt { #[derive(StructOpt, Debug, Serialize, Deserialize, PartialEq)] pub enum Action { /// Start the Eww daemon. - #[structopt(name = "daemon")] + #[structopt(name = "daemon", alias = "d")] Daemon { /// Custom Config Path #[structopt(short, long)] @@ -56,7 +56,7 @@ pub enum ActionWithServer { Ping, /// Update the value of a variable, in a running eww instance - #[structopt(name = "update")] + #[structopt(name = "update", alias = "u")] Update { /// variable_name="new_value"-pairs that will be updated #[structopt(parse(try_from_str = parse_var_update_arg))] @@ -64,7 +64,7 @@ pub enum ActionWithServer { }, /// open a window - #[structopt(name = "open")] + #[structopt(name = "open", alias = "o")] OpenWindow { /// Name of the window you want to open. window_name: WindowName, @@ -88,19 +88,19 @@ pub enum ActionWithServer { OpenMany { windows: Vec }, /// Close the window with the given name - #[structopt(name = "close")] + #[structopt(name = "close", alias = "c")] CloseWindow { window_name: WindowName }, /// Reload the configuration - #[structopt(name = "reload")] + #[structopt(name = "reload", alias = "r")] Reload, /// kill the eww daemon - #[structopt(name = "kill")] + #[structopt(name = "kill", alias = "k")] KillServer, /// Close all windows, without killing the daemon - #[structopt(name = "close-all")] + #[structopt(name = "close-all", alias = "ca")] CloseAll, /// Print the current eww-state diff --git a/src/value/coords.rs b/src/value/coords.rs index 04348c6..e017ace 100644 --- a/src/value/coords.rs +++ b/src/value/coords.rs @@ -15,6 +15,15 @@ pub enum NumWithUnit { 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 { type Err = anyhow::Error; @@ -35,7 +44,7 @@ impl FromStr for NumWithUnit { } #[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, Default)] -#[display(fmt = "{}X{}", x, y)] +#[display(fmt = "{}*{}", x, y)] pub struct Coords { pub x: NumWithUnit, pub y: NumWithUnit, @@ -46,7 +55,7 @@ impl FromStr for Coords { fn from_str(s: &str) -> Result { 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"))?; Coords::from_strs(x, y) } @@ -59,6 +68,13 @@ impl fmt::Debug for 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. pub fn from_strs(x: &str, y: &str) -> Result { Ok(Coords { @@ -69,16 +85,7 @@ impl Coords { /// resolve the possibly relative coordinates relative to a given containers size pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) { - ( - 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, - }, - ) + (self.x.relative_to(width), self.y.relative_to(height)) } }