From 535f21f5f92d67a1c70d9359ed547a9a5b88c3d2 Mon Sep 17 00:00:00 2001 From: Carlos Pinto Date: Mon, 29 Aug 2022 10:20:36 +0100 Subject: [PATCH] Allow output names to select a monitor (#522) Co-authored-by: elkowar <5300871+elkowar@users.noreply.github.com> --- CHANGELOG.md | 4 ++ Cargo.lock | 1 + crates/eww/src/app.rs | 68 ++++++++++++++++++--- crates/eww/src/display_backend.rs | 7 ++- crates/eww/src/opts.rs | 9 ++- crates/eww_shared_util/Cargo.toml | 1 + crates/yuck/src/config/mod.rs | 1 + crates/yuck/src/config/monitor.rs | 39 ++++++++++++ crates/yuck/src/config/window_definition.rs | 7 ++- docs/src/configuration.md | 3 +- 10 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 crates/yuck/src/config/monitor.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f530f05..1d4b33e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to eww will be listed here, starting at changes since versio ## [Unreleased] +### Features +- Add support for output names in X11 to select `:monitor`. + + ## 0.3.0 (26.05.2022) ### BREAKING CHANGES diff --git a/Cargo.lock b/Cargo.lock index 3839d34..d5a1c5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,6 +422,7 @@ name = "eww_shared_util" version = "0.1.0" dependencies = [ "derive_more", + "gdk", "ref-cast", "serde", ] diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index d415509..367f216 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -20,6 +20,7 @@ use std::{ use tokio::sync::mpsc::UnboundedSender; use yuck::{ config::{ + monitor::MonitorIdentifier, script_var_definition::ScriptVarDefinition, window_definition::WindowDefinition, window_geometry::{AnchorPoint, WindowGeometry}, @@ -45,7 +46,7 @@ pub enum DaemonCommand { pos: Option, size: Option, anchor: Option, - screen: Option, + screen: Option, should_toggle: bool, sender: DaemonResponseSender, }, @@ -294,7 +295,7 @@ impl App { window_name: &str, pos: Option, size: Option, - monitor: Option, + monitor: Option, anchor: Option, ) -> Result<()> { self.failed_windows.remove(window_name); @@ -327,7 +328,7 @@ impl App { None, )?; - let monitor_geometry = get_monitor_geometry(monitor.or(window_def.monitor_number))?; + let monitor_geometry = get_monitor_geometry(monitor.or(window_def.monitor.clone()))?; let mut eww_window = initialize_window(monitor_geometry, root_widget, window_def, window_scope)?; eww_window.gtk_window.style_context().add_class(&window_name.to_string()); @@ -401,7 +402,7 @@ fn initialize_window( window_scope: ScopeIndex, ) -> Result { let window = display_backend::initialize_window(&window_def, monitor_geometry) - .with_context(|| format!("monitor {} is unavailable", window_def.monitor_number.unwrap()))?; + .with_context(|| format!("monitor {} is unavailable", window_def.monitor.clone().unwrap()))?; window.set_title(&format!("Eww - {}", window_def.name)); window.set_position(gtk::WindowPosition::None); @@ -476,12 +477,38 @@ fn on_screen_changed(window: >k::Window, _old_screen: Option<&gdk::Screen>) { window.set_visual(visual.as_ref()); } -/// Get the monitor geometry of a given monitor number, or the default if none is given -fn get_monitor_geometry(n: Option) -> Result { - #[allow(deprecated)] +/// Get the monitor geometry of a given monitor, or the default if none is given +fn get_monitor_geometry(identifier: Option) -> Result { let display = gdk::Display::default().expect("could not get default display"); - let monitor = match n { - Some(n) => display.monitor(n).with_context(|| format!("Failed to get monitor with index {}", n))?, + let monitor = match identifier { + Some(ident) => { + let mon = get_monitor_from_display(&display, &ident); + + #[cfg(feature = "x11")] + { + mon.with_context(|| { + let head = format!("Failed to get monitor {}\nThe available monitors are:", ident); + let mut body = String::new(); + for m in 0..display.n_monitors() { + if let Some(model) = display.monitor(m).and_then(|x| x.model()) { + body.push_str(format!("\n\t[{}] {}", m, model).as_str()); + } + } + format!("{}{}", head, body) + })? + } + + #[cfg(not(feature = "x11"))] + { + mon.with_context(|| { + if ident.is_numeric() { + format!("Failed to get monitor {}", ident) + } else { + format!("Using ouput names (\"{}\" in the configuration) is not supported outside of x11 yet", ident) + } + })? + } + } None => display .primary_monitor() .context("Failed to get primary monitor from GTK. Try explicitly specifying the monitor on your window.")?, @@ -489,6 +516,29 @@ fn get_monitor_geometry(n: Option) -> Result { Ok(monitor.geometry()) } +/// Returns the [Monitor][gdk::Monitor] structure corresponding to the identifer. +/// Outside of x11, only [MonitorIdentifier::Numeric] is supported +pub fn get_monitor_from_display(display: &gdk::Display, identifier: &MonitorIdentifier) -> Option { + match identifier { + MonitorIdentifier::Numeric(num) => return display.monitor(*num), + + #[cfg(not(feature = "x11"))] + MonitorIdentifier::Name(_) => return None, + + #[cfg(feature = "x11")] + MonitorIdentifier::Name(name) => { + for m in 0..display.n_monitors() { + if let Some(model) = display.monitor(m).and_then(|x| x.model()) { + if model == *name { + return display.monitor(m); + } + } + } + } + } + return None; +} + pub fn get_window_rectangle(geometry: WindowGeometry, screen_rect: gdk::Rectangle) -> gdk::Rectangle { let (offset_x, offset_y) = geometry.offset.relative_to(screen_rect.width(), screen_rect.height()); let (width, height) = geometry.size.relative_to(screen_rect.width(), screen_rect.height()); diff --git a/crates/eww/src/display_backend.rs b/crates/eww/src/display_backend.rs index 00386b0..99a3157 100644 --- a/crates/eww/src/display_backend.rs +++ b/crates/eww/src/display_backend.rs @@ -23,9 +23,10 @@ mod platform { // Initialising a layer shell surface gtk_layer_shell::init_for_window(&window); // Sets the monitor where the surface is shown - match window_def.monitor_number { - Some(index) => { - if let Some(monitor) = gdk::Display::default().expect("could not get default display").monitor(index) { + match window_def.monitor.clone() { + Some(ident) => { + let display = gdk::Display::default().expect("could not get default display"); + if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) { gtk_layer_shell::set_monitor(&window, &monitor); } else { return None; diff --git a/crates/eww/src/opts.rs b/crates/eww/src/opts.rs index 10dbc1f..89fae51 100644 --- a/crates/eww/src/opts.rs +++ b/crates/eww/src/opts.rs @@ -3,7 +3,10 @@ use eww_shared_util::VarName; use serde::{Deserialize, Serialize}; use simplexpr::dynval::DynVal; use structopt::StructOpt; -use yuck::{config::window_geometry::AnchorPoint, value::Coords}; +use yuck::{ + config::{monitor::MonitorIdentifier, window_geometry::AnchorPoint}, + value::Coords, +}; use crate::{ app, @@ -91,9 +94,9 @@ pub enum ActionWithServer { /// Name of the window you want to open. window_name: String, - /// Monitor-index the window should open on + /// The identifier of the monitor the window should open on #[structopt(long)] - screen: Option, + screen: Option, /// The position of the window, where it should open. (i.e.: 200x100) #[structopt(short, long)] diff --git a/crates/eww_shared_util/Cargo.toml b/crates/eww_shared_util/Cargo.toml index 35b9e08..3073ea8 100644 --- a/crates/eww_shared_util/Cargo.toml +++ b/crates/eww_shared_util/Cargo.toml @@ -12,3 +12,4 @@ homepage = "https://github.com/elkowar/eww" serde = {version = "1.0", features = ["derive"]} derive_more = "0.99" ref-cast = "1.0.6" +gdk = { version = "*", features = ["v3_22"] } diff --git a/crates/yuck/src/config/mod.rs b/crates/yuck/src/config/mod.rs index e532f62..56079b6 100644 --- a/crates/yuck/src/config/mod.rs +++ b/crates/yuck/src/config/mod.rs @@ -2,6 +2,7 @@ pub mod attributes; pub mod backend_window_options; pub mod config; pub mod file_provider; +pub mod monitor; pub mod script_var_definition; #[cfg(test)] mod test; diff --git a/crates/yuck/src/config/monitor.rs b/crates/yuck/src/config/monitor.rs new file mode 100644 index 0000000..7e5dc44 --- /dev/null +++ b/crates/yuck/src/config/monitor.rs @@ -0,0 +1,39 @@ +use std::{convert::Infallible, fmt, str}; + +use serde::{Deserialize, Serialize}; + +/// The type of the identifier used to select a monitor +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum MonitorIdentifier { + Numeric(i32), + Name(String), +} + +impl MonitorIdentifier { + pub fn is_numeric(&self) -> bool { + match self { + Self::Numeric(_) => true, + _ => false, + } + } +} + +impl fmt::Display for MonitorIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Numeric(n) => write!(f, "{}", n), + Self::Name(n) => write!(f, "{}", n), + } + } +} + +impl str::FromStr for MonitorIdentifier { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + match s.parse::() { + Ok(n) => Ok(Self::Numeric(n)), + Err(_) => Ok(Self::Name(s.to_owned())), + } + } +} diff --git a/crates/yuck/src/config/window_definition.rs b/crates/yuck/src/config/window_definition.rs index e4a7beb..0953a53 100644 --- a/crates/yuck/src/config/window_definition.rs +++ b/crates/yuck/src/config/window_definition.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, fmt::Display, str::FromStr}; use simplexpr::{dynval::DynVal, SimplExpr}; use crate::{ + config::monitor::MonitorIdentifier, error::{AstError, AstResult}, parser::{ ast::Ast, @@ -20,7 +21,7 @@ pub struct WindowDefinition { pub name: String, pub geometry: Option, pub stacking: WindowStacking, - pub monitor_number: Option, + pub monitor: Option, pub widget: WidgetUse, pub resizable: bool, pub backend_options: BackendWindowOptions, @@ -32,14 +33,14 @@ impl FromAstElementContent for WindowDefinition { fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { let (_, name) = iter.expect_symbol()?; let mut attrs = iter.expect_key_values()?; - let monitor_number = attrs.primitive_optional("monitor")?; + let monitor = attrs.primitive_optional("monitor")?; let resizable = attrs.primitive_optional("resizable")?.unwrap_or(true); let stacking = attrs.primitive_optional("stacking")?.unwrap_or(WindowStacking::Foreground); let geometry = attrs.ast_optional("geometry")?; let backend_options = BackendWindowOptions::from_attrs(&mut attrs)?; let widget = iter.expect_any().and_then(WidgetUse::from_ast)?; iter.expect_done()?; - Ok(Self { name, monitor_number, resizable, widget, stacking, geometry, backend_options }) + Ok(Self { name, monitor, resizable, widget, stacking, geometry, backend_options }) } } diff --git a/docs/src/configuration.md b/docs/src/configuration.md index b23da4a..faa0567 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -50,7 +50,7 @@ You can now open your first window by running `eww open example`! Glorious! | Property | Description | | ---------: | ------------------------------------------------------------ | -| `monitor` | Which monitor this window should be displayed on. | +| `monitor` | Which monitor this window should be displayed on. Can be either a number (X11 and Wayland) or an output name (X11 only). | | `geometry` | Geometry of the window. | @@ -284,4 +284,3 @@ If you want to separate different widgets even further, you can create a new eww Then, you can tell eww to use that configuration directory by passing _every_ command the `--config /path/to/your/config/dir` flag. Make sure to actually include this in all your `eww` calls, including `eww kill`, `eww logs`, etc. This launches a separate instance of the eww daemon that has separate logs and state from your main eww configuration. -