Add window arguments (#431)

* Allow window definitions to have parameters

* Implement SimplExpr for all other window definition options

* Take gtk scaling into account when setting struts

* Cleanup

* Rename window_argumentss to instance_id_to_args

* Update docs to emphasis window arguments being constant

* Replace eww windows with active-windows and list-windows

* Fix extracting duration from string

* Format + reduce warnings

---------

Co-authored-by: elkowar <5300871+elkowar@users.noreply.github.com>
This commit is contained in:
WilfSilver 2023-12-20 20:04:38 +00:00 committed by GitHub
parent 4f1f853b5f
commit 65d622c81f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 863 additions and 210 deletions

View file

@ -5,6 +5,9 @@ All notable changes to eww will be listed here, starting at changes since versio
## [Unreleased] ## [Unreleased]
### BREAKING CHANGES
- Remove `eww windows` command, replace with `eww active-windows` and `eww list-windows`
### Features ### Features
- Add `:namespace` window option - Add `:namespace` window option
- Default to building with x11 and wayland support simultaneously - Default to building with x11 and wayland support simultaneously
@ -73,6 +76,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 `:onaccept` to input field, add `:onclick` to eventbox
- Add `EWW_CMD`, `EWW_CONFIG_DIR`, `EWW_EXECUTABLE` magic variables - Add `EWW_CMD`, `EWW_CONFIG_DIR`, `EWW_EXECUTABLE` magic variables
- Add `overlay` widget (By: viandoxdev) - Add `overlay` widget (By: viandoxdev)
- Add arguments option to `defwindow` (By: WilfSilver)
### Notable Internal changes ### Notable Internal changes
- Rework state management completely, now making local state and dynamic widget hierarchy changes possible. - Rework state management completely, now making local state and dynamic widget hierarchy changes possible.

View file

@ -7,15 +7,18 @@ use crate::{
paths::EwwPaths, paths::EwwPaths,
script_var_handler::ScriptVarHandlerHandle, script_var_handler::ScriptVarHandlerHandle,
state::scope_graph::{ScopeGraph, ScopeIndex}, state::scope_graph::{ScopeGraph, ScopeIndex},
window_arguments::WindowArguments,
window_initiator::WindowInitiator,
*, *,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use codespan_reporting::files::Files; use codespan_reporting::files::Files;
use eww_shared_util::{Span, VarName}; use eww_shared_util::{Span, VarName};
use gdk::Monitor;
use glib::ObjectExt; use glib::ObjectExt;
use itertools::Itertools; use itertools::Itertools;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use simplexpr::dynval::DynVal; use simplexpr::{dynval::DynVal, SimplExpr};
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
@ -26,7 +29,6 @@ use yuck::{
config::{ config::{
monitor::MonitorIdentifier, monitor::MonitorIdentifier,
script_var_definition::ScriptVarDefinition, script_var_definition::ScriptVarDefinition,
window_definition::WindowDefinition,
window_geometry::{AnchorPoint, WindowGeometry}, window_geometry::{AnchorPoint, WindowGeometry},
}, },
error::DiagError, error::DiagError,
@ -44,12 +46,14 @@ pub enum DaemonCommand {
ReloadConfigAndCss(DaemonResponseSender), ReloadConfigAndCss(DaemonResponseSender),
OpenInspector, OpenInspector,
OpenMany { OpenMany {
windows: Vec<String>, windows: Vec<(String, String)>,
args: Vec<(String, VarName, DynVal)>,
should_toggle: bool, should_toggle: bool,
sender: DaemonResponseSender, sender: DaemonResponseSender,
}, },
OpenWindow { OpenWindow {
window_name: String, window_name: String,
instance_id: Option<String>,
pos: Option<Coords>, pos: Option<Coords>,
size: Option<Coords>, size: Option<Coords>,
anchor: Option<AnchorPoint>, anchor: Option<AnchorPoint>,
@ -57,6 +61,7 @@ pub enum DaemonCommand {
should_toggle: bool, should_toggle: bool,
duration: Option<std::time::Duration>, duration: Option<std::time::Duration>,
sender: DaemonResponseSender, sender: DaemonResponseSender,
args: Option<Vec<(VarName, DynVal)>>,
}, },
CloseWindows { CloseWindows {
windows: Vec<String>, windows: Vec<String>,
@ -74,12 +79,17 @@ pub enum DaemonCommand {
}, },
PrintDebug(DaemonResponseSender), PrintDebug(DaemonResponseSender),
PrintGraph(DaemonResponseSender), PrintGraph(DaemonResponseSender),
PrintWindows(DaemonResponseSender), ListWindows(DaemonResponseSender),
ListActiveWindows(DaemonResponseSender),
} }
/// An opened window. /// An opened window.
#[derive(Debug)] #[derive(Debug)]
pub struct EwwWindow { 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 name: String,
pub scope_index: ScopeIndex, pub scope_index: ScopeIndex,
pub gtk_window: gtk::Window, pub gtk_window: gtk::Window,
@ -104,8 +114,9 @@ pub struct App<B> {
pub display_backend: B, pub display_backend: B,
pub scope_graph: Rc<RefCell<ScopeGraph>>, pub scope_graph: Rc<RefCell<ScopeGraph>>,
pub eww_config: config::EwwConfig, pub eww_config: config::EwwConfig,
/// Map of all currently open windows /// Map of all currently open windows by their IDs
pub open_windows: HashMap<String, EwwWindow>, pub open_windows: HashMap<String, EwwWindow>,
pub instance_id_to_args: HashMap<String, WindowArguments>,
/// Window names that are supposed to be open, but failed. /// Window names that are supposed to be open, but failed.
/// When reloading the config, these should be opened again. /// When reloading the config, these should be opened again.
pub failed_windows: HashSet<String>, pub failed_windows: HashSet<String>,
@ -128,6 +139,7 @@ impl<B> std::fmt::Debug for App<B> {
.field("eww_config", &self.eww_config) .field("eww_config", &self.eww_config)
.field("open_windows", &self.open_windows) .field("open_windows", &self.open_windows)
.field("failed_windows", &self.failed_windows) .field("failed_windows", &self.failed_windows)
.field("window_arguments", &self.instance_id_to_args)
.field("paths", &self.paths) .field("paths", &self.paths)
.finish() .finish()
} }
@ -178,14 +190,25 @@ impl<B: DisplayBackend> App<B> {
self.close_window(&window_name)?; self.close_window(&window_name)?;
} }
} }
DaemonCommand::OpenMany { windows, should_toggle, sender } => { DaemonCommand::OpenMany { windows, args, should_toggle, sender } => {
let errors = windows let errors = windows
.iter() .iter()
.map(|w| { .map(|w| {
if should_toggle && self.open_windows.contains_key(w) { let (config_name, id) = w;
self.close_window(w) if should_toggle && self.open_windows.contains_key(id) {
self.close_window(id)
} else { } else {
self.open_window(w, None, None, None, None, None) log::debug!("Config: {}, id: {}", config_name, id);
let window_args = args
.iter()
.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(),
config_name.clone(),
window_args,
)?)
} }
}) })
.filter_map(Result::err); .filter_map(Result::err);
@ -193,6 +216,7 @@ impl<B: DisplayBackend> App<B> {
} }
DaemonCommand::OpenWindow { DaemonCommand::OpenWindow {
window_name, window_name,
instance_id,
pos, pos,
size, size,
anchor, anchor,
@ -200,13 +224,27 @@ impl<B: DisplayBackend> App<B> {
should_toggle, should_toggle,
duration, duration,
sender, sender,
args,
} => { } => {
let is_open = self.open_windows.contains_key(&window_name); let instance_id = instance_id.unwrap_or_else(|| window_name.clone());
let is_open = self.open_windows.contains_key(&instance_id);
let result = if should_toggle && is_open { let result = if should_toggle && is_open {
self.close_window(&window_name) self.close_window(&instance_id)
} else { } else {
self.open_window(&window_name, pos, size, monitor, anchor, duration) self.open_window(&WindowArguments {
instance_id,
window_name,
pos,
size,
monitor,
anchor,
duration,
args: args.unwrap_or_default().into_iter().collect(),
})
}; };
sender.respond_with_result(result)?; sender.respond_with_result(result)?;
} }
DaemonCommand::CloseWindows { windows, sender } => { DaemonCommand::CloseWindows { windows, sender } => {
@ -233,16 +271,12 @@ impl<B: DisplayBackend> App<B> {
None => sender.send_failure(format!("Variable not found \"{}\"", name))?, None => sender.send_failure(format!("Variable not found \"{}\"", name))?,
} }
} }
DaemonCommand::PrintWindows(sender) => { DaemonCommand::ListWindows(sender) => {
let output = self let output = self.eww_config.get_windows().keys().join("\n");
.eww_config sender.send_success(output)?
.get_windows() }
.keys() DaemonCommand::ListActiveWindows(sender) => {
.map(|window_name| { let output = self.open_windows.iter().map(|(id, window)| format!("{id}: {}", window.name)).join("\n");
let is_open = self.open_windows.contains_key(window_name);
format!("{}{}", if is_open { "*" } else { "" }, window_name)
})
.join("\n");
sender.send_success(output)? sender.send_success(output)?
} }
DaemonCommand::PrintDebug(sender) => { DaemonCommand::PrintDebug(sender) => {
@ -304,14 +338,14 @@ impl<B: DisplayBackend> App<B> {
} }
/// Close a window and do all the required cleanups in the scope_graph and script_var_handler /// 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<()> { fn close_window(&mut self, instance_id: &str) -> Result<()> {
if let Some(old_abort_send) = self.window_close_timer_abort_senders.remove(window_name) { if let Some(old_abort_send) = self.window_close_timer_abort_senders.remove(instance_id) {
_ = old_abort_send.send(()); _ = old_abort_send.send(());
} }
let eww_window = self let eww_window = self
.open_windows .open_windows
.remove(window_name) .remove(instance_id)
.with_context(|| format!("Tried to close window named '{}', but no such window was open", window_name))?; .with_context(|| format!("Tried to close window with id '{instance_id}', but no such window was open"))?;
let scope_index = eww_window.scope_index; let scope_index = eww_window.scope_index;
eww_window.close(); eww_window.close();
@ -324,52 +358,54 @@ impl<B: DisplayBackend> App<B> {
self.script_var_handler.stop_for_variable(unused_var.clone()); self.script_var_handler.stop_for_variable(unused_var.clone());
} }
self.instance_id_to_args.remove(instance_id);
Ok(()) Ok(())
} }
fn open_window( fn open_window(&mut self, window_args: &WindowArguments) -> Result<()> {
&mut self, let instance_id = &window_args.instance_id;
window_name: &str, self.failed_windows.remove(instance_id);
pos: Option<Coords>, log::info!("Opening window {} as '{}'", window_args.window_name, instance_id);
size: Option<Coords>,
monitor: Option<MonitorIdentifier>,
anchor: Option<AnchorPoint>,
duration: Option<std::time::Duration>,
) -> Result<()> {
self.failed_windows.remove(window_name);
log::info!("Opening window {}", window_name);
// if an instance of this is already running, close it // 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(instance_id) {
if self.open_windows.contains_key(window_name) { self.close_window(instance_id)?;
self.close_window(window_name)?;
} }
self.instance_id_to_args.insert(instance_id.to_string(), window_args.clone());
let open_result: Result<_> = try { let open_result: Result<_> = try {
let mut window_def = self.eww_config.get_window(window_name)?.clone(); 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"); 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 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( let window_scope = self.scope_graph.borrow_mut().register_new_scope(
window_name.to_string(), instance_id.to_string(),
Some(root_index), Some(root_index),
root_index, root_index,
HashMap::new(), scoped_vars_literal,
)?; )?;
let root_widget = crate::widgets::build_widget::build_gtk_widget( let root_widget = crate::widgets::build_widget::build_gtk_widget(
&mut self.scope_graph.borrow_mut(), &mut self.scope_graph.borrow_mut(),
Rc::new(self.eww_config.get_widget_definitions().clone()), Rc::new(self.eww_config.get_widget_definitions().clone()),
window_scope, window_scope,
window_def.widget.clone(), window_def.widget,
None, 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::<B>(monitor_geometry, root_widget, window_def, window_scope)?; let monitor = get_gdk_monitor(initiator.monitor.clone())?;
let mut eww_window = initialize_window::<B>(&initiator, monitor, root_widget, window_scope)?;
eww_window.gtk_window.style_context().add_class(window_name); 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, // initialize script var handlers for variables. As starting a scriptvar with the script_var_handler is idempodent,
@ -383,32 +419,32 @@ impl<B: DisplayBackend> App<B> {
eww_window.destroy_event_handler_id = Some(eww_window.gtk_window.connect_destroy({ eww_window.destroy_event_handler_id = Some(eww_window.gtk_window.connect_destroy({
let app_evt_sender = self.app_evt_send.clone(); 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 |_| { move |_| {
// we don't care about the actual error response from the daemon as this is mostly just a fallback. // 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. // Generally, this should get disconnected before the gtk window gets destroyed.
// It serves as a fallback for when the window is closed manually. // It serves as a fallback for when the window is closed manually.
let (response_sender, _) = daemon_response::create_pair(); 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) { if let Err(err) = app_evt_sender.send(command) {
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err); 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 { if let Some(duration) = duration {
let app_evt_sender = self.app_evt_send.clone(); 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(); let (abort_send, abort_recv) = futures::channel::oneshot::channel();
glib::MainContext::default().spawn_local({ glib::MainContext::default().spawn_local({
let window_name = window_name.clone(); let instance_id = instance_id.to_string();
async move { async move {
tokio::select! { tokio::select! {
_ = glib::timeout_future(duration) => { _ = glib::timeout_future(duration) => {
let (response_sender, mut response_recv) = daemon_response::create_pair(); 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) { if let Err(err) = app_evt_sender.send(command) {
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err); log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
} }
@ -419,17 +455,17 @@ impl<B: DisplayBackend> App<B> {
} }
}); });
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(()); _ = 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 { if let Err(err) = open_result {
self.failed_windows.insert(window_name.to_string()); self.failed_windows.insert(instance_id.to_string());
Err(err).with_context(|| format!("failed to open window `{}`", window_name)) Err(err).with_context(|| format!("failed to open window `{}`", instance_id))
} else { } else {
Ok(()) Ok(())
} }
@ -448,10 +484,13 @@ impl<B: DisplayBackend> App<B> {
self.eww_config = config; self.eww_config = config;
self.scope_graph.borrow_mut().clear(self.eww_config.generate_initial_state()?); self.scope_graph.borrow_mut().clear(self.eww_config.generate_initial_state()?);
let window_names: Vec<String> = let open_window_ids: Vec<String> =
self.open_windows.keys().cloned().chain(self.failed_windows.iter().cloned()).dedup().collect(); self.open_windows.keys().cloned().chain(self.failed_windows.iter().cloned()).dedup().collect();
for window_name in &window_names { for instance_id in &open_window_ids {
self.open_window(window_name, None, None, None, None, None)?; 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())?;
} }
Ok(()) Ok(())
} }
@ -480,19 +519,20 @@ impl<B: DisplayBackend> App<B> {
} }
fn initialize_window<B: DisplayBackend>( fn initialize_window<B: DisplayBackend>(
monitor_geometry: gdk::Rectangle, window_init: &WindowInitiator,
monitor: Monitor,
root_widget: gtk::Widget, root_widget: gtk::Widget,
window_def: WindowDefinition,
window_scope: ScopeIndex, window_scope: ScopeIndex,
) -> Result<EwwWindow> { ) -> Result<EwwWindow> {
let window = B::initialize_window(&window_def, monitor_geometry) let monitor_geometry = 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_position(gtk::WindowPosition::None);
window.set_gravity(gdk::Gravity::Center); 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); let actual_window_rect = get_window_rectangle(geometry, monitor_geometry);
window.set_size_request(actual_window_rect.width(), actual_window_rect.height()); window.set_size_request(actual_window_rect.width(), actual_window_rect.height());
window.set_default_size(actual_window_rect.width(), actual_window_rect.height()); window.set_default_size(actual_window_rect.width(), actual_window_rect.height());
@ -511,21 +551,27 @@ fn initialize_window<B: DisplayBackend>(
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
if B::IS_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); 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, _| { window.connect_configure_event(move |window, _| {
let _ = apply_window_position(geometry, monitor_geometry, window); let _ = apply_window_position(geometry, monitor_geometry, window);
false false
}); });
} }
} }
display_backend::set_xprops(&window, monitor_geometry, &window_def)?; display_backend::set_xprops(&window, monitor, window_init)?;
} }
window.show_all(); 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. /// Apply the provided window-positioning rules to the window.
@ -555,7 +601,7 @@ fn on_screen_changed(window: &gtk::Window, _old_screen: Option<&gdk::Screen>) {
} }
/// Get the monitor geometry of a given monitor, or the default if none is given /// Get the monitor geometry of a given monitor, or the default if none is given
fn get_monitor_geometry(identifier: Option<MonitorIdentifier>) -> Result<gdk::Rectangle> { fn get_gdk_monitor(identifier: Option<MonitorIdentifier>) -> Result<Monitor> {
let display = gdk::Display::default().expect("could not get default display"); let display = gdk::Display::default().expect("could not get default display");
let monitor = match identifier { let monitor = match identifier {
Some(ident) => { Some(ident) => {
@ -575,7 +621,7 @@ fn get_monitor_geometry(identifier: Option<MonitorIdentifier>) -> Result<gdk::Re
.primary_monitor() .primary_monitor()
.context("Failed to get primary monitor from GTK. Try explicitly specifying the monitor on your window.")?, .context("Failed to get primary monitor from GTK. Try explicitly specifying the monitor on your window.")?,
}; };
Ok(monitor.geometry()) Ok(monitor)
} }
/// Returns the [Monitor][gdk::Monitor] structure corresponding to the identifer. /// Returns the [Monitor][gdk::Monitor] structure corresponding to the identifer.

View file

@ -1,4 +1,4 @@
use yuck::config::window_definition::WindowDefinition; use crate::window_initiator::WindowInitiator;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
pub use platform_wayland::WaylandBackend; pub use platform_wayland::WaylandBackend;
@ -8,7 +8,8 @@ pub use platform_x11::{set_xprops, X11Backend};
pub trait DisplayBackend: Send + Sync + 'static { pub trait DisplayBackend: Send + Sync + 'static {
const IS_X11: bool; const IS_X11: bool;
fn initialize_window(window_def: &WindowDefinition, monitor: gdk::Rectangle) -> Option<gtk::Window>;
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle) -> Option<gtk::Window>;
} }
pub struct NoBackend; pub struct NoBackend;
@ -16,18 +17,16 @@ pub struct NoBackend;
impl DisplayBackend for NoBackend { impl DisplayBackend for NoBackend {
const IS_X11: bool = false; const IS_X11: bool = false;
fn initialize_window(_window_def: &WindowDefinition, _monitor: gdk::Rectangle) -> Option<gtk::Window> { fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Rectangle) -> Option<gtk::Window> {
Some(gtk::Window::new(gtk::WindowType::Toplevel)) Some(gtk::Window::new(gtk::WindowType::Toplevel))
} }
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
mod platform_wayland { mod platform_wayland {
use crate::window_initiator::WindowInitiator;
use gtk::prelude::*; use gtk::prelude::*;
use yuck::config::{ use yuck::config::{window_definition::WindowStacking, window_geometry::AnchorAlignment};
window_definition::{WindowDefinition, WindowStacking},
window_geometry::AnchorAlignment,
};
use super::DisplayBackend; use super::DisplayBackend;
@ -36,12 +35,12 @@ mod platform_wayland {
impl DisplayBackend for WaylandBackend { impl DisplayBackend for WaylandBackend {
const IS_X11: bool = false; const IS_X11: bool = false;
fn initialize_window(window_def: &WindowDefinition, monitor: gdk::Rectangle) -> Option<gtk::Window> { fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle) -> Option<gtk::Window> {
let window = gtk::Window::new(gtk::WindowType::Toplevel); let window = gtk::Window::new(gtk::WindowType::Toplevel);
// Initialising a layer shell surface // Initialising a layer shell surface
gtk_layer_shell::init_for_window(&window); gtk_layer_shell::init_for_window(&window);
// Sets the monitor where the surface is shown // 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"); let display = gdk::Display::default().expect("could not get default display");
if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) { if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) {
gtk_layer_shell::set_monitor(&window, &monitor); gtk_layer_shell::set_monitor(&window, &monitor);
@ -49,24 +48,24 @@ mod platform_wayland {
return None; return None;
} }
}; };
window.set_resizable(window_def.resizable); window.set_resizable(window_init.resizable);
// Sets the layer where the layer shell surface will spawn // 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::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::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::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), 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); gtk_layer_shell::set_namespace(&window, namespace);
} }
// Sets the keyboard interactivity // 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 // Positioning surface
let mut top = false; let mut top = false;
let mut left = false; let mut left = false;
@ -103,7 +102,7 @@ mod platform_wayland {
gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Top, yoffset); 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); gtk_layer_shell::auto_exclusive_zone_enable(&window);
} }
Some(window) Some(window)
@ -113,7 +112,9 @@ mod platform_wayland {
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
mod platform_x11 { mod platform_x11 {
use crate::window_initiator::WindowInitiator;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use gdk::Monitor;
use gtk::{self, prelude::*}; use gtk::{self, prelude::*};
use x11rb::protocol::xproto::ConnectionExt; use x11rb::protocol::xproto::ConnectionExt;
@ -125,7 +126,7 @@ mod platform_x11 {
}; };
use yuck::config::{ use yuck::config::{
backend_window_options::{Side, X11WindowType}, backend_window_options::{Side, X11WindowType},
window_definition::{WindowDefinition, WindowStacking}, window_definition::WindowStacking,
}; };
use super::DisplayBackend; use super::DisplayBackend;
@ -134,14 +135,14 @@ mod platform_x11 {
impl DisplayBackend for X11Backend { impl DisplayBackend for X11Backend {
const IS_X11: bool = true; const IS_X11: bool = true;
fn initialize_window(window_def: &WindowDefinition, _monitor: gdk::Rectangle) -> Option<gtk::Window> { fn initialize_window(window_init: &WindowInitiator, _monitor: gdk::Rectangle) -> Option<gtk::Window> {
let window_type = 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); let window = gtk::Window::new(window_type);
window.set_resizable(window_def.resizable); window.set_resizable(window_init.resizable);
window.set_keep_above(window_def.stacking == WindowStacking::Foreground); window.set_keep_above(window_init.stacking == WindowStacking::Foreground);
window.set_keep_below(window_def.stacking == WindowStacking::Background); window.set_keep_below(window_init.stacking == WindowStacking::Background);
if window_def.backend_options.x11.sticky { if window_init.backend_options.x11.sticky {
window.stick(); window.stick();
} else { } else {
window.unstick(); window.unstick();
@ -150,9 +151,9 @@ mod platform_x11 {
} }
} }
pub fn set_xprops(window: &gtk::Window, monitor: gdk::Rectangle, window_def: &WindowDefinition) -> Result<()> { pub fn set_xprops(window: &gtk::Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
let backend = X11BackendConnection::new()?; let backend = X11BackendConnection::new()?;
backend.set_xprops_for(window, monitor, window_def)?; backend.set_xprops_for(window, monitor, window_init)?;
Ok(()) Ok(())
} }
@ -170,24 +171,23 @@ mod platform_x11 {
Ok(X11BackendConnection { conn, root_window: screen.root, atoms }) Ok(X11BackendConnection { conn, root_window: screen.root, atoms })
} }
fn set_xprops_for( fn set_xprops_for(&self, window: &gtk::Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
&self, let monitor_rect = monitor.geometry();
window: &gtk::Window, let scale_factor = monitor.scale_factor() as u32;
monitor_rect: gdk::Rectangle,
window_def: &WindowDefinition,
) -> Result<()> {
let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?; let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?;
let win_id = let win_id =
gdk_window.downcast_ref::<gdkx11::X11Window>().context("Failed to get x11 window for gtk window")?.xid() as u32; gdk_window.downcast_ref::<gdkx11::X11Window>().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 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_x = scale_factor * monitor_rect.x() as u32;
let mon_end_y = (monitor_rect.y() + monitor_rect.height()) as u32 - 1u32; 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 { let dist = match strut_def.side {
Side::Left | Side::Right => strut_def.dist.pixels_relative_to(monitor_rect.width()) as u32, Side::Left | Side::Right => strut_def.distance.pixels_relative_to(monitor_rect.width()) as u32,
Side::Top | Side::Bottom => strut_def.dist.pixels_relative_to(monitor_rect.height()) as u32, Side::Top | Side::Bottom => strut_def.distance.pixels_relative_to(monitor_rect.height()) as u32,
}; };
// don't question it,..... // don't question it,.....
@ -195,10 +195,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 // 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] #[rustfmt::skip]
let strut_list: Vec<u8> = match strut_def.side { let strut_list: Vec<u8> = match strut_def.side {
Side::Left => vec![dist + monitor_rect.x() as u32, 0, 0, 0, monitor_rect.y() as u32, mon_end_y, 0, 0, 0, 0, 0, 0], Side::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, monitor_rect.y() as u32, mon_end_y, 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 + monitor_rect.y() as u32, 0, 0, 0, 0, 0, monitor_rect.x() as u32, mon_end_x, 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, monitor_rect.x() as u32, mon_end_x], 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 // This should never happen but if it does the window will be anchored on the
// right of the screen // right of the screen
}.iter().flat_map(|x| x.to_le_bytes().to_vec()).collect(); }.iter().flat_map(|x| x.to_le_bytes().to_vec()).collect();
@ -233,7 +233,7 @@ mod platform_x11 {
win_id, win_id,
self.atoms._NET_WM_WINDOW_TYPE, self.atoms._NET_WM_WINDOW_TYPE,
self.atoms.ATOM, 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::Dock => self.atoms._NET_WM_WINDOW_TYPE_DOCK,
X11WindowType::Normal => self.atoms._NET_WM_WINDOW_TYPE_NORMAL, X11WindowType::Normal => self.atoms._NET_WM_WINDOW_TYPE_NORMAL,
X11WindowType::Dialog => self.atoms._NET_WM_WINDOW_TYPE_DIALOG, X11WindowType::Dialog => self.atoms._NET_WM_WINDOW_TYPE_DIALOG,

View file

@ -4,6 +4,7 @@
#![feature(slice_concat_trait)] #![feature(slice_concat_trait)]
#![feature(try_blocks)] #![feature(try_blocks)]
#![feature(hash_extract_if)] #![feature(hash_extract_if)]
#![feature(let_chains)]
#![allow(rustdoc::private_intra_doc_links)] #![allow(rustdoc::private_intra_doc_links)]
extern crate gtk; extern crate gtk;
@ -36,6 +37,8 @@ mod server;
mod state; mod state;
mod util; mod util;
mod widgets; mod widgets;
mod window_arguments;
mod window_initiator;
fn main() { fn main() {
let eww_binary_name = std::env::args().next().unwrap(); let eww_binary_name = std::env::args().next().unwrap();

View file

@ -101,6 +101,10 @@ pub enum ActionWithServer {
/// Name of the window you want to open. /// Name of the window you want to open.
window_name: String, window_name: String,
// The id of the window instance
#[arg(long)]
id: Option<String>,
/// The identifier of the monitor the window should open on /// The identifier of the monitor the window should open on
#[arg(long)] #[arg(long)]
screen: Option<MonitorIdentifier>, screen: Option<MonitorIdentifier>,
@ -124,13 +128,23 @@ pub enum ActionWithServer {
/// Automatically close the window after a specified amount of time, i.e.: 1s /// Automatically close the window after a specified amount of time, i.e.: 1s
#[arg(long, value_parser=parse_duration)] #[arg(long, value_parser=parse_duration)]
duration: Option<std::time::Duration>, duration: Option<std::time::Duration>,
/// Define a variable for the window, i.e.: `--arg "var_name=value"`
#[arg(long = "arg", value_parser = parse_var_update_arg)]
args: Option<Vec<(VarName, DynVal)>>,
}, },
/// Open multiple windows at once. /// Open multiple windows at once.
/// NOTE: This will in the future be part of eww open, and will then be removed. /// NOTE: This will in the future be part of eww open, and will then be removed.
#[command(name = "open-many")] #[command(name = "open-many")]
OpenMany { OpenMany {
windows: Vec<String>, /// 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)>,
/// 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 /// If a window is already open, close it instead
#[arg(long = "toggle")] #[arg(long = "toggle")]
@ -165,9 +179,13 @@ pub enum ActionWithServer {
#[command(name = "get")] #[command(name = "get")]
GetVar { name: String }, GetVar { name: String },
/// Print the names of all configured windows. Windows with a * in front of them are currently opened. /// List the names of active windows
#[command(name = "windows")] #[command(name = "list-windows")]
ShowWindows, ListWindows,
/// Show active window IDs, formatted linewise `<window_id>: <window_name>`
#[command(name = "active-windows")]
ListActiveWindows,
/// Print out the widget structure as seen by eww. /// Print out the widget structure as seen by eww.
/// ///
@ -195,6 +213,25 @@ impl From<RawOpt> 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)?;
let (id, var_name) = name.0.split_once(':').unwrap_or(("", &name.0));
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)> { fn parse_var_update_arg(s: &str) -> Result<(VarName, DynVal)> {
let (name, value) = s let (name, value) = s
.split_once('=') .split_once('=')
@ -219,12 +256,13 @@ impl ActionWithServer {
let _ = send.send(DaemonResponse::Success("pong".to_owned())); let _ = send.send(DaemonResponse::Success("pong".to_owned()));
return (app::DaemonCommand::NoOp, Some(recv)); return (app::DaemonCommand::NoOp, Some(recv));
} }
ActionWithServer::OpenMany { windows, should_toggle } => { ActionWithServer::OpenMany { windows, args, should_toggle } => {
return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, should_toggle, sender }); 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, args } => {
return with_response_channel(|sender| app::DaemonCommand::OpenWindow { return with_response_channel(|sender| app::DaemonCommand::OpenWindow {
window_name, window_name,
instance_id: id,
pos, pos,
size, size,
anchor, anchor,
@ -232,13 +270,15 @@ impl ActionWithServer {
should_toggle, should_toggle,
duration, duration,
sender, sender,
args,
}) })
} }
ActionWithServer::CloseWindows { windows } => { ActionWithServer::CloseWindows { windows } => {
return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, sender }); return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, sender });
} }
ActionWithServer::Reload => return with_response_channel(app::DaemonCommand::ReloadConfigAndCss), 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 } => { ActionWithServer::ShowState { all } => {
return with_response_channel(|sender| app::DaemonCommand::PrintState { all, sender }) return with_response_channel(|sender| app::DaemonCommand::PrintState { all, sender })
} }

View file

@ -81,6 +81,7 @@ pub fn initialize_server<B: DisplayBackend>(
eww_config, eww_config,
open_windows: HashMap::new(), open_windows: HashMap::new(),
failed_windows: HashSet::new(), failed_windows: HashSet::new(),
instance_id_to_args: HashMap::new(),
css_provider: gtk::CssProvider::new(), css_provider: gtk::CssProvider::new(),
script_var_handler, script_var_handler,
app_evt_send: ui_send.clone(), app_evt_send: ui_send.clone(),

View file

@ -0,0 +1,90 @@
use anyhow::{bail, 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 parse_value_from_args<T: FromStr>(name: &str, args: &mut HashMap<VarName, DynVal>) -> Result<Option<T>, T::Err> {
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
/// While creating a window, we combine this with information from 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<AnchorPoint>,
pub args: HashMap<VarName, DynVal>,
pub duration: Option<std::time::Duration>,
pub monitor: Option<MonitorIdentifier>,
pub pos: Option<Coords>,
pub size: Option<Coords>,
}
impl WindowArguments {
pub fn new_from_args(id: String, config_name: String, mut args: HashMap<VarName, DynVal>) -> Result<Self> {
let initiator = WindowArguments {
window_name: config_name,
instance_id: id,
pos: parse_value_from_args::<Coords>("pos", &mut args)?,
size: parse_value_from_args::<Coords>("size", &mut args)?,
monitor: parse_value_from_args::<MonitorIdentifier>("screen", &mut args)?,
anchor: parse_value_from_args::<AnchorPoint>("anchor", &mut args)?,
duration: parse_value_from_args::<DynVal>("duration", &mut args)?
.map(|x| x.as_duration())
.transpose()
.context("Not a valid duration")?,
args,
};
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<HashMap<VarName, DynVal>> {
let expected_args: HashSet<&String> = window_def.expected_args.iter().map(|x| &x.name.0).collect();
let mut local_variables: HashMap<VarName, DynVal> = HashMap::new();
// 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.instance_id.clone()));
}
if self.monitor.is_some() && expected_args.contains(&"screen".to_string()) {
let mon_dyn = DynVal::from(&self.monitor.clone().unwrap());
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());
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.keys().cloned().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)
}
}

View file

@ -0,0 +1,52 @@
use anyhow::Result;
use eww_shared_util::{AttrName, VarName};
use simplexpr::dynval::DynVal;
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<WindowGeometry>,
pub id: String,
pub local_variables: HashMap<VarName, DynVal>,
pub monitor: Option<MonitorIdentifier>,
pub name: String,
pub resizable: bool,
pub stacking: WindowStacking,
}
impl WindowInitiator {
pub fn new(window_def: &WindowDefinition, args: &WindowArguments) -> Result<Self> {
let vars = args.get_local_window_variables(window_def)?;
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() };
Ok(WindowInitiator {
backend_options: window_def.backend_options.eval(&vars)?,
geometry,
id: args.instance_id.clone(),
monitor,
name: window_def.name.clone(),
resizable: window_def.eval_resizable(&vars)?,
stacking: window_def.eval_stacking(&vars)?,
local_variables: vars,
})
}
pub fn get_scoped_vars(&self) -> HashMap<AttrName, DynVal> {
self.local_variables.iter().map(|(k, v)| (AttrName::from(k.clone()), v.clone())).collect()
}
}

View file

@ -106,6 +106,14 @@ impl TryFrom<serde_json::Value> for DynVal {
} }
} }
impl From<Vec<DynVal>> for DynVal {
fn from(v: Vec<DynVal>) -> 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::<Vec<_>>();
DynVal(serde_json::to_string(&elements).unwrap(), span)
}
}
impl From<std::time::Duration> for DynVal { impl From<std::time::Duration> for DynVal {
fn from(d: std::time::Duration) -> Self { fn from(d: std::time::Duration) -> Self {
DynVal(format!("{}ms", d.as_millis()), Span::DUMMY) DynVal(format!("{}ms", d.as_millis()), Span::DUMMY)

View file

@ -3,7 +3,7 @@ use std::collections::HashMap;
use simplexpr::{dynval::FromDynVal, eval::EvalError, SimplExpr}; use simplexpr::{dynval::FromDynVal, eval::EvalError, SimplExpr};
use crate::{ use crate::{
error::DiagError, error::{DiagError, DiagResult},
parser::{ast::Ast, from_ast::FromAst}, parser::{ast::Ast, from_ast::FromAst},
}; };
use eww_shared_util::{AttrName, Span, Spanned}; use eww_shared_util::{AttrName, Span, Spanned};
@ -109,3 +109,20 @@ impl Attributes {
self.attrs.into_iter().map(|(k, v)| (v.key_span.to(v.value.span()), k)) self.attrs.into_iter().map(|(k, v)| (v.key_span.to(v.value.span()), k))
} }
} }
/// Specification of an argument to a widget or window
#[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<Self> {
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 })
}
}

View file

@ -1,19 +1,66 @@
use std::str::FromStr; use std::{collections::HashMap, str::FromStr};
use anyhow::Result; use anyhow::Result;
use simplexpr::{
dynval::{DynVal, FromDynVal},
eval::EvalError,
SimplExpr,
};
use crate::{ use crate::{
enum_parse, enum_parse,
error::DiagResult, error::DiagResult,
parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent}, 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 super::{attributes::Attributes, window_definition::EnumParseError};
use crate::error::{DiagError, DiagResultExt}; 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
/// Unevaluated form of [`BackendWindowOptions`]
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct BackendWindowOptionsDef {
pub wayland: WlBackendWindowOptionsDef,
pub x11: X11BackendWindowOptionsDef,
}
impl BackendWindowOptionsDef {
pub fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<BackendWindowOptions, Error> {
Ok(BackendWindowOptions { wayland: self.wayland.eval(local_variables)?, x11: self.x11.eval(local_variables)? })
}
pub fn from_attrs(attrs: &mut Attributes) -> DiagResult<Self> {
let struts = attrs.ast_optional("reserve")?;
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 = WlBackendWindowOptionsDef {
exclusive: attrs.ast_optional("exclusive")?,
focusable: attrs.ast_optional("focusable")?,
namespace: attrs.ast_optional("namespace")?,
};
Ok(Self { wayland, x11 })
}
}
/// Backend-specific options of a window that are backend /// Backend-specific options of a window that are backend
#[derive(Debug, Clone, serde::Serialize, PartialEq)] #[derive(Debug, Clone, serde::Serialize, PartialEq)]
pub struct BackendWindowOptions { pub struct BackendWindowOptions {
@ -21,25 +68,6 @@ pub struct BackendWindowOptions {
pub wayland: WlBackendWindowOptions, pub wayland: WlBackendWindowOptions,
} }
impl BackendWindowOptions {
pub fn from_attrs(attrs: &mut Attributes) -> DiagResult<Self> {
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 wayland = WlBackendWindowOptions {
exclusive: attrs.primitive_optional("exclusive")?.unwrap_or(false),
focusable: attrs.primitive_optional("focusable")?.unwrap_or(false),
namespace: attrs.primitive_optional("namespace")?,
};
Ok(Self { x11, wayland })
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub struct X11BackendWindowOptions { pub struct X11BackendWindowOptions {
pub wm_ignore: bool, pub wm_ignore: bool,
@ -48,6 +76,36 @@ pub struct X11BackendWindowOptions {
pub struts: X11StrutDefinition, pub struts: X11StrutDefinition,
} }
/// Unevaluated form of [`X11BackendWindowOptions`]
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct X11BackendWindowOptionsDef {
pub sticky: Option<SimplExpr>,
pub struts: Option<X11StrutDefinitionExpr>,
pub window_type: Option<SimplExpr>,
pub wm_ignore: Option<SimplExpr>,
}
impl X11BackendWindowOptionsDef {
fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<X11BackendWindowOptions, Error> {
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)] #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct WlBackendWindowOptions { pub struct WlBackendWindowOptions {
pub exclusive: bool, pub exclusive: bool,
@ -55,6 +113,38 @@ pub struct WlBackendWindowOptions {
pub namespace: Option<String>, pub namespace: Option<String>,
} }
/// Unevaluated form of [`WlBackendWindowOptions`]
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct WlBackendWindowOptionsDef {
pub exclusive: Option<SimplExpr>,
pub focusable: Option<SimplExpr>,
pub namespace: Option<SimplExpr>,
}
impl WlBackendWindowOptionsDef {
fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<WlBackendWindowOptions, EvalError> {
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<SimplExpr>,
default: bool,
local_variables: &HashMap<VarName, DynVal>,
) -> Result<bool, EvalError> {
Ok(match opt_expr {
Some(expr) => expr.eval(local_variables)?.as_bool()?,
None => default,
})
}
/// Window type of an x11 window /// Window type of an x11 window
#[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)] #[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)]
pub enum X11WindowType { pub enum X11WindowType {
@ -105,18 +195,37 @@ impl std::str::FromStr for Side {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize)] /// Unevaluated form of [`X11StrutDefinition`]
pub struct X11StrutDefinition { #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub side: Side, pub struct X11StrutDefinitionExpr {
pub dist: NumWithUnit, pub side: Option<SimplExpr>,
pub distance: SimplExpr,
} }
impl FromAstElementContent for X11StrutDefinition { impl X11StrutDefinitionExpr {
fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<X11StrutDefinition, Error> {
Ok(X11StrutDefinition {
side: match &self.side {
Some(expr) => Side::from_dynval(&expr.eval(local_variables)?)?,
None => Side::default(),
},
distance: NumWithUnit::from_dynval(&self.distance.eval(local_variables)?)?,
})
}
}
impl FromAstElementContent for X11StrutDefinitionExpr {
const ELEMENT_NAME: &'static str = "struts"; const ELEMENT_NAME: &'static str = "struts";
fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> { fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {
let mut attrs = iter.expect_key_values()?; 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")?; 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")?, distance: attrs.ast_required("distance")? })
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize)]
pub struct X11StrutDefinition {
pub side: Side,
pub distance: NumWithUnit,
}

View file

@ -1,6 +1,11 @@
use std::{convert::Infallible, fmt, str}; use std::{
convert::Infallible,
fmt,
str::{self, FromStr},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use simplexpr::dynval::{ConversionError, DynVal};
/// The type of the identifier used to select a monitor /// The type of the identifier used to select a monitor
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -12,11 +17,34 @@ pub enum MonitorIdentifier {
} }
impl MonitorIdentifier { impl MonitorIdentifier {
pub fn from_dynval(val: &DynVal) -> Result<Self, ConversionError> {
match val.as_json_array() {
Ok(arr) => Ok(MonitorIdentifier::List(
arr.iter().map(|x| MonitorIdentifier::from_dynval(&x.into())).collect::<Result<_, _>>()?,
)),
Err(_) => match val.as_i32() {
Ok(x) => Ok(MonitorIdentifier::Numeric(x)),
Err(_) => Ok(MonitorIdentifier::from_str(&val.as_string().unwrap()).unwrap()),
},
}
}
pub fn is_numeric(&self) -> bool { pub fn is_numeric(&self) -> bool {
matches!(self, Self::Numeric(_)) 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::<Vec<_>>().into(),
MonitorIdentifier::Numeric(n) => DynVal::from(*n),
MonitorIdentifier::Name(n) => DynVal::from(n.clone()),
MonitorIdentifier::Primary => DynVal::from("<primary>"),
}
}
}
impl fmt::Display for MonitorIdentifier { impl fmt::Display for MonitorIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {

View file

@ -41,6 +41,8 @@ Config(
window_definitions: { window_definitions: {
"some-window": WindowDefinition( "some-window": WindowDefinition(
name: "some-window", name: "some-window",
expected_args: [],
args_span: Span(18446744073709551615, 18446744073709551615, 18446744073709551615),
geometry: Some(WindowGeometry( geometry: Some(WindowGeometry(
anchor_point: AnchorPoint( anchor_point: AnchorPoint(
x: START, x: START,
@ -56,7 +58,7 @@ Config(
), ),
)), )),
stacking: Foreground, stacking: Foreground,
monitor_number: Some(12), monitor_number: Some(Literal(DynVal("12", Span(278, 280, 0)))),
widget: Basic(BasicWidgetUse( widget: Basic(BasicWidgetUse(
name: "bar", name: "bar",
attrs: Attributes( 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: { var_definitions: {
VarName("some_var"): VarDefinition( VarName("some_var"): VarDefinition(

View file

@ -33,13 +33,17 @@ impl Spanned for ValidationError {
} }
pub fn validate(config: &Config, additional_globals: Vec<VarName>) -> Result<(), ValidationError> { pub fn validate(config: &Config, additional_globals: Vec<VarName>) -> Result<(), ValidationError> {
let var_names = std::iter::empty() let var_names: HashSet<VarName> = std::iter::empty()
.chain(additional_globals.iter().cloned()) .chain(additional_globals.iter().cloned())
.chain(config.script_vars.keys().cloned()) .chain(config.script_vars.keys().cloned())
.chain(config.var_definitions.keys().cloned()) .chain(config.var_definitions.keys().cloned())
.collect(); .collect();
for window in config.window_definitions.values() { 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<VarName> = 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() { for def in config.widget_definitions.values() {
validate_widget_definition(&config.widget_definitions, &var_names, def)?; validate_widget_definition(&config.widget_definitions, &var_names, def)?;

View file

@ -8,25 +8,9 @@ use crate::{
from_ast::{FromAst, FromAstElementContent}, from_ast::{FromAst, FromAstElementContent},
}, },
}; };
use eww_shared_util::{AttrName, Span, Spanned}; use eww_shared_util::{Span, Spanned};
use super::widget_use::WidgetUse; use super::{attributes::AttrSpec, 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<Self> {
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 })
}
}
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct WidgetDefinition { pub struct WidgetDefinition {

View file

@ -1,4 +1,4 @@
use std::{fmt::Display, str::FromStr}; use std::{collections::HashMap, fmt::Display};
use crate::{ use crate::{
config::monitor::MonitorIdentifier, config::monitor::MonitorIdentifier,
@ -9,26 +9,67 @@ use crate::{
from_ast::{FromAst, FromAstElementContent}, from_ast::{FromAst, FromAstElementContent},
}, },
}; };
use eww_shared_util::Span; use eww_shared_util::{Span, VarName};
use simplexpr::{
dynval::{DynVal, FromDynVal},
eval::EvalError,
SimplExpr,
};
use super::{backend_window_options::BackendWindowOptions, widget_use::WidgetUse, window_geometry::WindowGeometry}; use super::{
attributes::AttrSpec, backend_window_options::BackendWindowOptionsDef, widget_use::WidgetUse,
window_geometry::WindowGeometryDef,
};
#[derive(Debug, Clone, serde::Serialize, PartialEq)] #[derive(Debug, thiserror::Error)]
pub struct WindowDefinition { pub enum WindowStackingConversionError {
pub name: String, #[error(transparent)]
pub geometry: Option<WindowGeometry>, EvalError(#[from] EvalError),
pub stacking: WindowStacking, #[error(transparent)]
pub monitor: Option<MonitorIdentifier>, EnumParseError(#[from] EnumParseError),
pub widget: WidgetUse,
pub resizable: bool,
pub backend_options: BackendWindowOptions,
} }
impl FromAst for MonitorIdentifier { #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
fn from_ast(x: Ast) -> DiagResult<Self> { pub struct WindowDefinition {
match x { pub name: String,
Ast::Array(_, x) => Ok(Self::List(x.into_iter().map(MonitorIdentifier::from_ast).collect::<DiagResult<_>>()?)), pub expected_args: Vec<AttrSpec>,
other => Ok(Self::from_str(&String::from_ast(other)?).unwrap()), pub args_span: Span,
pub geometry: Option<WindowGeometryDef>,
pub stacking: Option<SimplExpr>,
pub monitor: Option<SimplExpr>,
pub widget: WidgetUse,
pub resizable: Option<SimplExpr>,
pub backend_options: BackendWindowOptionsDef,
}
impl WindowDefinition {
/// Evaluate the `monitor` field of the window definition
pub fn eval_monitor(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<Option<MonitorIdentifier>, EvalError> {
Ok(match &self.monitor {
Some(monitor_expr) => Some(MonitorIdentifier::from_dynval(&monitor_expr.eval(local_variables)?)?),
None => None,
})
}
/// Evaluate the `resizable` field of the window definition
pub fn eval_resizable(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<bool, EvalError> {
Ok(match &self.resizable {
Some(expr) => expr.eval(local_variables)?.as_bool()?,
None => true,
})
}
/// Evaluate the `stacking` field of the window definition
pub fn eval_stacking(
&self,
local_variables: &HashMap<VarName, DynVal>,
) -> Result<WindowStacking, WindowStackingConversionError> {
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),
} }
} }
} }
@ -38,15 +79,17 @@ impl FromAstElementContent for WindowDefinition {
fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> { fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {
let (_, name) = iter.expect_symbol()?; 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::<DiagResult<_>>()?;
let mut attrs = iter.expect_key_values()?; let mut attrs = iter.expect_key_values()?;
let monitor = attrs.ast_optional::<MonitorIdentifier>("monitor")?; let monitor = attrs.ast_optional("monitor")?;
let resizable = attrs.primitive_optional("resizable")?.unwrap_or(true); let resizable = attrs.ast_optional("resizable")?;
let stacking = attrs.primitive_optional("stacking")?.unwrap_or(WindowStacking::Foreground); let stacking = attrs.ast_optional("stacking")?;
let geometry = attrs.ast_optional("geometry")?; 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)?; let widget = iter.expect_any().map_err(DiagError::from).and_then(WidgetUse::from_ast)?;
iter.expect_done()?; 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 })
} }
} }

View file

@ -1,14 +1,21 @@
use std::collections::HashMap;
use crate::{ use crate::{
enum_parse, enum_parse,
error::DiagResult, error::DiagResult,
format_diagnostic::ToDiagnostic, format_diagnostic::ToDiagnostic,
parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent}, parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent},
value::Coords, value::{coords, Coords, NumWithUnit},
}; };
use super::window_definition::EnumParseError; use super::window_definition::EnumParseError;
use eww_shared_util::Span; use eww_shared_util::{Span, VarName};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use simplexpr::{
dynval::{DynVal, FromDynVal},
eval::EvalError,
SimplExpr,
};
#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, strum::Display)] #[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, strum::Display)]
pub enum AnchorAlignment { pub enum AnchorAlignment {
@ -102,34 +109,86 @@ impl std::str::FromStr for AnchorPoint {
} }
} }
#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize)] /// Unevaluated variant of [`Coords`]
pub struct WindowGeometry { #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub anchor_point: AnchorPoint, pub struct CoordsDef {
pub offset: Coords, pub x: Option<SimplExpr>,
pub size: Coords, pub y: Option<SimplExpr>,
} }
impl FromAstElementContent for WindowGeometry { #[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<VarName, DynVal>) -> Result<Coords, Error> {
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<SimplExpr>,
local_variables: &HashMap<VarName, DynVal>,
) -> Result<NumWithUnit, Error> {
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<SimplExpr>,
pub offset: CoordsDef,
pub size: CoordsDef,
}
impl FromAstElementContent for WindowGeometryDef {
const ELEMENT_NAME: &'static str = "geometry"; const ELEMENT_NAME: &'static str = "geometry";
fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> { fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {
let mut attrs = iter.expect_key_values()?; let mut attrs = iter.expect_key_values()?;
iter.expect_done() 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()]))?; .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(), Ok(WindowGeometryDef {
size: Coords { anchor_point: attrs.ast_optional("anchor")?,
x: attrs.primitive_optional("width")?.unwrap_or_default(), size: CoordsDef { x: attrs.ast_optional("width")?, y: attrs.ast_optional("height")? },
y: attrs.primitive_optional("height")?.unwrap_or_default(), offset: CoordsDef { x: attrs.ast_optional("x")?, y: attrs.ast_optional("y")? },
},
offset: Coords {
x: attrs.primitive_optional("x")?.unwrap_or_default(),
y: attrs.primitive_optional("y")?.unwrap_or_default(),
},
}) })
} }
} }
impl WindowGeometryDef {
pub fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<WindowGeometry, Error> {
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 { impl WindowGeometry {
pub fn override_if_given(&self, anchor_point: Option<AnchorPoint>, offset: Option<Coords>, size: Option<Coords>) -> Self { pub fn override_if_given(&self, anchor_point: Option<AnchorPoint>, offset: Option<Coords>, size: Option<Coords>) -> Self {
WindowGeometry { WindowGeometry {

View file

@ -259,6 +259,112 @@ 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! Note that this is not all that efficient. Make sure to only use `literal` when necessary!
## 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 arguments and ids come in.
### 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.
```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.
### Window Arguments
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.
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]
:geometry (geometry
:x "0%"
:y "6px"
:width "100%"
: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:
```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.
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 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.
```bash
eww open-many my_primary_bar --arg my_primary_bar:screen=0
```
## Generating a list of widgets from JSON using `for` ## 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. 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.