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",
"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"

View file

@ -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"

View file

@ -22,15 +22,15 @@ contain are defined. -->
<def name="workspaces">
<box orientation="h" class="workspaces" space-evenly="true" halign="start"> <!-- Your workspaces.-->
<button onclick="wmctrl -s 0">1</button>
<button onclick="wmctrl -s 1">2</button>
<button onclick="wmctrl -s 0">1</button>
<button onclick="wmctrl -s 1">2</button>
<button onclick="wmctrl -s 2">3</button>
<button onclick="wmctrl -s 3">4</button>
<button onclick="wmctrl -s 4">5</button>
<button onclick="wmctrl -s 5">6</button>
<button onclick="wmctrl -s 6">7</button>
<button onclick="wmctrl -s 7">8</button>
<button onclick="wmctrl -s 8">9</button>
<button onclick="wmctrl -s 3">4</button>
<button onclick="wmctrl -s 4">5</button>
<button onclick="wmctrl -s 5">6</button>
<button onclick="wmctrl -s 6">7</button>
<button onclick="wmctrl -s 7">8</button>
<button onclick="wmctrl -s 8">9</button>
</box>
</def>
<def name="music">
@ -45,7 +45,7 @@ contain are defined. -->
<!--A basic volume slider-->
<box orientation="h" class="slider-vol" space-evenly="false">
<box class="label-vol"></box>
<scale min="0" max="101" value="{{volume}}" onchange="amixer -D pulse sset Master {}%"/>
<scale min="0" max="101" value="{{volume}}" onchange="amixer -D pulse sset Master {}%"/>
</box>
</def>
<def name="slider-ram">
@ -66,11 +66,11 @@ contain are defined. -->
<!-- Music vars -->
<!-- These are your variables. Like they say, they run a script, and then output that to the variable. In
the following var, we get music info from playerctl. -->
<script-var name="music" interval="5s">playerctl metadata --format '{{ artist }} - {{ title }}'</script-var>
<!--Volume var-->
<!--Volume var-->
<!-- If your vars get too complex to write down here, you can use an external script and paste it's path here as well, as shown below.
-->
@ -93,8 +93,9 @@ contain are defined. -->
<windows>
<!-- These are the windows -->
<window name="bar">
<window name="bar" screen="0">
<geometry x="0%" y="0%" width="100%" height="4%"/> <!--Specifies geometry-->
<reserve side="top" distance="4%"/>
<widget>
<bar/> <!--This is the def we created earlier-->
</widget>

View file

@ -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,

View file

@ -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<i32>,
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<Self> {
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<StrutDefinition> = xml
.child("reserve")
.ok()
.map(parse_strut_definition)
.transpose()
.context("Failed to parse <reserve>")?;
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<StrutDefinition> {
Ok(StrutDefinition {
side: parse_side(xml.attr("side")?)?,
dist: xml.attr("distance")?.parse()?,
})
}
impl Struts {
pub fn from_xml_element(xml: XmlElement) -> Result<Self> {
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<Side> {
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
)),
}
}

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 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")

View file

@ -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<WindowName> },
/// 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

View file

@ -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<Self, Self::Err> {
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<Coords> {
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))
}
}