From 0d43339e531d9d507e47544967d114d6e84f9f02 Mon Sep 17 00:00:00 2001 From: Wilf Silver Date: Mon, 18 Apr 2022 12:41:32 +0100 Subject: [PATCH 1/9] Allow window definitions to have parameters --- CHANGELOG.md | 1 + crates/eww/src/app.rs | 163 ++++++++++++------ crates/eww/src/display_backend.rs | 54 +++--- crates/eww/src/main.rs | 2 + crates/eww/src/opts.rs | 36 +++- crates/eww/src/server.rs | 1 + crates/eww/src/window_arguments.rs | 122 +++++++++++++ crates/eww/src/window_initiator.rs | 50 ++++++ crates/simplexpr/src/dynval.rs | 8 + crates/yuck/src/config/attributes.rs | 18 +- crates/yuck/src/config/monitor.rs | 28 ++- .../snapshots/yuck__config__test__config.snap | 61 ++++++- crates/yuck/src/config/validate.rs | 8 +- crates/yuck/src/config/window_definition.rs | 32 ++-- docs/src/configuration.md | 77 +++++++++ 15 files changed, 559 insertions(+), 102 deletions(-) create mode 100644 crates/eww/src/window_arguments.rs create mode 100644 crates/eww/src/window_initiator.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f93a74ad..9becc4cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ All notable changes to eww will be listed here, starting at changes since versio - Add `:onaccept` to input field, add `:onclick` to eventbox - Add `EWW_CMD`, `EWW_CONFIG_DIR`, `EWW_EXECUTABLE` magic variables - Add `overlay` widget (By: viandoxdev) +- Add arguments option to `defwindow` (By: WilfSilver) ### Notable Internal changes - Rework state management completely, now making local state and dynamic widget hierarchy changes possible. diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index 57cf1b1b..ee56b603 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -7,6 +7,8 @@ use crate::{ paths::EwwPaths, script_var_handler::ScriptVarHandlerHandle, state::scope_graph::{ScopeGraph, ScopeIndex}, + window_arguments::WindowArguments, + window_initiator::WindowInitiator, *, }; use anyhow::anyhow; @@ -26,7 +28,6 @@ use yuck::{ config::{ monitor::MonitorIdentifier, script_var_definition::ScriptVarDefinition, - window_definition::WindowDefinition, window_geometry::{AnchorPoint, WindowGeometry}, }, error::DiagError, @@ -44,12 +45,14 @@ pub enum DaemonCommand { ReloadConfigAndCss(DaemonResponseSender), OpenInspector, OpenMany { - windows: Vec, + windows: Vec<(String, String)>, + args: Vec<(String, VarName, DynVal)>, should_toggle: bool, sender: DaemonResponseSender, }, OpenWindow { window_name: String, + instance_id: Option, pos: Option, size: Option, anchor: Option, @@ -57,6 +60,7 @@ pub enum DaemonCommand { should_toggle: bool, duration: Option, sender: DaemonResponseSender, + args: Option>, }, CloseWindows { windows: Vec, @@ -80,6 +84,7 @@ pub enum DaemonCommand { /// An opened window. #[derive(Debug)] pub struct EwwWindow { + pub instance_id: String, pub name: String, pub scope_index: ScopeIndex, pub gtk_window: gtk::Window, @@ -106,6 +111,7 @@ pub struct App { pub eww_config: config::EwwConfig, /// Map of all currently open windows pub open_windows: HashMap, + pub window_argumentss: HashMap, /// Window names that are supposed to be open, but failed. /// When reloading the config, these should be opened again. pub failed_windows: HashSet, @@ -128,6 +134,7 @@ impl std::fmt::Debug for App { .field("eww_config", &self.eww_config) .field("open_windows", &self.open_windows) .field("failed_windows", &self.failed_windows) + .field("window_argumentss", &self.window_argumentss) .field("paths", &self.paths) .finish() } @@ -178,14 +185,30 @@ impl App { self.close_window(&window_name)?; } } - DaemonCommand::OpenMany { windows, should_toggle, sender } => { + DaemonCommand::OpenMany { windows, args, should_toggle, sender } => { let errors = windows .iter() .map(|w| { - if should_toggle && self.open_windows.contains_key(w) { - self.close_window(w) + let (config_name, id) = w; + if should_toggle && self.open_windows.contains_key(id) { + self.close_window(id) } else { - self.open_window(w, None, None, None, None, None) + log::debug!("Config: {}, id: {}", config_name, id); + let window_args: Vec<(VarName, DynVal)> = args + .iter() + .filter_map(|(win_id, n, v)| { + if win_id.is_empty() || win_id == id { + Some((n.clone(), v.clone())) + } else { + None + } + }) + .collect(); + self.open_window(&WindowArguments::new_from_args( + id.to_string(), + config_name.clone(), + window_args, + )?) } }) .filter_map(Result::err); @@ -193,6 +216,7 @@ impl App { } DaemonCommand::OpenWindow { window_name, + instance_id, pos, size, anchor, @@ -200,13 +224,27 @@ impl App { should_toggle, duration, sender, + args, } => { - let is_open = self.open_windows.contains_key(&window_name); + let id = instance_id.unwrap_or_else(|| window_name.clone()); + + let is_open = self.open_windows.contains_key(&id); + let result = if should_toggle && is_open { - self.close_window(&window_name) + self.close_window(&id) } else { - self.open_window(&window_name, pos, size, monitor, anchor, duration) + self.open_window(&WindowArguments::new( + id, + window_name, + pos, + size, + monitor, + anchor, + duration, + args.unwrap_or_default(), + )) }; + sender.respond_with_result(result)?; } DaemonCommand::CloseWindows { windows, sender } => { @@ -304,14 +342,14 @@ impl App { } /// Close a window and do all the required cleanups in the scope_graph and script_var_handler - fn close_window(&mut self, window_name: &str) -> Result<()> { - if let Some(old_abort_send) = self.window_close_timer_abort_senders.remove(window_name) { + fn close_window(&mut self, instance_id: &str) -> Result<()> { + if let Some(old_abort_send) = self.window_close_timer_abort_senders.remove(instance_id) { _ = old_abort_send.send(()); } let eww_window = self .open_windows - .remove(window_name) - .with_context(|| format!("Tried to close window named '{}', but no such window was open", window_name))?; + .remove(instance_id) + .with_context(|| format!("Tried to close window named '{}', but no such window was open", instance_id))?; let scope_index = eww_window.scope_index; eww_window.close(); @@ -324,52 +362,52 @@ impl App { self.script_var_handler.stop_for_variable(unused_var.clone()); } + self.window_argumentss.remove(instance_id); + Ok(()) } - fn open_window( - &mut self, - window_name: &str, - pos: Option, - size: Option, - monitor: Option, - anchor: Option, - duration: Option, - ) -> Result<()> { - self.failed_windows.remove(window_name); - log::info!("Opening window {}", window_name); + fn open_window(&mut self, window_args: &WindowArguments) -> Result<()> { + let instance_id = &window_args.id; + self.failed_windows.remove(instance_id); + log::info!("Opening window {} as '{}'", window_args.config_name, instance_id); // if an instance of this is already running, close it - // TODO make reopening optional via a --no-reopen flag? - if self.open_windows.contains_key(window_name) { - self.close_window(window_name)?; + if self.open_windows.contains_key(instance_id) { + self.close_window(instance_id)?; } + self.window_argumentss.insert(instance_id.to_string(), window_args.clone()); + let open_result: Result<_> = try { - let mut window_def = self.eww_config.get_window(window_name)?.clone(); + let window_name: &str = &window_args.config_name; + + let window_def = self.eww_config.get_window(window_name)?.clone(); assert_eq!(window_def.name, window_name, "window definition name did not equal the called window"); - window_def.geometry = window_def.geometry.map(|x| x.override_if_given(anchor, pos, size)); + + let initiator = WindowInitiator::new(&window_def, window_args)?; let root_index = self.scope_graph.borrow().root_index; let window_scope = self.scope_graph.borrow_mut().register_new_scope( - window_name.to_string(), + instance_id.to_string(), Some(root_index), root_index, - HashMap::new(), + initiator.get_scoped_vars(), )?; let root_widget = crate::widgets::build_widget::build_gtk_widget( &mut self.scope_graph.borrow_mut(), Rc::new(self.eww_config.get_widget_definitions().clone()), window_scope, - window_def.widget.clone(), + window_def.widget, None, )?; - let monitor_geometry = get_monitor_geometry(monitor.or_else(|| window_def.monitor.clone()))?; + root_widget.style_context().add_class(window_name); - let mut eww_window = initialize_window::(monitor_geometry, root_widget, window_def, window_scope)?; + let monitor_geometry = get_monitor_geometry(initiator.monitor.clone())?; + let mut eww_window = initialize_window::(&initiator, monitor_geometry, root_widget, window_scope)?; eww_window.gtk_window.style_context().add_class(window_name); // initialize script var handlers for variables. As starting a scriptvar with the script_var_handler is idempodent, @@ -383,32 +421,32 @@ impl App { eww_window.destroy_event_handler_id = Some(eww_window.gtk_window.connect_destroy({ let app_evt_sender = self.app_evt_send.clone(); - let window_name: String = eww_window.name.to_string(); + let instance_id = instance_id.to_string(); move |_| { // we don't care about the actual error response from the daemon as this is mostly just a fallback. // Generally, this should get disconnected before the gtk window gets destroyed. // It serves as a fallback for when the window is closed manually. let (response_sender, _) = daemon_response::create_pair(); - let command = DaemonCommand::CloseWindows { windows: vec![window_name.clone()], sender: response_sender }; + let command = DaemonCommand::CloseWindows { windows: vec![instance_id.clone()], sender: response_sender }; if let Err(err) = app_evt_sender.send(command) { log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err); } } })); + let duration = window_args.duration; if let Some(duration) = duration { let app_evt_sender = self.app_evt_send.clone(); - let window_name = window_name.to_string(); let (abort_send, abort_recv) = futures::channel::oneshot::channel(); glib::MainContext::default().spawn_local({ - let window_name = window_name.clone(); + let instance_id = instance_id.to_string(); async move { tokio::select! { _ = glib::timeout_future(duration) => { let (response_sender, mut response_recv) = daemon_response::create_pair(); - let command = DaemonCommand::CloseWindows { windows: vec![window_name], sender: response_sender }; + let command = DaemonCommand::CloseWindows { windows: vec![instance_id.clone()], sender: response_sender }; if let Err(err) = app_evt_sender.send(command) { log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err); } @@ -419,17 +457,17 @@ impl App { } }); - if let Some(old_abort_send) = self.window_close_timer_abort_senders.insert(window_name, abort_send) { + if let Some(old_abort_send) = self.window_close_timer_abort_senders.insert(instance_id.to_string(), abort_send) { _ = old_abort_send.send(()); } } - self.open_windows.insert(window_name.to_string(), eww_window); + self.open_windows.insert(instance_id.to_string(), eww_window); }; if let Err(err) = open_result { - self.failed_windows.insert(window_name.to_string()); - Err(err).with_context(|| format!("failed to open window `{}`", window_name)) + self.failed_windows.insert(instance_id.to_string()); + Err(err).with_context(|| format!("failed to open window `{}`", instance_id)) } else { Ok(()) } @@ -448,10 +486,19 @@ impl App { self.eww_config = config; self.scope_graph.borrow_mut().clear(self.eww_config.generate_initial_state()?); - let window_names: Vec = + let instances: Vec = self.open_windows.keys().cloned().chain(self.failed_windows.iter().cloned()).dedup().collect(); - for window_name in &window_names { - self.open_window(window_name, None, None, None, None, None)?; + let initiators = self.window_argumentss.clone(); + for instance_id in &instances { + let window_arguments; + match initiators.get(instance_id) { + Some(x) => window_arguments = x, + None => { + return Err(anyhow!("Cannot reopen window, initial parameters were not saved correctly for {}", instance_id)) + } + }; + + self.open_window(window_arguments)?; } Ok(()) } @@ -480,19 +527,19 @@ impl App { } fn initialize_window( + window_init: &WindowInitiator, monitor_geometry: gdk::Rectangle, root_widget: gtk::Widget, - window_def: WindowDefinition, window_scope: ScopeIndex, ) -> Result { - let window = B::initialize_window(&window_def, monitor_geometry) - .with_context(|| format!("monitor {} is unavailable", window_def.monitor.clone().unwrap()))?; + let window = B::initialize_window(window_init, monitor_geometry) + .with_context(|| format!("monitor {} is unavailable", window_init.monitor.clone().unwrap()))?; - window.set_title(&format!("Eww - {}", window_def.name)); + window.set_title(&format!("Eww - {}", window_init.name)); window.set_position(gtk::WindowPosition::None); window.set_gravity(gdk::Gravity::Center); - if let Some(geometry) = window_def.geometry { + if let Some(geometry) = window_init.geometry { let actual_window_rect = get_window_rectangle(geometry, monitor_geometry); window.set_size_request(actual_window_rect.width(), actual_window_rect.height()); window.set_default_size(actual_window_rect.width(), actual_window_rect.height()); @@ -511,21 +558,27 @@ fn initialize_window( #[cfg(feature = "x11")] if B::IS_X11 { - if let Some(geometry) = window_def.geometry { + if let Some(geometry) = window_init.geometry { let _ = apply_window_position(geometry, monitor_geometry, &window); - if window_def.backend_options.x11.window_type != yuck::config::backend_window_options::X11WindowType::Normal { + if window_init.backend_options.x11.window_type != yuck::config::backend_window_options::X11WindowType::Normal { window.connect_configure_event(move |window, _| { let _ = apply_window_position(geometry, monitor_geometry, window); false }); } } - display_backend::set_xprops(&window, monitor_geometry, &window_def)?; + display_backend::set_xprops(&window, monitor_geometry, window_init)?; } window.show_all(); - Ok(EwwWindow { name: window_def.name, gtk_window: window, scope_index: window_scope, destroy_event_handler_id: None }) + Ok(EwwWindow { + instance_id: window_init.id.clone(), + name: window_init.name.clone(), + gtk_window: window, + scope_index: window_scope, + destroy_event_handler_id: None, + }) } /// Apply the provided window-positioning rules to the window. diff --git a/crates/eww/src/display_backend.rs b/crates/eww/src/display_backend.rs index e1830201..bf8e3774 100644 --- a/crates/eww/src/display_backend.rs +++ b/crates/eww/src/display_backend.rs @@ -1,4 +1,4 @@ -use yuck::config::window_definition::WindowDefinition; +use crate::window_initiator::WindowInitiator; #[cfg(feature = "wayland")] pub use platform_wayland::WaylandBackend; @@ -8,7 +8,8 @@ pub use platform_x11::{set_xprops, X11Backend}; pub trait DisplayBackend: Send + Sync + 'static { const IS_X11: bool; - fn initialize_window(window_def: &WindowDefinition, monitor: gdk::Rectangle) -> Option; + + fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle) -> Option; } pub struct NoBackend; @@ -16,18 +17,16 @@ pub struct NoBackend; impl DisplayBackend for NoBackend { const IS_X11: bool = false; - fn initialize_window(_window_def: &WindowDefinition, _monitor: gdk::Rectangle) -> Option { + fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Rectangle) -> Option { Some(gtk::Window::new(gtk::WindowType::Toplevel)) } } #[cfg(feature = "wayland")] mod platform_wayland { + use crate::window_initiator::WindowInitiator; use gtk::prelude::*; - use yuck::config::{ - window_definition::{WindowDefinition, WindowStacking}, - window_geometry::AnchorAlignment, - }; + use yuck::config::{window_definition::WindowStacking, window_geometry::AnchorAlignment}; use super::DisplayBackend; @@ -36,12 +35,12 @@ mod platform_wayland { impl DisplayBackend for WaylandBackend { const IS_X11: bool = false; - fn initialize_window(window_def: &WindowDefinition, monitor: gdk::Rectangle) -> Option { + fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle) -> Option { let window = gtk::Window::new(gtk::WindowType::Toplevel); // Initialising a layer shell surface gtk_layer_shell::init_for_window(&window); // Sets the monitor where the surface is shown - if let Some(ident) = window_def.monitor.clone() { + if let Some(ident) = window_init.monitor.clone() { 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); @@ -49,24 +48,24 @@ mod platform_wayland { return None; } }; - window.set_resizable(window_def.resizable); + window.set_resizable(window_init.resizable); // Sets the layer where the layer shell surface will spawn - match window_def.stacking { + match window_init.stacking { WindowStacking::Foreground => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Top), WindowStacking::Background => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Background), WindowStacking::Bottom => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Bottom), WindowStacking::Overlay => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Overlay), } - if let Some(namespace) = &window_def.backend_options.wayland.namespace { + if let Some(namespace) = &window_init.backend_options.wayland.namespace { gtk_layer_shell::set_namespace(&window, namespace); } // Sets the keyboard interactivity - gtk_layer_shell::set_keyboard_interactivity(&window, window_def.backend_options.wayland.focusable); + gtk_layer_shell::set_keyboard_interactivity(&window, window_init.backend_options.wayland.focusable); - if let Some(geometry) = window_def.geometry { + if let Some(geometry) = window_init.geometry { // Positioning surface let mut top = false; let mut left = false; @@ -103,7 +102,7 @@ mod platform_wayland { gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Top, yoffset); } } - if window_def.backend_options.wayland.exclusive { + if window_init.backend_options.wayland.exclusive { gtk_layer_shell::auto_exclusive_zone_enable(&window); } Some(window) @@ -113,6 +112,7 @@ mod platform_wayland { #[cfg(feature = "x11")] mod platform_x11 { + use crate::window_initiator::WindowInitiator; use anyhow::{Context, Result}; use gtk::{self, prelude::*}; use x11rb::protocol::xproto::ConnectionExt; @@ -125,7 +125,7 @@ mod platform_x11 { }; use yuck::config::{ backend_window_options::{Side, X11WindowType}, - window_definition::{WindowDefinition, WindowStacking}, + window_definition::WindowStacking, }; use super::DisplayBackend; @@ -134,14 +134,14 @@ mod platform_x11 { impl DisplayBackend for X11Backend { const IS_X11: bool = true; - fn initialize_window(window_def: &WindowDefinition, _monitor: gdk::Rectangle) -> Option { + fn initialize_window(window_init: &WindowInitiator, _monitor: gdk::Rectangle) -> Option { let window_type = - if window_def.backend_options.x11.wm_ignore { gtk::WindowType::Popup } else { gtk::WindowType::Toplevel }; + if window_init.backend_options.x11.wm_ignore { gtk::WindowType::Popup } else { gtk::WindowType::Toplevel }; let window = gtk::Window::new(window_type); - window.set_resizable(window_def.resizable); - window.set_keep_above(window_def.stacking == WindowStacking::Foreground); - window.set_keep_below(window_def.stacking == WindowStacking::Background); - if window_def.backend_options.x11.sticky { + window.set_resizable(window_init.resizable); + window.set_keep_above(window_init.stacking == WindowStacking::Foreground); + window.set_keep_below(window_init.stacking == WindowStacking::Background); + if window_init.backend_options.x11.sticky { window.stick(); } else { window.unstick(); @@ -150,9 +150,9 @@ mod platform_x11 { } } - pub fn set_xprops(window: >k::Window, monitor: gdk::Rectangle, window_def: &WindowDefinition) -> Result<()> { + pub fn set_xprops(window: >k::Window, monitor: gdk::Rectangle, window_init: &WindowInitiator) -> Result<()> { let backend = X11BackendConnection::new()?; - backend.set_xprops_for(window, monitor, window_def)?; + backend.set_xprops_for(window, monitor, window_init)?; Ok(()) } @@ -174,12 +174,12 @@ mod platform_x11 { &self, window: >k::Window, monitor_rect: gdk::Rectangle, - window_def: &WindowDefinition, + window_init: &WindowInitiator, ) -> Result<()> { let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?; let win_id = gdk_window.downcast_ref::().context("Failed to get x11 window for gtk window")?.xid() as u32; - let strut_def = window_def.backend_options.x11.struts; + let strut_def = window_init.backend_options.x11.struts; 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; @@ -233,7 +233,7 @@ mod platform_x11 { win_id, self.atoms._NET_WM_WINDOW_TYPE, self.atoms.ATOM, - &[match window_def.backend_options.x11.window_type { + &[match window_init.backend_options.x11.window_type { X11WindowType::Dock => self.atoms._NET_WM_WINDOW_TYPE_DOCK, X11WindowType::Normal => self.atoms._NET_WM_WINDOW_TYPE_NORMAL, X11WindowType::Dialog => self.atoms._NET_WM_WINDOW_TYPE_DIALOG, diff --git a/crates/eww/src/main.rs b/crates/eww/src/main.rs index 9211d03b..1e00f5e3 100644 --- a/crates/eww/src/main.rs +++ b/crates/eww/src/main.rs @@ -36,6 +36,8 @@ mod server; mod state; mod util; mod widgets; +mod window_arguments; +mod window_initiator; fn main() { let eww_binary_name = std::env::args().next().unwrap(); diff --git a/crates/eww/src/opts.rs b/crates/eww/src/opts.rs index 75d08c3b..35777a04 100644 --- a/crates/eww/src/opts.rs +++ b/crates/eww/src/opts.rs @@ -101,6 +101,10 @@ pub enum ActionWithServer { /// Name of the window you want to open. window_name: String, + // The id of the window instance + #[arg(long)] + id: Option, + /// The identifier of the monitor the window should open on #[arg(long)] screen: Option, @@ -124,13 +128,20 @@ pub enum ActionWithServer { /// Automatically close the window after a specified amount of time, i.e.: 1s #[arg(long, value_parser=parse_duration)] duration: Option, + + #[arg(long, value_parser = parse_var_update_arg)] + arg: Option>, }, /// Open multiple windows at once. /// NOTE: This will in the future be part of eww open, and will then be removed. #[command(name = "open-many")] OpenMany { - windows: Vec, + #[arg(value_parser = parse_window_config_and_id)] + windows: Vec<(String, String)>, + + #[arg(long, value_parser = parse_window_id_args)] + arg: Vec<(String, VarName, DynVal)>, /// If a window is already open, close it instead #[arg(long = "toggle")] @@ -195,6 +206,21 @@ impl From for Opt { } } +fn parse_window_config_and_id(s: &str) -> Result<(String, String)> { + let (name, id) = s.split_once(':').unwrap_or((s, s)); + + Ok((name.to_string(), id.to_string())) +} + +fn parse_window_id_args(s: &str) -> Result<(String, VarName, DynVal)> { + // Parse the = first so we know if an id has not been given + let (name, value) = parse_var_update_arg(s)?; + + let (id, var_name) = (&name.0).split_once(':').unwrap_or((&"", &name.0)); + + Ok((id.to_string(), var_name.into(), value)) +} + fn parse_var_update_arg(s: &str) -> Result<(VarName, DynVal)> { let (name, value) = s .split_once('=') @@ -219,12 +245,13 @@ impl ActionWithServer { let _ = send.send(DaemonResponse::Success("pong".to_owned())); return (app::DaemonCommand::NoOp, Some(recv)); } - ActionWithServer::OpenMany { windows, should_toggle } => { - return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, should_toggle, sender }); + ActionWithServer::OpenMany { windows, arg: args, should_toggle } => { + return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, args, should_toggle, sender }); } - ActionWithServer::OpenWindow { window_name, pos, size, screen, anchor, should_toggle, duration } => { + ActionWithServer::OpenWindow { window_name, id, pos, size, screen, anchor, should_toggle, duration, arg: args } => { return with_response_channel(|sender| app::DaemonCommand::OpenWindow { window_name, + instance_id: id, pos, size, anchor, @@ -232,6 +259,7 @@ impl ActionWithServer { should_toggle, duration, sender, + args, }) } ActionWithServer::CloseWindows { windows } => { diff --git a/crates/eww/src/server.rs b/crates/eww/src/server.rs index fbe2286a..feebfce2 100644 --- a/crates/eww/src/server.rs +++ b/crates/eww/src/server.rs @@ -81,6 +81,7 @@ pub fn initialize_server( eww_config, open_windows: HashMap::new(), failed_windows: HashSet::new(), + window_argumentss: HashMap::new(), css_provider: gtk::CssProvider::new(), script_var_handler, app_evt_send: ui_send.clone(), diff --git a/crates/eww/src/window_arguments.rs b/crates/eww/src/window_arguments.rs new file mode 100644 index 00000000..7a7e03a0 --- /dev/null +++ b/crates/eww/src/window_arguments.rs @@ -0,0 +1,122 @@ +use anyhow::{anyhow, Context, Result}; +use eww_shared_util::VarName; +use simplexpr::dynval::DynVal; +use std::{ + collections::{HashMap, HashSet}, + str::FromStr, +}; +use yuck::{ + config::{monitor::MonitorIdentifier, window_definition::WindowDefinition, window_geometry::AnchorPoint}, + value::Coords, +}; + +fn extract_value_from_args(name: &str, args: &mut Vec<(VarName, DynVal)>) -> Option { + let var_name = name.to_string(); + let pos = args.iter().position(|(n, _)| n.0 == var_name); + + if let Some(unwrapped_pos) = pos { + let (_, val) = args.remove(unwrapped_pos); + Some(val) + } else { + None + } +} + +fn parse_value_from_args(name: &str, args: &mut Vec<(VarName, DynVal)>) -> Result, T::Err> { + extract_value_from_args(name, args).map(|x| FromStr::from_str(&x.as_string().unwrap())).transpose() +} + +/// This stores the arguments given in the command line to create a window +/// While creating a window, we combine this with information from the +/// WindowDefinition to create a WindowInitiator, which stores all the +/// information required to start a window +#[derive(Debug, Clone)] +pub struct WindowArguments { + pub anchor: Option, + pub args: Vec<(VarName, DynVal)>, + pub config_name: String, + pub duration: Option, + pub id: String, + pub monitor: Option, + pub pos: Option, + pub size: Option, +} + +impl WindowArguments { + pub fn new( + id: String, + config_name: String, + pos: Option, + size: Option, + monitor: Option, + anchor: Option, + duration: Option, + args: Vec<(VarName, DynVal)>, + ) -> Self { + WindowArguments { id, config_name, pos, size, monitor, anchor, duration, args } + } + + pub fn new_from_args(id: String, config_name: String, mut args: Vec<(VarName, DynVal)>) -> Result { + let initiator = WindowArguments { + config_name, + id, + pos: parse_value_from_args::("pos", &mut args)?, + size: parse_value_from_args::("size", &mut args)?, + monitor: parse_value_from_args::("screen", &mut args)?, + anchor: parse_value_from_args::("anchor", &mut args)?, + duration: extract_value_from_args("duration", &mut args) + .map(|x| x.as_duration()) + .transpose() + .context("Not a valid duration")?, + args, + }; + + Ok(initiator) + } + + pub fn get_local_window_variables(&self, window_def: &WindowDefinition) -> Result> { + let expected_args: HashSet<&String> = window_def.expected_args.iter().map(|x| &x.name.0).collect(); + let mut local_variables: HashMap = HashMap::new(); + + // Inserts these first so they can be overridden + if expected_args.contains(&"id".to_string()) { + local_variables.insert(VarName::from("id"), DynVal::from(self.id.clone())); + } + if self.monitor.is_some() && expected_args.contains(&"screen".to_string()) { + let mon_dyn = self.monitor.clone().unwrap().to_dynval(); + local_variables.insert(VarName::from("screen"), mon_dyn); + } + + local_variables.extend(self.args.clone()); + + for attr in &window_def.expected_args { + let name = VarName::from(attr.name.clone()); + + // This is here to get around the map_entry warning + let mut inserted = false; + local_variables.entry(name).or_insert_with(|| { + inserted = true; + DynVal::from_string(String::new()) + }); + + if inserted && !attr.optional { + return Err(anyhow!("Error, {} was required when creating {} but was not given", attr.name, self.config_name)); + } + } + + if local_variables.len() != window_def.expected_args.len() { + let unexpected_vars: Vec = local_variables + .iter() + .filter_map(|(n, _)| if !expected_args.contains(&n.0) { Some(n.clone()) } else { None }) + .collect(); + return Err(anyhow!( + "'{}' {} unexpectedly defined when creating window {}", + unexpected_vars.join(","), + if unexpected_vars.len() == 1 { "was" } else { "were" }, + self.config_name + )); + } + + Ok(local_variables) + } +} diff --git a/crates/eww/src/window_initiator.rs b/crates/eww/src/window_initiator.rs new file mode 100644 index 00000000..3e72e785 --- /dev/null +++ b/crates/eww/src/window_initiator.rs @@ -0,0 +1,50 @@ +use anyhow::Result; +use eww_shared_util::{AttrName, VarName}; +use simplexpr::{dynval::DynVal, SimplExpr}; +use std::collections::HashMap; +use yuck::config::{ + backend_window_options::BackendWindowOptions, + monitor::MonitorIdentifier, + window_definition::{WindowDefinition, WindowStacking}, + window_geometry::WindowGeometry, +}; + +use crate::window_arguments::WindowArguments; + +/// This stores all the information required to create a window and is created +/// via combining information from the WindowDefinition and the WindowInitiator +#[derive(Debug, Clone)] +pub struct WindowInitiator { + pub backend_options: BackendWindowOptions, + pub geometry: Option, + pub id: String, + pub local_variables: HashMap, + pub monitor: Option, + pub name: String, + pub resizable: bool, + pub stacking: WindowStacking, +} + +impl WindowInitiator { + pub fn new(window_def: &WindowDefinition, args: &WindowArguments) -> Result { + let vars = args.get_local_window_variables(window_def)?; + let geometry = window_def.geometry.map(|x| x.override_if_given(args.anchor, args.pos, args.size)); + + let monitor = if args.monitor.is_none() { window_def.get_monitor(&vars)? } else { args.monitor.clone() }; + + Ok(WindowInitiator { + backend_options: window_def.backend_options.clone(), + geometry, + id: args.id.clone(), + local_variables: vars, + monitor, + name: window_def.name.clone(), + resizable: window_def.resizable, + stacking: window_def.stacking, + }) + } + + pub fn get_scoped_vars(&self) -> HashMap { + self.local_variables.iter().map(|(k, v)| (AttrName::from(k.clone()), SimplExpr::Literal(v.clone()))).collect() + } +} diff --git a/crates/simplexpr/src/dynval.rs b/crates/simplexpr/src/dynval.rs index 2bdc3129..33530fb3 100644 --- a/crates/simplexpr/src/dynval.rs +++ b/crates/simplexpr/src/dynval.rs @@ -106,6 +106,14 @@ impl TryFrom for DynVal { } } +impl From> for DynVal { + fn from(v: Vec) -> Self { + let span = if let (Some(first), Some(last)) = (v.first(), v.last()) { first.span().to(last.span()) } else { Span::DUMMY }; + let elements = v.into_iter().map(|x| x.as_string().unwrap()).collect::>(); + DynVal(serde_json::to_string(&elements).unwrap(), span) + } +} + impl From for DynVal { fn from(d: std::time::Duration) -> Self { DynVal(format!("{}ms", d.as_millis()), Span::DUMMY) diff --git a/crates/yuck/src/config/attributes.rs b/crates/yuck/src/config/attributes.rs index 9a10eb40..b91a562a 100644 --- a/crates/yuck/src/config/attributes.rs +++ b/crates/yuck/src/config/attributes.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use simplexpr::{dynval::FromDynVal, eval::EvalError, SimplExpr}; use crate::{ - error::DiagError, + error::{DiagError, DiagResult}, parser::{ast::Ast, from_ast::FromAst}, }; use eww_shared_util::{AttrName, Span, Spanned}; @@ -109,3 +109,19 @@ impl Attributes { self.attrs.into_iter().map(|(k, v)| (v.key_span.to(v.value.span()), k)) } } + +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] +pub struct AttrSpec { + pub name: AttrName, + pub optional: bool, + pub span: Span, +} + +impl FromAst for AttrSpec { + fn from_ast(e: Ast) -> DiagResult { + let span = e.span(); + let symbol = e.as_symbol()?; + let (name, optional) = if let Some(name) = symbol.strip_prefix('?') { (name.to_string(), true) } else { (symbol, false) }; + Ok(Self { name: AttrName(name), optional, span }) + } +} diff --git a/crates/yuck/src/config/monitor.rs b/crates/yuck/src/config/monitor.rs index a6432912..9aee9cef 100644 --- a/crates/yuck/src/config/monitor.rs +++ b/crates/yuck/src/config/monitor.rs @@ -1,6 +1,11 @@ -use std::{convert::Infallible, fmt, str}; +use std::{ + convert::Infallible, + fmt, + str::{self, FromStr}, +}; use serde::{Deserialize, Serialize}; +use simplexpr::dynval::{ConversionError, DynVal}; /// The type of the identifier used to select a monitor #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -12,6 +17,27 @@ pub enum MonitorIdentifier { } impl MonitorIdentifier { + pub fn from_dynval(val: &DynVal) -> Result { + match val.as_json_array() { + Ok(arr) => Ok(MonitorIdentifier::List( + arr.iter().map(|x| MonitorIdentifier::from_dynval(&x.into())).collect::>()?, + )), + Err(_) => match val.as_i32() { + Ok(x) => Ok(MonitorIdentifier::Numeric(x)), + Err(_) => Ok(MonitorIdentifier::from_str(&val.as_string().unwrap()).unwrap()), + }, + } + } + + pub fn to_dynval(&self) -> DynVal { + match self { + Self::List(l) => l.iter().map(|x| x.to_dynval()).collect::>().into(), + Self::Numeric(n) => DynVal::from(*n), + Self::Name(n) => DynVal::from(n.clone()), + Self::Primary => DynVal::from(""), + } + } + pub fn is_numeric(&self) -> bool { matches!(self, Self::Numeric(_)) } diff --git a/crates/yuck/src/config/snapshots/yuck__config__test__config.snap b/crates/yuck/src/config/snapshots/yuck__config__test__config.snap index 2440bf03..3a511e1f 100644 --- a/crates/yuck/src/config/snapshots/yuck__config__test__config.snap +++ b/crates/yuck/src/config/snapshots/yuck__config__test__config.snap @@ -41,6 +41,8 @@ Config( window_definitions: { "some-window": WindowDefinition( name: "some-window", + expected_args: [], + args_span: Span(18446744073709551615, 18446744073709551615, 18446744073709551615), geometry: Some(WindowGeometry( anchor_point: AnchorPoint( x: START, @@ -56,7 +58,7 @@ Config( ), )), stacking: Foreground, - monitor_number: Some(12), + monitor_number: Some(Literal(DynVal("12", Span(278, 280, 0)))), widget: Basic(BasicWidgetUse( name: "bar", attrs: Attributes( @@ -83,6 +85,63 @@ Config( ), ), ), + "some-window-with-args": WindowDefinition( + name: "some-window-with-args", + expected_args: [ + AttrSpec( + name: AttrName("arg"), + optional: false, + span: Span(523, 526, 0), + ), + AttrSpec( + name: AttrName("arg2"), + optional: false, + span: Span(527, 531, 0), + ), + ], + args_span: Span(522, 532, 0), + geometry: Some(WindowGeometry( + anchor_point: AnchorPoint( + x: START, + y: START, + ), + offset: Coords( + x: Pixels(0), + y: Pixels(0), + ), + size: Coords( + x: Percent(12), + y: Pixels(20), + ), + )), + stacking: Foreground, + monitor_number: Some(Literal(DynVal("12", Span(595, 597, 0)))), + widget: Basic(BasicWidgetUse( + name: "bar", + attrs: Attributes( + span: Span(784, 795, 0), + attrs: { + AttrName("arg"): AttrEntry( + key_span: Span(785, 789, 0), + value: SimplExpr(Span(790, 795, 0), Literal(DynVal("bla", Span(790, 795, 0)))), + ), + }, + ), + children: [], + span: Span(780, 796, 0), + name_span: Span(781, 784, 0), + )), + resizable: true, + backend_options: BackendWindowOptions( + wm_ignore: false, + sticky: true, + window_type: Dock, + struts: StrutDefinition( + side: Left, + dist: Pixels(30), + ), + ), + ), }, var_definitions: { VarName("some_var"): VarDefinition( diff --git a/crates/yuck/src/config/validate.rs b/crates/yuck/src/config/validate.rs index 5f9b035a..0c20e5c2 100644 --- a/crates/yuck/src/config/validate.rs +++ b/crates/yuck/src/config/validate.rs @@ -33,13 +33,17 @@ impl Spanned for ValidationError { } pub fn validate(config: &Config, additional_globals: Vec) -> Result<(), ValidationError> { - let var_names = std::iter::empty() + let var_names: HashSet = std::iter::empty() .chain(additional_globals.iter().cloned()) .chain(config.script_vars.keys().cloned()) .chain(config.var_definitions.keys().cloned()) .collect(); for window in config.window_definitions.values() { - validate_variables_in_widget_use(&config.widget_definitions, &var_names, &window.widget, false)?; + let local_var_names: HashSet = std::iter::empty() + .chain(var_names.iter().cloned()) + .chain(window.expected_args.iter().map(|x| VarName::from(x.name.clone()))) + .collect(); + validate_variables_in_widget_use(&config.widget_definitions, &local_var_names, &window.widget, false)?; } for def in config.widget_definitions.values() { validate_widget_definition(&config.widget_definitions, &var_names, def)?; diff --git a/crates/yuck/src/config/window_definition.rs b/crates/yuck/src/config/window_definition.rs index f62ed364..7347f9cf 100644 --- a/crates/yuck/src/config/window_definition.rs +++ b/crates/yuck/src/config/window_definition.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, str::FromStr}; +use std::{collections::HashMap, fmt::Display}; use crate::{ config::monitor::MonitorIdentifier, @@ -9,26 +9,34 @@ use crate::{ from_ast::{FromAst, FromAstElementContent}, }, }; -use eww_shared_util::Span; +use eww_shared_util::{Span, VarName}; +use simplexpr::{dynval::DynVal, eval::EvalError, SimplExpr}; -use super::{backend_window_options::BackendWindowOptions, widget_use::WidgetUse, window_geometry::WindowGeometry}; +use super::{ + attributes::AttrSpec, backend_window_options::BackendWindowOptions, widget_use::WidgetUse, window_geometry::WindowGeometry, +}; #[derive(Debug, Clone, serde::Serialize, PartialEq)] pub struct WindowDefinition { pub name: String, + pub expected_args: Vec, + pub args_span: Span, pub geometry: Option, pub stacking: WindowStacking, - pub monitor: Option, + pub monitor: Option, pub widget: WidgetUse, pub resizable: bool, pub backend_options: BackendWindowOptions, } -impl FromAst for MonitorIdentifier { - fn from_ast(x: Ast) -> DiagResult { - match x { - Ast::Array(_, x) => Ok(Self::List(x.into_iter().map(MonitorIdentifier::from_ast).collect::>()?)), - other => Ok(Self::from_str(&String::from_ast(other)?).unwrap()), +impl WindowDefinition { + pub fn get_monitor(&self, local_variables: &HashMap) -> Result, EvalError> { + match &self.monitor { + Some(monitor_expr) => match monitor_expr.eval(local_variables) { + Ok(val) => Ok(Some(MonitorIdentifier::from_dynval(&val)?)), + Err(err) => Err(err), + }, + None => Ok(None), } } } @@ -38,15 +46,17 @@ impl FromAstElementContent for WindowDefinition { fn from_tail>(_span: Span, mut iter: AstIterator) -> DiagResult { let (_, name) = iter.expect_symbol()?; + let (args_span, expected_args) = iter.expect_array().unwrap_or((Span::DUMMY, Vec::new())); + let expected_args = expected_args.into_iter().map(AttrSpec::from_ast).collect::>()?; let mut attrs = iter.expect_key_values()?; - let monitor = attrs.ast_optional::("monitor")?; + let monitor = attrs.ast_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().map_err(DiagError::from).and_then(WidgetUse::from_ast)?; iter.expect_done()?; - Ok(Self { name, monitor, resizable, widget, stacking, geometry, backend_options }) + Ok(Self { name, expected_args, args_span, monitor, resizable, widget, stacking, geometry, backend_options }) } } diff --git a/docs/src/configuration.md b/docs/src/configuration.md index 975099b5..f87211b0 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -259,6 +259,83 @@ Eww then reads the provided value and renders the resulting widget. Whenever it Note that this is not all that efficient. Make sure to only use `literal` when necessary! +## Dynamically generated windows with arguments and ids + +In some cases you may want to use the same window configuration for multiple widgets, e.g. for multiple windows. This is where arugments and ids come in. + +Firstly let us start off with ids. An id can be specified in the `open` command with `--id`, by default the id will be set to the name of the window configuration. These ids allow you to spawn multiple of the same windows. So for example you can do: + +```bash +eww open my_bar --screen 0 --id primary +eww open my_bar --screen 1 --id secondary +``` + +When using `open-many` you can follow the structure below. Again if no id is given, the id will default to the name of the window configuration. + +```bash +eww open-many my_config:primary my_config:secondary +``` + +You may notice with this we didn't set `screen`, this is set through the `--arg` system, please see below for more information. + +However you may want to have slight changes for each of these bars, e.g. spawning other windows on the same monitor. This is where the arguments come in. + +Defining arguments in a window is the exact same as in a widget so you can have: + +```lisp +(defwindow my_bar [arg1 ?arg2] + :geometry (geometry + :x "0%" + :y "6px" + :width "100%" + :height "30px" + :anchor "top center") + :stacking "bg" + :windowtype "dock" + :reserve (struts :distance "50px" :side "top") + ...) +``` + +Here we have two arguments, `arg1` and `arg2` (an optional parameter). + +Once we have these parameters, when opening a new window, we must specify them (unless they are required, like `arg2`), but how? Well, we use the `--arg` option when running the `open` command: + +```bash +eww open my_bar --id primary --arg arg1=some_value --arg arg2=another_value +``` + +With the `open-many` it looks like this: + +```bash +# Please note that `--arg` option must be given after all the windows names +eww open-many my_bar:primary --arg primary:arg1=some_value --arg primary:arg2=another_value +``` + +Using this method you can define `screen`, `anchor`, `pos`, `size` inside the args for each window and it will act like giving `--screen`, `--anchor` etc. in the `open` command. + +You may notice that this is the same layout to set values with `update` and you'd be correct. + +So, now you know the basics, I shall introduce you to some of these "special" parameters, which are set slightly differently. However these can all be overridden by the `--arg` option. + +- `id` - If `id` is included in the argument list, it will be set to the id specified by `--id` or will be set to the name of the config. This can be used when closing the current window through eww commands. +- `screen` - If `screen` is specified it will be set to the value given by `--screen`, so you can use this in other widgets to access screen specific information. + +### Further insight into args in `open-many` + +Now due to the system behind processing the `open-many` `--arg` option you don't have to specify an id for each argument. If you do not, that argument will be applied across all windows e.g. + +```bash +eww open-many -c "~/.config/eww/bars" my_bar:primary my_bar:secondary --arg config="~/.config/eww/bars" +``` + +This will mean the config is the same throughout the bars. + +Furthermore if you didn't specify an id for the window, you can still set args specifically for that window - following the idea that the id will be set to the window configuration if not given - by just using the name of the window configuration e.g. + +```bash +eww open-many my_primary_bar --arg my_primary_bar:screen=0 +``` + ## Generating a list of widgets from JSON using `for` If you want to display a list of values, you can use the `for`-Element to fill a container with a list of elements generated from a JSON-array. From 76dd65f7bbc2fdc312c7f486708c9e1505febc2a Mon Sep 17 00:00:00 2001 From: Wilf Silver Date: Sun, 2 Jul 2023 12:11:44 +0100 Subject: [PATCH 2/9] Implement SimplExpr for all other window definition options --- crates/eww/src/window_initiator.rs | 16 +- .../yuck/src/config/backend_window_options.rs | 149 +++++++++++++++--- crates/yuck/src/config/window_definition.rs | 56 +++++-- crates/yuck/src/config/window_geometry.rs | 88 +++++++++-- 4 files changed, 246 insertions(+), 63 deletions(-) diff --git a/crates/eww/src/window_initiator.rs b/crates/eww/src/window_initiator.rs index 3e72e785..f4af1cb1 100644 --- a/crates/eww/src/window_initiator.rs +++ b/crates/eww/src/window_initiator.rs @@ -28,19 +28,25 @@ pub struct WindowInitiator { impl WindowInitiator { pub fn new(window_def: &WindowDefinition, args: &WindowArguments) -> Result { let vars = args.get_local_window_variables(window_def)?; - let geometry = window_def.geometry.map(|x| x.override_if_given(args.anchor, args.pos, args.size)); - let monitor = if args.monitor.is_none() { window_def.get_monitor(&vars)? } else { args.monitor.clone() }; + let backend_options = window_def.backend_options.eval(&vars)?; + let geometry = match &window_def.geometry { + Some(geo) => Some(geo.eval(&vars)?.override_if_given(args.anchor, args.pos, args.size)), + None => None, + }; + let monitor = if args.monitor.is_none() { window_def.eval_monitor(&vars)? } else { args.monitor.clone() }; + let resizable = window_def.eval_resizable(&vars)?; + let stacking = window_def.eval_stacking(&vars)?; Ok(WindowInitiator { - backend_options: window_def.backend_options.clone(), + backend_options, geometry, id: args.id.clone(), local_variables: vars, monitor, name: window_def.name.clone(), - resizable: window_def.resizable, - stacking: window_def.stacking, + resizable, + stacking, }) } diff --git a/crates/yuck/src/config/backend_window_options.rs b/crates/yuck/src/config/backend_window_options.rs index d237a200..1d9a9851 100644 --- a/crates/yuck/src/config/backend_window_options.rs +++ b/crates/yuck/src/config/backend_window_options.rs @@ -1,45 +1,71 @@ -use std::str::FromStr; +use std::{str::FromStr, collections::HashMap}; use anyhow::Result; +use simplexpr::{SimplExpr, dynval::{DynVal, FromDynVal}, eval::EvalError}; use crate::{ enum_parse, error::DiagResult, parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent}, - value::NumWithUnit, + value::{coords, NumWithUnit}, }; -use eww_shared_util::Span; +use eww_shared_util::{Span, VarName}; use super::{attributes::Attributes, window_definition::EnumParseError}; use crate::error::{DiagError, DiagResultExt}; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + EnumParseError(#[from] EnumParseError), + #[error(transparent)] + CoordsError(#[from] coords::Error), + #[error(transparent)] + EvalError(#[from] EvalError), +} + /// Backend-specific options of a window that are backend -#[derive(Debug, Clone, serde::Serialize, PartialEq)] -pub struct BackendWindowOptions { - pub x11: X11BackendWindowOptions, - pub wayland: WlBackendWindowOptions, +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +pub struct BackendWindowOptionsDef { + pub wayland: WlBackendWindowOptionsDef, + pub x11: X11BackendWindowOptionsDef, } -impl BackendWindowOptions { +impl BackendWindowOptionsDef { + pub fn eval(&self, local_variables: &HashMap) -> Result { + Ok(BackendWindowOptions { + wayland: self.wayland.eval(local_variables)?, + x11: self.x11.eval(local_variables)?, + }) + } + pub fn from_attrs(attrs: &mut Attributes) -> DiagResult { let struts = attrs.ast_optional("reserve")?; - let window_type = attrs.primitive_optional("windowtype")?; - let x11 = X11BackendWindowOptions { - wm_ignore: attrs.primitive_optional("wm-ignore")?.unwrap_or(window_type.is_none() && struts.is_none()), - window_type: window_type.unwrap_or_default(), - sticky: attrs.primitive_optional("sticky")?.unwrap_or(true), - struts: struts.unwrap_or_default(), + let window_type = attrs.ast_optional("windowtype")?; + let x11 = X11BackendWindowOptionsDef { + sticky: attrs.ast_optional("sticky")?, + struts, + window_type, + wm_ignore: attrs.ast_optional("wm-ignore")?, }; - let wayland = WlBackendWindowOptions { - exclusive: attrs.primitive_optional("exclusive")?.unwrap_or(false), - focusable: attrs.primitive_optional("focusable")?.unwrap_or(false), - namespace: attrs.primitive_optional("namespace")?, + let wayland = WlBackendWindowOptionsDef { + exclusive: attrs.ast_optional("exclusive")?, + focusable: attrs.ast_optional("focusable")?, + namespace: attrs.ast_optional("namespace")?, }; - Ok(Self { x11, wayland }) + + Ok(Self { wayland, x11 }) } } +/// Backend-specific options of a window that are backend +#[derive(Debug, Clone, serde::Serialize, PartialEq)] +pub struct BackendWindowOptions { + pub x11: X11BackendWindowOptions, + pub wayland: WlBackendWindowOptions, +} + #[derive(Debug, Clone, PartialEq, serde::Serialize)] pub struct X11BackendWindowOptions { pub wm_ignore: bool, @@ -48,6 +74,31 @@ pub struct X11BackendWindowOptions { pub struts: X11StrutDefinition, } +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +pub struct X11BackendWindowOptionsDef { + pub sticky: Option, + pub struts: Option, + pub window_type: Option, + pub wm_ignore: Option, +} + +impl X11BackendWindowOptionsDef { + fn eval(&self, local_variables: &HashMap) -> Result { + Ok(X11BackendWindowOptions { + sticky: eval_opt_expr_as_bool(&self.sticky, true, local_variables)?, + struts: match &self.struts { + Some(expr) => expr.eval(local_variables)?, + None => X11StrutDefinition::default(), + }, + window_type: match &self.window_type { + Some(expr) => X11WindowType::from_dynval(&expr.eval(local_variables)?)?, + None => X11WindowType::default(), + }, + wm_ignore: eval_opt_expr_as_bool(&self.wm_ignore, self.window_type.is_none() && self.struts.is_none(), local_variables)?, + }) + } +} + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] pub struct WlBackendWindowOptions { pub exclusive: bool, @@ -55,6 +106,33 @@ pub struct WlBackendWindowOptions { pub namespace: Option, } +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +pub struct WlBackendWindowOptionsDef { + pub exclusive: Option, + pub focusable: Option, + pub namespace: Option, +} + +impl WlBackendWindowOptionsDef { + fn eval(&self, local_variables: &HashMap) -> Result { + Ok(WlBackendWindowOptions { + exclusive: eval_opt_expr_as_bool(&self.exclusive, false, local_variables)?, + focusable: eval_opt_expr_as_bool(&self.focusable, false, local_variables)?, + namespace: match &self.namespace { + Some(expr) => Some(expr.eval(local_variables)?.as_string()?), + None => None, + } + }) + } +} + +fn eval_opt_expr_as_bool(opt_expr: &Option, default: bool, local_variables: &HashMap) -> Result { + Ok(match opt_expr { + Some(expr) => expr.eval(local_variables)?.as_bool()?, + None => default, + }) +} + /// Window type of an x11 window #[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)] pub enum X11WindowType { @@ -105,18 +183,39 @@ impl std::str::FromStr for Side { } } -#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize)] -pub struct X11StrutDefinition { - pub side: Side, - pub dist: NumWithUnit, +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +pub struct X11StrutDefinitionExpr { + pub side: Option, + pub dist: SimplExpr, +} + +impl X11StrutDefinitionExpr { + fn eval(&self, local_variables: &HashMap) -> Result { + Ok(X11StrutDefinition { + side: match &self.side { + Some(expr) => Side::from_dynval(&expr.eval(local_variables)?)?, + None => Side::default(), + }, + dist: NumWithUnit::from_dynval(&self.dist.eval(local_variables)?)?, + }) + } } -impl FromAstElementContent for X11StrutDefinition { +impl FromAstElementContent for X11StrutDefinitionExpr { const ELEMENT_NAME: &'static str = "struts"; fn from_tail>(_span: Span, mut iter: AstIterator) -> DiagResult { let mut attrs = iter.expect_key_values()?; iter.expect_done().map_err(DiagError::from).note("Check if you are missing a colon in front of a key")?; - Ok(X11StrutDefinition { side: attrs.primitive_required("side")?, dist: attrs.primitive_required("distance")? }) + Ok(X11StrutDefinitionExpr { + side: attrs.ast_optional("side")?, + dist: attrs.ast_required("distance")? + }) } } + +#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize)] +pub struct X11StrutDefinition { + pub side: Side, + pub dist: NumWithUnit, +} diff --git a/crates/yuck/src/config/window_definition.rs b/crates/yuck/src/config/window_definition.rs index 7347f9cf..eaa9b589 100644 --- a/crates/yuck/src/config/window_definition.rs +++ b/crates/yuck/src/config/window_definition.rs @@ -10,33 +10,55 @@ use crate::{ }, }; use eww_shared_util::{Span, VarName}; -use simplexpr::{dynval::DynVal, eval::EvalError, SimplExpr}; +use simplexpr::{dynval::{DynVal, FromDynVal}, eval::EvalError, SimplExpr}; use super::{ - attributes::AttrSpec, backend_window_options::BackendWindowOptions, widget_use::WidgetUse, window_geometry::WindowGeometry, + attributes::AttrSpec, backend_window_options::BackendWindowOptionsDef, widget_use::WidgetUse, window_geometry::WindowGeometryDef, }; -#[derive(Debug, Clone, serde::Serialize, PartialEq)] +#[derive(Debug, thiserror::Error)] +pub enum WindowStackingConversionError { + #[error(transparent)] + EvalError(#[from] EvalError), + #[error(transparent)] + EnumParseError(#[from] EnumParseError), +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub struct WindowDefinition { pub name: String, pub expected_args: Vec, pub args_span: Span, - pub geometry: Option, - pub stacking: WindowStacking, + pub geometry: Option, + pub stacking: Option, pub monitor: Option, pub widget: WidgetUse, - pub resizable: bool, - pub backend_options: BackendWindowOptions, + pub resizable: Option, + pub backend_options: BackendWindowOptionsDef, } impl WindowDefinition { - pub fn get_monitor(&self, local_variables: &HashMap) -> Result, EvalError> { - match &self.monitor { - Some(monitor_expr) => match monitor_expr.eval(local_variables) { - Ok(val) => Ok(Some(MonitorIdentifier::from_dynval(&val)?)), - Err(err) => Err(err), - }, - None => Ok(None), + pub fn eval_monitor(&self, local_variables: &HashMap) -> Result, EvalError> { + Ok(match &self.monitor { + Some(monitor_expr) => Some(MonitorIdentifier::from_dynval(&monitor_expr.eval(local_variables)?)?), + None => None, + }) + } + + pub fn eval_resizable(&self, local_variables: &HashMap) -> Result { + Ok(match &self.resizable { + Some(expr) => expr.eval(local_variables)?.as_bool()?, + None => true, + }) + } + + pub fn eval_stacking(&self, local_variables: &HashMap) -> Result { + match &self.stacking { + Some(stacking_expr) => match stacking_expr.eval(local_variables) { + Ok(val) => Ok(WindowStacking::from_dynval(&val)?), + Err(err) => Err(WindowStackingConversionError::EvalError(err)), + } + None => Ok(WindowStacking::Foreground), } } } @@ -50,10 +72,10 @@ impl FromAstElementContent for WindowDefinition { let expected_args = expected_args.into_iter().map(AttrSpec::from_ast).collect::>()?; let mut attrs = iter.expect_key_values()?; let monitor = attrs.ast_optional("monitor")?; - let resizable = attrs.primitive_optional("resizable")?.unwrap_or(true); - let stacking = attrs.primitive_optional("stacking")?.unwrap_or(WindowStacking::Foreground); + let resizable = attrs.ast_optional("resizable")?; + let stacking = attrs.ast_optional("stacking")?; let geometry = attrs.ast_optional("geometry")?; - let backend_options = BackendWindowOptions::from_attrs(&mut attrs)?; + let backend_options = BackendWindowOptionsDef::from_attrs(&mut attrs)?; let widget = iter.expect_any().map_err(DiagError::from).and_then(WidgetUse::from_ast)?; iter.expect_done()?; Ok(Self { name, expected_args, args_span, monitor, resizable, widget, stacking, geometry, backend_options }) diff --git a/crates/yuck/src/config/window_geometry.rs b/crates/yuck/src/config/window_geometry.rs index a96c256a..5f8d7925 100644 --- a/crates/yuck/src/config/window_geometry.rs +++ b/crates/yuck/src/config/window_geometry.rs @@ -1,14 +1,17 @@ +use std::collections::HashMap; + use crate::{ enum_parse, error::DiagResult, format_diagnostic::ToDiagnostic, parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent}, - value::Coords, + value::{Coords, coords, NumWithUnit}, }; use super::window_definition::EnumParseError; -use eww_shared_util::Span; +use eww_shared_util::{Span, VarName}; use serde::{Deserialize, Serialize}; +use simplexpr::{SimplExpr, dynval::{DynVal, FromDynVal}, eval::EvalError}; #[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, strum::Display)] pub enum AnchorAlignment { @@ -102,34 +105,87 @@ impl std::str::FromStr for AnchorPoint { } } -#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize)] -pub struct WindowGeometry { - pub anchor_point: AnchorPoint, - pub offset: Coords, - pub size: Coords, +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct CoordsDef { + pub x: Option, + pub y: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + AnchorPointParseError(#[from] AnchorPointParseError), + #[error(transparent)] + CoordsError(#[from] coords::Error), + #[error(transparent)] + EvalError(#[from] EvalError), +} + +impl CoordsDef { + pub fn eval(&self, local_variables: &HashMap) -> Result { + Ok(Coords { + x: convert_to_num_with_unit(&self.x, local_variables)?, + y: convert_to_num_with_unit(&self.y, local_variables)?, + }) + } +} + +fn convert_to_num_with_unit(opt_expr: &Option, local_variables: &HashMap) -> Result { + Ok(match opt_expr { + Some(expr) => NumWithUnit::from_dynval(&expr.eval(local_variables)?)?, + None => NumWithUnit::default(), + }) } -impl FromAstElementContent for WindowGeometry { +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct WindowGeometryDef { + pub anchor_point: Option, + pub offset: CoordsDef, + pub size: CoordsDef, +} + +impl FromAstElementContent for WindowGeometryDef { const ELEMENT_NAME: &'static str = "geometry"; fn from_tail>(_span: Span, mut iter: AstIterator) -> DiagResult { let mut attrs = iter.expect_key_values()?; iter.expect_done() .map_err(|e| e.to_diagnostic().with_notes(vec!["Check if you are missing a colon in front of a key".to_string()]))?; - Ok(WindowGeometry { - anchor_point: attrs.primitive_optional("anchor")?.unwrap_or_default(), - size: Coords { - x: attrs.primitive_optional("width")?.unwrap_or_default(), - y: attrs.primitive_optional("height")?.unwrap_or_default(), + + Ok(WindowGeometryDef { + anchor_point: attrs.ast_optional("anchor")?, + size: CoordsDef { + x: attrs.ast_optional("width")?, + y: attrs.ast_optional("height")?, }, - offset: Coords { - x: attrs.primitive_optional("x")?.unwrap_or_default(), - y: attrs.primitive_optional("y")?.unwrap_or_default(), + offset: CoordsDef { + x: attrs.ast_optional("x")?, + y: attrs.ast_optional("y")?, }, }) } } +impl WindowGeometryDef { + pub fn eval(&self, local_variables: &HashMap) -> Result { + Ok(WindowGeometry { + anchor_point: match &self.anchor_point { + Some(expr) => AnchorPoint::from_dynval(&expr.eval(local_variables)?)?, + None => AnchorPoint::default(), + }, + size: self.size.eval(local_variables)?, + offset: self.offset.eval(local_variables)?, + }) + } +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize)] +pub struct WindowGeometry { + pub anchor_point: AnchorPoint, + pub offset: Coords, + pub size: Coords, +} + impl WindowGeometry { pub fn override_if_given(&self, anchor_point: Option, offset: Option, size: Option) -> Self { WindowGeometry { From fc36ac621f46b60c8eeb17f6b10fcc40ca86fd56 Mon Sep 17 00:00:00 2001 From: Wilf Silver Date: Sun, 2 Jul 2023 15:37:10 +0100 Subject: [PATCH 3/9] Take gtk scaling into account when setting struts --- crates/eww/src/app.rs | 14 ++++++++------ crates/eww/src/display_backend.rs | 21 +++++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index ee56b603..5782a317 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -14,6 +14,7 @@ use crate::{ use anyhow::anyhow; use codespan_reporting::files::Files; use eww_shared_util::{Span, VarName}; +use gdk::Monitor; use glib::ObjectExt; use itertools::Itertools; use once_cell::sync::Lazy; @@ -406,8 +407,8 @@ impl App { root_widget.style_context().add_class(window_name); - let monitor_geometry = get_monitor_geometry(initiator.monitor.clone())?; - let mut eww_window = initialize_window::(&initiator, monitor_geometry, root_widget, window_scope)?; + let monitor = get_monitor(initiator.monitor.clone())?; + let mut eww_window = initialize_window::(&initiator, monitor, root_widget, window_scope)?; eww_window.gtk_window.style_context().add_class(window_name); // initialize script var handlers for variables. As starting a scriptvar with the script_var_handler is idempodent, @@ -528,10 +529,11 @@ impl App { fn initialize_window( window_init: &WindowInitiator, - monitor_geometry: gdk::Rectangle, + monitor: Monitor, root_widget: gtk::Widget, window_scope: ScopeIndex, ) -> Result { + let monitor_geometry = monitor.geometry(); let window = B::initialize_window(window_init, monitor_geometry) .with_context(|| format!("monitor {} is unavailable", window_init.monitor.clone().unwrap()))?; @@ -567,7 +569,7 @@ fn initialize_window( }); } } - display_backend::set_xprops(&window, monitor_geometry, window_init)?; + display_backend::set_xprops(&window, monitor, window_init)?; } window.show_all(); @@ -608,7 +610,7 @@ fn on_screen_changed(window: >k::Window, _old_screen: Option<&gdk::Screen>) { } /// Get the monitor geometry of a given monitor, or the default if none is given -fn get_monitor_geometry(identifier: Option) -> Result { +fn get_monitor(identifier: Option) -> Result { let display = gdk::Display::default().expect("could not get default display"); let monitor = match identifier { Some(ident) => { @@ -628,7 +630,7 @@ fn get_monitor_geometry(identifier: Option) -> Result Result<()> { + pub fn set_xprops(window: >k::Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> { let backend = X11BackendConnection::new()?; backend.set_xprops_for(window, monitor, window_init)?; Ok(()) @@ -173,17 +174,21 @@ mod platform_x11 { fn set_xprops_for( &self, window: >k::Window, - monitor_rect: gdk::Rectangle, + monitor: Monitor, window_init: &WindowInitiator, ) -> Result<()> { + let monitor_rect = monitor.geometry(); + let scale_factor = monitor.scale_factor() as u32; let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?; let win_id = gdk_window.downcast_ref::().context("Failed to get x11 window for gtk window")?.xid() as u32; let strut_def = window_init.backend_options.x11.struts; 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 mon_x = scale_factor * monitor_rect.x() as u32; + let mon_y = scale_factor * monitor_rect.y() as u32; + let mon_end_x = scale_factor * (monitor_rect.x() + monitor_rect.width()) as u32 - 1u32; + let mon_end_y = scale_factor * (monitor_rect.y() + monitor_rect.height()) as u32 - 1u32; let dist = match strut_def.side { Side::Left | Side::Right => strut_def.dist.pixels_relative_to(monitor_rect.width()) as u32, @@ -195,10 +200,10 @@ mod platform_x11 { // 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], + Side::Left => vec![dist + mon_x, 0, 0, 0, mon_x, 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, mon_x, mon_end_y, 0, 0, 0, 0], + Side::Top => vec![0, 0, dist + mon_y as u32, 0, 0, 0, 0, 0, mon_x, 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, mon_x as u32, mon_end_x], // This should never happen but if it does the window will be anchored on the // right of the screen }.iter().flat_map(|x| x.to_le_bytes().to_vec()).collect(); From c5d0859d2d5fd9388f23202b44f084fd57892c52 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Wed, 16 Aug 2023 14:21:46 +0200 Subject: [PATCH 4/9] Cleanup --- crates/eww/src/app.rs | 66 +++++++-------- crates/eww/src/display_backend.rs | 11 +-- crates/eww/src/main.rs | 1 + crates/eww/src/opts.rs | 19 +++-- crates/eww/src/window_arguments.rs | 83 +++++++------------ crates/eww/src/window_initiator.rs | 22 ++--- crates/yuck/src/config/attributes.rs | 1 + .../yuck/src/config/backend_window_options.rs | 46 ++++++---- crates/yuck/src/config/monitor.rs | 20 +++-- crates/yuck/src/config/widget_definition.rs | 20 +---- crates/yuck/src/config/window_definition.rs | 19 ++++- crates/yuck/src/config/window_geometry.rs | 25 +++--- 12 files changed, 155 insertions(+), 178 deletions(-) diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index 5782a317..aef434c9 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -18,7 +18,7 @@ use gdk::Monitor; use glib::ObjectExt; use itertools::Itertools; use once_cell::sync::Lazy; -use simplexpr::dynval::DynVal; +use simplexpr::{dynval::DynVal, SimplExpr}; use std::{ cell::RefCell, collections::{HashMap, HashSet}, @@ -85,6 +85,9 @@ pub enum DaemonCommand { /// An opened window. #[derive(Debug)] pub struct EwwWindow { + /// Every window has an id, uniquely identifying it. + /// If no specific ID was specified whilst starting the window, + /// this will be the same as the window name. pub instance_id: String, pub name: String, pub scope_index: ScopeIndex, @@ -135,7 +138,7 @@ impl std::fmt::Debug for App { .field("eww_config", &self.eww_config) .field("open_windows", &self.open_windows) .field("failed_windows", &self.failed_windows) - .field("window_argumentss", &self.window_argumentss) + .field("window_arguments", &self.window_argumentss) .field("paths", &self.paths) .finish() } @@ -195,15 +198,10 @@ impl App { self.close_window(id) } else { log::debug!("Config: {}, id: {}", config_name, id); - let window_args: Vec<(VarName, DynVal)> = args + let window_args = args .iter() - .filter_map(|(win_id, n, v)| { - if win_id.is_empty() || win_id == id { - Some((n.clone(), v.clone())) - } else { - None - } - }) + .filter(|(win_id, ..)| win_id.is_empty() || win_id == id) + .map(|(_, n, v)| (n.clone(), v.clone())) .collect(); self.open_window(&WindowArguments::new_from_args( id.to_string(), @@ -227,23 +225,23 @@ impl App { sender, args, } => { - let id = instance_id.unwrap_or_else(|| window_name.clone()); + let instance_id = instance_id.unwrap_or_else(|| window_name.clone()); - let is_open = self.open_windows.contains_key(&id); + let is_open = self.open_windows.contains_key(&instance_id); let result = if should_toggle && is_open { - self.close_window(&id) + self.close_window(&instance_id) } else { - self.open_window(&WindowArguments::new( - id, + self.open_window(&WindowArguments { + instance_id, window_name, pos, size, monitor, anchor, duration, - args.unwrap_or_default(), - )) + args: args.unwrap_or_default().into_iter().collect(), + }) }; sender.respond_with_result(result)?; @@ -350,7 +348,7 @@ impl App { let eww_window = self .open_windows .remove(instance_id) - .with_context(|| format!("Tried to close window named '{}', but no such window was open", instance_id))?; + .with_context(|| format!("Tried to close window with id '{instance_id}', but no such window was open"))?; let scope_index = eww_window.scope_index; eww_window.close(); @@ -369,9 +367,9 @@ impl App { } fn open_window(&mut self, window_args: &WindowArguments) -> Result<()> { - let instance_id = &window_args.id; + let instance_id = &window_args.instance_id; self.failed_windows.remove(instance_id); - log::info!("Opening window {} as '{}'", window_args.config_name, instance_id); + log::info!("Opening window {} as '{}'", window_args.window_name, instance_id); // if an instance of this is already running, close it if self.open_windows.contains_key(instance_id) { @@ -381,7 +379,7 @@ impl App { self.window_argumentss.insert(instance_id.to_string(), window_args.clone()); let open_result: Result<_> = try { - let window_name: &str = &window_args.config_name; + let window_name: &str = &window_args.window_name; let window_def = self.eww_config.get_window(window_name)?.clone(); assert_eq!(window_def.name, window_name, "window definition name did not equal the called window"); @@ -390,11 +388,13 @@ impl App { let root_index = self.scope_graph.borrow().root_index; + let scoped_vars_literal = initiator.get_scoped_vars().into_iter().map(|(k, v)| (k, SimplExpr::Literal(v))).collect(); + let window_scope = self.scope_graph.borrow_mut().register_new_scope( instance_id.to_string(), Some(root_index), root_index, - initiator.get_scoped_vars(), + scoped_vars_literal, )?; let root_widget = crate::widgets::build_widget::build_gtk_widget( @@ -407,7 +407,7 @@ impl App { root_widget.style_context().add_class(window_name); - let monitor = get_monitor(initiator.monitor.clone())?; + let monitor = get_gdk_monitor(initiator.monitor.clone())?; let mut eww_window = initialize_window::(&initiator, monitor, root_widget, window_scope)?; eww_window.gtk_window.style_context().add_class(window_name); @@ -487,19 +487,13 @@ impl App { self.eww_config = config; self.scope_graph.borrow_mut().clear(self.eww_config.generate_initial_state()?); - let instances: Vec = + let open_window_ids: Vec = self.open_windows.keys().cloned().chain(self.failed_windows.iter().cloned()).dedup().collect(); - let initiators = self.window_argumentss.clone(); - for instance_id in &instances { - let window_arguments; - match initiators.get(instance_id) { - Some(x) => window_arguments = x, - None => { - return Err(anyhow!("Cannot reopen window, initial parameters were not saved correctly for {}", instance_id)) - } - }; - - self.open_window(window_arguments)?; + for instance_id in &open_window_ids { + let window_arguments = self.window_argumentss.get(instance_id).with_context(|| { + format!("Cannot reopen window, initial parameters were not saved correctly for {instance_id}") + })?; + self.open_window(&window_arguments.clone())?; } Ok(()) } @@ -610,7 +604,7 @@ fn on_screen_changed(window: >k::Window, _old_screen: Option<&gdk::Screen>) { } /// Get the monitor geometry of a given monitor, or the default if none is given -fn get_monitor(identifier: Option) -> Result { +fn get_gdk_monitor(identifier: Option) -> Result { let display = gdk::Display::default().expect("could not get default display"); let monitor = match identifier { Some(ident) => { diff --git a/crates/eww/src/display_backend.rs b/crates/eww/src/display_backend.rs index 018c39ed..e653123e 100644 --- a/crates/eww/src/display_backend.rs +++ b/crates/eww/src/display_backend.rs @@ -171,12 +171,7 @@ mod platform_x11 { Ok(X11BackendConnection { conn, root_window: screen.root, atoms }) } - fn set_xprops_for( - &self, - window: >k::Window, - monitor: Monitor, - window_init: &WindowInitiator, - ) -> Result<()> { + fn set_xprops_for(&self, window: >k::Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> { let monitor_rect = monitor.geometry(); let scale_factor = monitor.scale_factor() as u32; let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?; @@ -191,8 +186,8 @@ mod platform_x11 { let mon_end_y = scale_factor * (monitor_rect.y() + monitor_rect.height()) as u32 - 1u32; let dist = match strut_def.side { - Side::Left | Side::Right => strut_def.dist.pixels_relative_to(monitor_rect.width()) as u32, - Side::Top | Side::Bottom => strut_def.dist.pixels_relative_to(monitor_rect.height()) as u32, + Side::Left | Side::Right => strut_def.distance.pixels_relative_to(monitor_rect.width()) as u32, + Side::Top | Side::Bottom => strut_def.distance.pixels_relative_to(monitor_rect.height()) as u32, }; // don't question it,..... diff --git a/crates/eww/src/main.rs b/crates/eww/src/main.rs index 1e00f5e3..a2fc5282 100644 --- a/crates/eww/src/main.rs +++ b/crates/eww/src/main.rs @@ -4,6 +4,7 @@ #![feature(slice_concat_trait)] #![feature(try_blocks)] #![feature(hash_extract_if)] +#![feature(let_chains)] #![allow(rustdoc::private_intra_doc_links)] extern crate gtk; diff --git a/crates/eww/src/opts.rs b/crates/eww/src/opts.rs index 35777a04..df6f159c 100644 --- a/crates/eww/src/opts.rs +++ b/crates/eww/src/opts.rs @@ -129,19 +129,22 @@ pub enum ActionWithServer { #[arg(long, value_parser=parse_duration)] duration: Option, - #[arg(long, value_parser = parse_var_update_arg)] - arg: Option>, + /// Define a variable for the window, i.e.: `--arg "var_name=value"` + #[arg(long = "arg", value_parser = parse_var_update_arg)] + args: Option>, }, /// Open multiple windows at once. /// NOTE: This will in the future be part of eww open, and will then be removed. #[command(name = "open-many")] OpenMany { + /// List the windows to open, optionally including their id, i.e.: `--window "window_name:window_id"` #[arg(value_parser = parse_window_config_and_id)] windows: Vec<(String, String)>, - #[arg(long, value_parser = parse_window_id_args)] - arg: Vec<(String, VarName, DynVal)>, + /// Define a variable for the window, i.e.: `--arg "window_id:var_name=value"` + #[arg(long = "arg", value_parser = parse_window_id_args)] + args: Vec<(String, VarName, DynVal)>, /// If a window is already open, close it instead #[arg(long = "toggle")] @@ -206,12 +209,15 @@ impl From for Opt { } } +/// Parse a window-name:window-id pair of the form `name:id` or `name` into a tuple of `(name, id)`. fn parse_window_config_and_id(s: &str) -> Result<(String, String)> { let (name, id) = s.split_once(':').unwrap_or((s, s)); Ok((name.to_string(), id.to_string())) } +/// Parse a window-id specific variable value declaration with the syntax `window-id:variable_name="new_value"` +/// into a tuple of `(id, variable_name, new_value)`. fn parse_window_id_args(s: &str) -> Result<(String, VarName, DynVal)> { // Parse the = first so we know if an id has not been given let (name, value) = parse_var_update_arg(s)?; @@ -221,6 +227,7 @@ fn parse_window_id_args(s: &str) -> Result<(String, VarName, DynVal)> { Ok((id.to_string(), var_name.into(), value)) } +/// Split the input string at `=`, parsing the value into a [`DynVal`]. fn parse_var_update_arg(s: &str) -> Result<(VarName, DynVal)> { let (name, value) = s .split_once('=') @@ -245,10 +252,10 @@ impl ActionWithServer { let _ = send.send(DaemonResponse::Success("pong".to_owned())); return (app::DaemonCommand::NoOp, Some(recv)); } - ActionWithServer::OpenMany { windows, arg: args, should_toggle } => { + ActionWithServer::OpenMany { windows, args, should_toggle } => { return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, args, should_toggle, sender }); } - ActionWithServer::OpenWindow { window_name, id, pos, size, screen, anchor, should_toggle, duration, arg: args } => { + ActionWithServer::OpenWindow { window_name, id, pos, size, screen, anchor, should_toggle, duration, args } => { return with_response_channel(|sender| app::DaemonCommand::OpenWindow { window_name, instance_id: id, diff --git a/crates/eww/src/window_arguments.rs b/crates/eww/src/window_arguments.rs index 7a7e03a0..10db92f0 100644 --- a/crates/eww/src/window_arguments.rs +++ b/crates/eww/src/window_arguments.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{bail, Context, Result}; use eww_shared_util::VarName; use simplexpr::dynval::DynVal; use std::{ @@ -10,56 +10,37 @@ use yuck::{ value::Coords, }; -fn extract_value_from_args(name: &str, args: &mut Vec<(VarName, DynVal)>) -> Option { - let var_name = name.to_string(); - let pos = args.iter().position(|(n, _)| n.0 == var_name); - - if let Some(unwrapped_pos) = pos { - let (_, val) = args.remove(unwrapped_pos); - Some(val) - } else { - None - } +pub fn extract_value_from_args(name: &str, args: &mut HashMap) -> Option { + args.remove(&VarName(name.to_string())) } -fn parse_value_from_args(name: &str, args: &mut Vec<(VarName, DynVal)>) -> Result, T::Err> { +fn parse_value_from_args(name: &str, args: &mut HashMap) -> Result, T::Err> { extract_value_from_args(name, args).map(|x| FromStr::from_str(&x.as_string().unwrap())).transpose() } /// This stores the arguments given in the command line to create a window /// While creating a window, we combine this with information from the -/// WindowDefinition to create a WindowInitiator, which stores all the +/// [`WindowDefinition`] to create a [WindowInitiator](`crate::window_initiator::WindowInitiator`), which stores all the /// information required to start a window #[derive(Debug, Clone)] pub struct WindowArguments { + /// Name of the window as defined in the eww config + pub window_name: String, + /// Instance ID of the window + pub instance_id: String, pub anchor: Option, - pub args: Vec<(VarName, DynVal)>, - pub config_name: String, + pub args: HashMap, pub duration: Option, - pub id: String, pub monitor: Option, pub pos: Option, pub size: Option, } impl WindowArguments { - pub fn new( - id: String, - config_name: String, - pos: Option, - size: Option, - monitor: Option, - anchor: Option, - duration: Option, - args: Vec<(VarName, DynVal)>, - ) -> Self { - WindowArguments { id, config_name, pos, size, monitor, anchor, duration, args } - } - - pub fn new_from_args(id: String, config_name: String, mut args: Vec<(VarName, DynVal)>) -> Result { + pub fn new_from_args(id: String, config_name: String, mut args: HashMap) -> Result { let initiator = WindowArguments { - config_name, - id, + window_name: config_name, + instance_id: id, pos: parse_value_from_args::("pos", &mut args)?, size: parse_value_from_args::("size", &mut args)?, monitor: parse_value_from_args::("screen", &mut args)?, @@ -74,16 +55,19 @@ impl WindowArguments { Ok(initiator) } + /// Return a hashmap of all arguments the window was passed and expected, returning + /// an error in case required arguments are missing or unexpected arguments are passed. pub fn get_local_window_variables(&self, window_def: &WindowDefinition) -> Result> { let expected_args: HashSet<&String> = window_def.expected_args.iter().map(|x| &x.name.0).collect(); let mut local_variables: HashMap = HashMap::new(); - // Inserts these first so they can be overridden + // Ensure that the arguments passed to the window that are already interpreted by eww (id, screen) + // are set to the correct values if expected_args.contains(&"id".to_string()) { - local_variables.insert(VarName::from("id"), DynVal::from(self.id.clone())); + local_variables.insert(VarName::from("id"), DynVal::from(self.instance_id.clone())); } if self.monitor.is_some() && expected_args.contains(&"screen".to_string()) { - let mon_dyn = self.monitor.clone().unwrap().to_dynval(); + let mon_dyn = DynVal::from(&self.monitor.clone().unwrap()); local_variables.insert(VarName::from("screen"), mon_dyn); } @@ -91,30 +75,19 @@ impl WindowArguments { for attr in &window_def.expected_args { let name = VarName::from(attr.name.clone()); - - // This is here to get around the map_entry warning - let mut inserted = false; - local_variables.entry(name).or_insert_with(|| { - inserted = true; - DynVal::from_string(String::new()) - }); - - if inserted && !attr.optional { - return Err(anyhow!("Error, {} was required when creating {} but was not given", attr.name, self.config_name)); + if !local_variables.contains_key(&name) && !attr.optional { + bail!("Error, missing argument '{}' when creating window with id '{}'", attr.name, self.instance_id); } } if local_variables.len() != window_def.expected_args.len() { - let unexpected_vars: Vec = local_variables - .iter() - .filter_map(|(n, _)| if !expected_args.contains(&n.0) { Some(n.clone()) } else { None }) - .collect(); - return Err(anyhow!( - "'{}' {} unexpectedly defined when creating window {}", - unexpected_vars.join(","), - if unexpected_vars.len() == 1 { "was" } else { "were" }, - self.config_name - )); + let unexpected_vars: Vec<_> = + local_variables.iter().map(|(name, _)| name.clone()).filter(|n| !expected_args.contains(&n.0)).collect(); + bail!( + "variables {} unexpectedly defined when creating window with id '{}'", + unexpected_vars.join(", "), + self.instance_id, + ); } Ok(local_variables) diff --git a/crates/eww/src/window_initiator.rs b/crates/eww/src/window_initiator.rs index f4af1cb1..cb2cda00 100644 --- a/crates/eww/src/window_initiator.rs +++ b/crates/eww/src/window_initiator.rs @@ -1,6 +1,6 @@ use anyhow::Result; use eww_shared_util::{AttrName, VarName}; -use simplexpr::{dynval::DynVal, SimplExpr}; +use simplexpr::dynval::DynVal; use std::collections::HashMap; use yuck::config::{ backend_window_options::BackendWindowOptions, @@ -12,7 +12,7 @@ use yuck::config::{ use crate::window_arguments::WindowArguments; /// This stores all the information required to create a window and is created -/// via combining information from the WindowDefinition and the WindowInitiator +/// via combining information from the [`WindowDefinition`] and the [`WindowInitiator`] #[derive(Debug, Clone)] pub struct WindowInitiator { pub backend_options: BackendWindowOptions, @@ -29,28 +29,24 @@ impl WindowInitiator { pub fn new(window_def: &WindowDefinition, args: &WindowArguments) -> Result { let vars = args.get_local_window_variables(window_def)?; - let backend_options = window_def.backend_options.eval(&vars)?; let geometry = match &window_def.geometry { Some(geo) => Some(geo.eval(&vars)?.override_if_given(args.anchor, args.pos, args.size)), None => None, }; let monitor = if args.monitor.is_none() { window_def.eval_monitor(&vars)? } else { args.monitor.clone() }; - let resizable = window_def.eval_resizable(&vars)?; - let stacking = window_def.eval_stacking(&vars)?; - Ok(WindowInitiator { - backend_options, + backend_options: window_def.backend_options.eval(&vars)?, geometry, - id: args.id.clone(), - local_variables: vars, + id: args.instance_id.clone(), monitor, name: window_def.name.clone(), - resizable, - stacking, + resizable: window_def.eval_resizable(&vars)?, + stacking: window_def.eval_stacking(&vars)?, + local_variables: vars, }) } - pub fn get_scoped_vars(&self) -> HashMap { - self.local_variables.iter().map(|(k, v)| (AttrName::from(k.clone()), SimplExpr::Literal(v.clone()))).collect() + pub fn get_scoped_vars(&self) -> HashMap { + self.local_variables.iter().map(|(k, v)| (AttrName::from(k.clone()), v.clone())).collect() } } diff --git a/crates/yuck/src/config/attributes.rs b/crates/yuck/src/config/attributes.rs index b91a562a..15639a54 100644 --- a/crates/yuck/src/config/attributes.rs +++ b/crates/yuck/src/config/attributes.rs @@ -110,6 +110,7 @@ impl Attributes { } } +/// Specification of an argument to a widget or window #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] pub struct AttrSpec { pub name: AttrName, diff --git a/crates/yuck/src/config/backend_window_options.rs b/crates/yuck/src/config/backend_window_options.rs index 1d9a9851..1314664a 100644 --- a/crates/yuck/src/config/backend_window_options.rs +++ b/crates/yuck/src/config/backend_window_options.rs @@ -1,7 +1,11 @@ -use std::{str::FromStr, collections::HashMap}; +use std::{collections::HashMap, str::FromStr}; use anyhow::Result; -use simplexpr::{SimplExpr, dynval::{DynVal, FromDynVal}, eval::EvalError}; +use simplexpr::{ + dynval::{DynVal, FromDynVal}, + eval::EvalError, + SimplExpr, +}; use crate::{ enum_parse, @@ -25,7 +29,8 @@ pub enum Error { EvalError(#[from] EvalError), } -/// Backend-specific options of a window that are backend +/// Backend-specific options of a window +/// Unevaluated form of [`BackendWindowOptions`] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub struct BackendWindowOptionsDef { pub wayland: WlBackendWindowOptionsDef, @@ -34,10 +39,7 @@ pub struct BackendWindowOptionsDef { impl BackendWindowOptionsDef { pub fn eval(&self, local_variables: &HashMap) -> Result { - Ok(BackendWindowOptions { - wayland: self.wayland.eval(local_variables)?, - x11: self.x11.eval(local_variables)?, - }) + Ok(BackendWindowOptions { wayland: self.wayland.eval(local_variables)?, x11: self.x11.eval(local_variables)? }) } pub fn from_attrs(attrs: &mut Attributes) -> DiagResult { @@ -55,7 +57,7 @@ impl BackendWindowOptionsDef { namespace: attrs.ast_optional("namespace")?, }; - Ok(Self { wayland, x11 }) + Ok(Self { wayland, x11 }) } } @@ -74,6 +76,7 @@ pub struct X11BackendWindowOptions { pub struts: X11StrutDefinition, } +/// Unevaluated form of [`X11BackendWindowOptions`] #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] pub struct X11BackendWindowOptionsDef { pub sticky: Option, @@ -94,7 +97,11 @@ impl X11BackendWindowOptionsDef { Some(expr) => X11WindowType::from_dynval(&expr.eval(local_variables)?)?, None => X11WindowType::default(), }, - wm_ignore: eval_opt_expr_as_bool(&self.wm_ignore, self.window_type.is_none() && self.struts.is_none(), local_variables)?, + wm_ignore: eval_opt_expr_as_bool( + &self.wm_ignore, + self.window_type.is_none() && self.struts.is_none(), + local_variables, + )?, }) } } @@ -106,6 +113,7 @@ pub struct WlBackendWindowOptions { pub namespace: Option, } +/// Unevaluated form of [`WlBackendWindowOptions`] #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] pub struct WlBackendWindowOptionsDef { pub exclusive: Option, @@ -121,12 +129,16 @@ impl WlBackendWindowOptionsDef { namespace: match &self.namespace { Some(expr) => Some(expr.eval(local_variables)?.as_string()?), None => None, - } + }, }) } } -fn eval_opt_expr_as_bool(opt_expr: &Option, default: bool, local_variables: &HashMap) -> Result { +fn eval_opt_expr_as_bool( + opt_expr: &Option, + default: bool, + local_variables: &HashMap, +) -> Result { Ok(match opt_expr { Some(expr) => expr.eval(local_variables)?.as_bool()?, None => default, @@ -183,10 +195,11 @@ impl std::str::FromStr for Side { } } +/// Unevaluated form of [`X11StrutDefinition`] #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] pub struct X11StrutDefinitionExpr { pub side: Option, - pub dist: SimplExpr, + pub distance: SimplExpr, } impl X11StrutDefinitionExpr { @@ -196,7 +209,7 @@ impl X11StrutDefinitionExpr { Some(expr) => Side::from_dynval(&expr.eval(local_variables)?)?, None => Side::default(), }, - dist: NumWithUnit::from_dynval(&self.dist.eval(local_variables)?)?, + distance: NumWithUnit::from_dynval(&self.distance.eval(local_variables)?)?, }) } } @@ -207,15 +220,12 @@ impl FromAstElementContent for X11StrutDefinitionExpr { fn from_tail>(_span: Span, mut iter: AstIterator) -> DiagResult { let mut attrs = iter.expect_key_values()?; iter.expect_done().map_err(DiagError::from).note("Check if you are missing a colon in front of a key")?; - Ok(X11StrutDefinitionExpr { - side: attrs.ast_optional("side")?, - dist: attrs.ast_required("distance")? - }) + Ok(X11StrutDefinitionExpr { side: attrs.ast_optional("side")?, distance: attrs.ast_required("distance")? }) } } #[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize)] pub struct X11StrutDefinition { pub side: Side, - pub dist: NumWithUnit, + pub distance: NumWithUnit, } diff --git a/crates/yuck/src/config/monitor.rs b/crates/yuck/src/config/monitor.rs index 9aee9cef..46d4ec2a 100644 --- a/crates/yuck/src/config/monitor.rs +++ b/crates/yuck/src/config/monitor.rs @@ -29,20 +29,22 @@ impl MonitorIdentifier { } } - pub fn to_dynval(&self) -> DynVal { - match self { - Self::List(l) => l.iter().map(|x| x.to_dynval()).collect::>().into(), - Self::Numeric(n) => DynVal::from(*n), - Self::Name(n) => DynVal::from(n.clone()), - Self::Primary => DynVal::from(""), - } - } - pub fn is_numeric(&self) -> bool { matches!(self, Self::Numeric(_)) } } +impl From<&MonitorIdentifier> for DynVal { + fn from(val: &MonitorIdentifier) -> Self { + match val { + MonitorIdentifier::List(l) => l.iter().map(|x| x.into()).collect::>().into(), + MonitorIdentifier::Numeric(n) => DynVal::from(*n), + MonitorIdentifier::Name(n) => DynVal::from(n.clone()), + MonitorIdentifier::Primary => DynVal::from(""), + } + } +} + impl fmt::Display for MonitorIdentifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/crates/yuck/src/config/widget_definition.rs b/crates/yuck/src/config/widget_definition.rs index 8a17233a..093db778 100644 --- a/crates/yuck/src/config/widget_definition.rs +++ b/crates/yuck/src/config/widget_definition.rs @@ -8,25 +8,9 @@ use crate::{ from_ast::{FromAst, FromAstElementContent}, }, }; -use eww_shared_util::{AttrName, Span, Spanned}; +use eww_shared_util::{Span, Spanned}; -use super::widget_use::WidgetUse; - -#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] -pub struct AttrSpec { - pub name: AttrName, - pub optional: bool, - pub span: Span, -} - -impl FromAst for AttrSpec { - fn from_ast(e: Ast) -> DiagResult { - let span = e.span(); - let symbol = e.as_symbol()?; - let (name, optional) = if let Some(name) = symbol.strip_prefix('?') { (name.to_string(), true) } else { (symbol, false) }; - Ok(Self { name: AttrName(name), optional, span }) - } -} +use super::{attributes::AttrSpec, widget_use::WidgetUse}; #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] pub struct WidgetDefinition { diff --git a/crates/yuck/src/config/window_definition.rs b/crates/yuck/src/config/window_definition.rs index eaa9b589..18bd581b 100644 --- a/crates/yuck/src/config/window_definition.rs +++ b/crates/yuck/src/config/window_definition.rs @@ -10,10 +10,15 @@ use crate::{ }, }; use eww_shared_util::{Span, VarName}; -use simplexpr::{dynval::{DynVal, FromDynVal}, eval::EvalError, SimplExpr}; +use simplexpr::{ + dynval::{DynVal, FromDynVal}, + eval::EvalError, + SimplExpr, +}; use super::{ - attributes::AttrSpec, backend_window_options::BackendWindowOptionsDef, widget_use::WidgetUse, window_geometry::WindowGeometryDef, + attributes::AttrSpec, backend_window_options::BackendWindowOptionsDef, widget_use::WidgetUse, + window_geometry::WindowGeometryDef, }; #[derive(Debug, thiserror::Error)] @@ -38,6 +43,7 @@ pub struct WindowDefinition { } impl WindowDefinition { + /// Evaluate the `monitor` field of the window definition pub fn eval_monitor(&self, local_variables: &HashMap) -> Result, EvalError> { Ok(match &self.monitor { Some(monitor_expr) => Some(MonitorIdentifier::from_dynval(&monitor_expr.eval(local_variables)?)?), @@ -45,6 +51,7 @@ impl WindowDefinition { }) } + /// Evaluate the `resizable` field of the window definition pub fn eval_resizable(&self, local_variables: &HashMap) -> Result { Ok(match &self.resizable { Some(expr) => expr.eval(local_variables)?.as_bool()?, @@ -52,12 +59,16 @@ impl WindowDefinition { }) } - pub fn eval_stacking(&self, local_variables: &HashMap) -> Result { + /// Evaluate the `stacking` field of the window definition + pub fn eval_stacking( + &self, + local_variables: &HashMap, + ) -> Result { match &self.stacking { Some(stacking_expr) => match stacking_expr.eval(local_variables) { Ok(val) => Ok(WindowStacking::from_dynval(&val)?), Err(err) => Err(WindowStackingConversionError::EvalError(err)), - } + }, None => Ok(WindowStacking::Foreground), } } diff --git a/crates/yuck/src/config/window_geometry.rs b/crates/yuck/src/config/window_geometry.rs index 5f8d7925..07b221d6 100644 --- a/crates/yuck/src/config/window_geometry.rs +++ b/crates/yuck/src/config/window_geometry.rs @@ -5,13 +5,17 @@ use crate::{ error::DiagResult, format_diagnostic::ToDiagnostic, parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent}, - value::{Coords, coords, NumWithUnit}, + value::{coords, Coords, NumWithUnit}, }; use super::window_definition::EnumParseError; use eww_shared_util::{Span, VarName}; use serde::{Deserialize, Serialize}; -use simplexpr::{SimplExpr, dynval::{DynVal, FromDynVal}, eval::EvalError}; +use simplexpr::{ + dynval::{DynVal, FromDynVal}, + eval::EvalError, + SimplExpr, +}; #[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, strum::Display)] pub enum AnchorAlignment { @@ -105,6 +109,7 @@ impl std::str::FromStr for AnchorPoint { } } +/// Unevaluated variant of [`Coords`] #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub struct CoordsDef { pub x: Option, @@ -130,13 +135,17 @@ impl CoordsDef { } } -fn convert_to_num_with_unit(opt_expr: &Option, local_variables: &HashMap) -> Result { +fn convert_to_num_with_unit( + opt_expr: &Option, + local_variables: &HashMap, +) -> Result { Ok(match opt_expr { Some(expr) => NumWithUnit::from_dynval(&expr.eval(local_variables)?)?, None => NumWithUnit::default(), }) } +/// Unevaluated variant of [`WindowGeometry`] #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub struct WindowGeometryDef { pub anchor_point: Option, @@ -154,14 +163,8 @@ impl FromAstElementContent for WindowGeometryDef { Ok(WindowGeometryDef { anchor_point: attrs.ast_optional("anchor")?, - size: CoordsDef { - x: attrs.ast_optional("width")?, - y: attrs.ast_optional("height")?, - }, - offset: CoordsDef { - x: attrs.ast_optional("x")?, - y: attrs.ast_optional("y")?, - }, + size: CoordsDef { x: attrs.ast_optional("width")?, y: attrs.ast_optional("height")? }, + offset: CoordsDef { x: attrs.ast_optional("x")?, y: attrs.ast_optional("y")? }, }) } } From 6a085e15a40174b96eeb468b7bf2a031a01477bf Mon Sep 17 00:00:00 2001 From: Wilf Silver Date: Wed, 16 Aug 2023 18:03:38 +0100 Subject: [PATCH 5/9] Rename window_argumentss to instance_id_to_args --- crates/eww/src/app.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index aef434c9..5c9bd510 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -115,7 +115,7 @@ pub struct App { pub eww_config: config::EwwConfig, /// Map of all currently open windows pub open_windows: HashMap, - pub window_argumentss: HashMap, + pub instance_id_to_args: HashMap, /// Window names that are supposed to be open, but failed. /// When reloading the config, these should be opened again. pub failed_windows: HashSet, @@ -138,7 +138,7 @@ impl std::fmt::Debug for App { .field("eww_config", &self.eww_config) .field("open_windows", &self.open_windows) .field("failed_windows", &self.failed_windows) - .field("window_arguments", &self.window_argumentss) + .field("window_arguments", &self.instance_id_to_args) .field("paths", &self.paths) .finish() } @@ -361,7 +361,7 @@ impl App { self.script_var_handler.stop_for_variable(unused_var.clone()); } - self.window_argumentss.remove(instance_id); + self.instance_id_to_args.remove(instance_id); Ok(()) } @@ -376,7 +376,7 @@ impl App { self.close_window(instance_id)?; } - self.window_argumentss.insert(instance_id.to_string(), window_args.clone()); + self.instance_id_to_args.insert(instance_id.to_string(), window_args.clone()); let open_result: Result<_> = try { let window_name: &str = &window_args.window_name; @@ -490,7 +490,7 @@ impl App { let open_window_ids: Vec = self.open_windows.keys().cloned().chain(self.failed_windows.iter().cloned()).dedup().collect(); for instance_id in &open_window_ids { - let window_arguments = self.window_argumentss.get(instance_id).with_context(|| { + let window_arguments = self.instance_id_to_args.get(instance_id).with_context(|| { format!("Cannot reopen window, initial parameters were not saved correctly for {instance_id}") })?; self.open_window(&window_arguments.clone())?; From 30b18039c9b4a56af885c40fa4575068076cf8f8 Mon Sep 17 00:00:00 2001 From: Wilf Silver Date: Wed, 16 Aug 2023 18:52:46 +0100 Subject: [PATCH 6/9] Update docs to emphasis window arguments being constant --- docs/src/configuration.md | 67 ++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/docs/src/configuration.md b/docs/src/configuration.md index f87211b0..2f62d10c 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -259,28 +259,44 @@ Eww then reads the provided value and renders the resulting widget. Whenever it Note that this is not all that efficient. Make sure to only use `literal` when necessary! -## Dynamically generated windows with arguments and ids +## Using window arguments and IDs -In some cases you may want to use the same window configuration for multiple widgets, e.g. for multiple windows. This is where arugments and ids come in. +In some cases you may want to use the same window configuration for multiple widgets, e.g. for multiple windows. This is where arguments and ids come in. -Firstly let us start off with ids. An id can be specified in the `open` command with `--id`, by default the id will be set to the name of the window configuration. These ids allow you to spawn multiple of the same windows. So for example you can do: +### Window ID + +Firstly let us start off with ids. An id can be specified in the `open` command +with `--id`, by default the id will be set to the name of the window +configuration. These ids allow you to spawn multiple of the same windows. So +for example you can do: ```bash eww open my_bar --screen 0 --id primary eww open my_bar --screen 1 --id secondary ``` -When using `open-many` you can follow the structure below. Again if no id is given, the id will default to the name of the window configuration. +When using `open-many` you can follow the structure below. Again if no id is +given, the id will default to the name of the window configuration. ```bash eww open-many my_config:primary my_config:secondary ``` -You may notice with this we didn't set `screen`, this is set through the `--arg` system, please see below for more information. +You may notice with this we didn't set `screen`, this is set through the +`--arg` system, please see below for more information. + +### Window Arguments -However you may want to have slight changes for each of these bars, e.g. spawning other windows on the same monitor. This is where the arguments come in. +However this may not be enough and you want to have slight changes for each of +these bars, e.g. having a different class for 1080p displays vs 4k or having +spawning the window in a different size or location. This is where the +arguments come in. -Defining arguments in a window is the exact same as in a widget so you can have: +Please note these arguments are **CONSTANT** and so cannot be update after the +window has been opened. + +Defining arguments in a window is the exact same as in a widget so you can +have: ```lisp (defwindow my_bar [arg1 ?arg2] @@ -288,17 +304,19 @@ Defining arguments in a window is the exact same as in a widget so you can have: :x "0%" :y "6px" :width "100%" - :height "30px" + :height { arg1 == "small" ? "30px" : "40px" } :anchor "top center") :stacking "bg" :windowtype "dock" :reserve (struts :distance "50px" :side "top") - ...) + (my_widget :arg2 arg2)) ``` Here we have two arguments, `arg1` and `arg2` (an optional parameter). -Once we have these parameters, when opening a new window, we must specify them (unless they are required, like `arg2`), but how? Well, we use the `--arg` option when running the `open` command: +Once we have these parameters, when opening a new window, we must specify them +(unless they are required, like `arg2`), but how? Well, we use the `--arg` +option when running the `open` command: ```bash eww open my_bar --id primary --arg arg1=some_value --arg arg2=another_value @@ -311,26 +329,37 @@ With the `open-many` it looks like this: eww open-many my_bar:primary --arg primary:arg1=some_value --arg primary:arg2=another_value ``` -Using this method you can define `screen`, `anchor`, `pos`, `size` inside the args for each window and it will act like giving `--screen`, `--anchor` etc. in the `open` command. - -You may notice that this is the same layout to set values with `update` and you'd be correct. +Using this method you can define `screen`, `anchor`, `pos`, `size` inside the +args for each window and it will act like giving `--screen`, `--anchor` etc. in +the `open` command. -So, now you know the basics, I shall introduce you to some of these "special" parameters, which are set slightly differently. However these can all be overridden by the `--arg` option. +So, now you know the basics, I shall introduce you to some of these "special" +parameters, which are set slightly differently. However these can all be +overridden by the `--arg` option. -- `id` - If `id` is included in the argument list, it will be set to the id specified by `--id` or will be set to the name of the config. This can be used when closing the current window through eww commands. -- `screen` - If `screen` is specified it will be set to the value given by `--screen`, so you can use this in other widgets to access screen specific information. +- `id` - If `id` is included in the argument list, it will be set to the id + specified by `--id` or will be set to the name of the config. This can be + used when closing the current window through eww commands. +- `screen` - If `screen` is specified it will be set to the value given by + `--screen`, so you can use this in other widgets to access screen specific + information. ### Further insight into args in `open-many` -Now due to the system behind processing the `open-many` `--arg` option you don't have to specify an id for each argument. If you do not, that argument will be applied across all windows e.g. +Now due to the system behind processing the `open-many` `--arg` option you +don't have to specify an id for each argument. If you do not, that argument +will be applied across all windows e.g. ```bash -eww open-many -c "~/.config/eww/bars" my_bar:primary my_bar:secondary --arg config="~/.config/eww/bars" +eww open-many my_bar:primary my_bar:secondary --arg gui_size="small" ``` This will mean the config is the same throughout the bars. -Furthermore if you didn't specify an id for the window, you can still set args specifically for that window - following the idea that the id will be set to the window configuration if not given - by just using the name of the window configuration e.g. +Furthermore if you didn't specify an id for the window, you can still set args +specifically for that window - following the idea that the id will be set to +the window configuration if not given - by just using the name of the window +configuration e.g. ```bash eww open-many my_primary_bar --arg my_primary_bar:screen=0 From fe245c750b43c2386e59f79258111b83667c2171 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:48:55 +0200 Subject: [PATCH 7/9] Replace eww windows with active-windows and list-windows --- CHANGELOG.md | 3 +++ crates/eww/src/app.rs | 21 +++++++++------------ crates/eww/src/opts.rs | 13 +++++++++---- crates/eww/src/server.rs | 2 +- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9becc4cd..47fbdf83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ All notable changes to eww will be listed here, starting at changes since versio ## [Unreleased] +### BREAKING CHANGES +- Remove `eww windows` command, replace with `eww active-windows` and `eww list-windows` + ### Features - Add `:namespace` window option - Default to building with x11 and wayland support simultaneously diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index 5c9bd510..a3bc918a 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -79,7 +79,8 @@ pub enum DaemonCommand { }, PrintDebug(DaemonResponseSender), PrintGraph(DaemonResponseSender), - PrintWindows(DaemonResponseSender), + ListWindows(DaemonResponseSender), + ListActiveWindows(DaemonResponseSender), } /// An opened window. @@ -113,7 +114,7 @@ pub struct App { pub display_backend: B, pub scope_graph: Rc>, pub eww_config: config::EwwConfig, - /// Map of all currently open windows + /// Map of all currently open windows by their IDs pub open_windows: HashMap, pub instance_id_to_args: HashMap, /// Window names that are supposed to be open, but failed. @@ -270,16 +271,12 @@ impl App { None => sender.send_failure(format!("Variable not found \"{}\"", name))?, } } - DaemonCommand::PrintWindows(sender) => { - let output = self - .eww_config - .get_windows() - .keys() - .map(|window_name| { - let is_open = self.open_windows.contains_key(window_name); - format!("{}{}", if is_open { "*" } else { "" }, window_name) - }) - .join("\n"); + DaemonCommand::ListWindows(sender) => { + let output = self.eww_config.get_windows().keys().join("\n"); + sender.send_success(output)? + } + DaemonCommand::ListActiveWindows(sender) => { + let output = self.open_windows.iter().map(|(id, window)| format!("{id}: {}", window.name)).join("\n"); sender.send_success(output)? } DaemonCommand::PrintDebug(sender) => { diff --git a/crates/eww/src/opts.rs b/crates/eww/src/opts.rs index df6f159c..39420fbd 100644 --- a/crates/eww/src/opts.rs +++ b/crates/eww/src/opts.rs @@ -179,9 +179,13 @@ pub enum ActionWithServer { #[command(name = "get")] GetVar { name: String }, - /// Print the names of all configured windows. Windows with a * in front of them are currently opened. - #[command(name = "windows")] - ShowWindows, + /// List the names of active windows + #[command(name = "list-windows")] + ListWindows, + + /// Show active window IDs, formatted linewise `: ` + #[command(name = "active-windows")] + ListActiveWindows, /// Print out the widget structure as seen by eww. /// @@ -273,7 +277,8 @@ impl ActionWithServer { return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, sender }); } ActionWithServer::Reload => return with_response_channel(app::DaemonCommand::ReloadConfigAndCss), - ActionWithServer::ShowWindows => return with_response_channel(app::DaemonCommand::PrintWindows), + ActionWithServer::ListWindows => return with_response_channel(app::DaemonCommand::ListWindows), + ActionWithServer::ListActiveWindows => return with_response_channel(app::DaemonCommand::ListActiveWindows), ActionWithServer::ShowState { all } => { return with_response_channel(|sender| app::DaemonCommand::PrintState { all, sender }) } diff --git a/crates/eww/src/server.rs b/crates/eww/src/server.rs index feebfce2..5a7ecc87 100644 --- a/crates/eww/src/server.rs +++ b/crates/eww/src/server.rs @@ -81,7 +81,7 @@ pub fn initialize_server( eww_config, open_windows: HashMap::new(), failed_windows: HashSet::new(), - window_argumentss: HashMap::new(), + instance_id_to_args: HashMap::new(), css_provider: gtk::CssProvider::new(), script_var_handler, app_evt_send: ui_send.clone(), From cac553e9facc408282db44c221ad0e28df5e43a2 Mon Sep 17 00:00:00 2001 From: Wilf Silver Date: Fri, 18 Aug 2023 21:40:56 +0100 Subject: [PATCH 8/9] Fix extracting duration from string --- crates/eww/src/window_arguments.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/eww/src/window_arguments.rs b/crates/eww/src/window_arguments.rs index 10db92f0..029bd924 100644 --- a/crates/eww/src/window_arguments.rs +++ b/crates/eww/src/window_arguments.rs @@ -10,12 +10,8 @@ use yuck::{ value::Coords, }; -pub fn extract_value_from_args(name: &str, args: &mut HashMap) -> Option { - args.remove(&VarName(name.to_string())) -} - fn parse_value_from_args(name: &str, args: &mut HashMap) -> Result, T::Err> { - extract_value_from_args(name, args).map(|x| FromStr::from_str(&x.as_string().unwrap())).transpose() + args.remove(&VarName(name.to_string())).map(|x| FromStr::from_str(&x.as_string().unwrap())).transpose() } /// This stores the arguments given in the command line to create a window @@ -45,7 +41,7 @@ impl WindowArguments { size: parse_value_from_args::("size", &mut args)?, monitor: parse_value_from_args::("screen", &mut args)?, anchor: parse_value_from_args::("anchor", &mut args)?, - duration: extract_value_from_args("duration", &mut args) + duration: parse_value_from_args::("duration", &mut args)? .map(|x| x.as_duration()) .transpose() .context("Not a valid duration")?, From 80441ce6587fe6563f640a68c2fd869fd9cfe589 Mon Sep 17 00:00:00 2001 From: Wilf Silver Date: Sat, 19 Aug 2023 09:24:29 +0100 Subject: [PATCH 9/9] Format + reduce warnings --- crates/eww/src/opts.rs | 2 +- crates/eww/src/window_arguments.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/eww/src/opts.rs b/crates/eww/src/opts.rs index 39420fbd..526aa3c7 100644 --- a/crates/eww/src/opts.rs +++ b/crates/eww/src/opts.rs @@ -226,7 +226,7 @@ fn parse_window_id_args(s: &str) -> Result<(String, VarName, DynVal)> { // Parse the = first so we know if an id has not been given let (name, value) = parse_var_update_arg(s)?; - let (id, var_name) = (&name.0).split_once(':').unwrap_or((&"", &name.0)); + let (id, var_name) = name.0.split_once(':').unwrap_or(("", &name.0)); Ok((id.to_string(), var_name.into(), value)) } diff --git a/crates/eww/src/window_arguments.rs b/crates/eww/src/window_arguments.rs index 029bd924..8833bac9 100644 --- a/crates/eww/src/window_arguments.rs +++ b/crates/eww/src/window_arguments.rs @@ -77,8 +77,7 @@ impl WindowArguments { } if local_variables.len() != window_def.expected_args.len() { - let unexpected_vars: Vec<_> = - local_variables.iter().map(|(name, _)| name.clone()).filter(|n| !expected_args.contains(&n.0)).collect(); + let unexpected_vars: Vec<_> = local_variables.keys().cloned().filter(|n| !expected_args.contains(&n.0)).collect(); bail!( "variables {} unexpectedly defined when creating window with id '{}'", unexpected_vars.join(", "),