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]
### 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
@ -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 `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.

View file

@ -7,15 +7,18 @@ use crate::{
paths::EwwPaths,
script_var_handler::ScriptVarHandlerHandle,
state::scope_graph::{ScopeGraph, ScopeIndex},
window_arguments::WindowArguments,
window_initiator::WindowInitiator,
*,
};
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;
use simplexpr::dynval::DynVal;
use simplexpr::{dynval::DynVal, SimplExpr};
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
@ -26,7 +29,6 @@ use yuck::{
config::{
monitor::MonitorIdentifier,
script_var_definition::ScriptVarDefinition,
window_definition::WindowDefinition,
window_geometry::{AnchorPoint, WindowGeometry},
},
error::DiagError,
@ -44,12 +46,14 @@ pub enum DaemonCommand {
ReloadConfigAndCss(DaemonResponseSender),
OpenInspector,
OpenMany {
windows: Vec<String>,
windows: Vec<(String, String)>,
args: Vec<(String, VarName, DynVal)>,
should_toggle: bool,
sender: DaemonResponseSender,
},
OpenWindow {
window_name: String,
instance_id: Option<String>,
pos: Option<Coords>,
size: Option<Coords>,
anchor: Option<AnchorPoint>,
@ -57,6 +61,7 @@ pub enum DaemonCommand {
should_toggle: bool,
duration: Option<std::time::Duration>,
sender: DaemonResponseSender,
args: Option<Vec<(VarName, DynVal)>>,
},
CloseWindows {
windows: Vec<String>,
@ -74,12 +79,17 @@ pub enum DaemonCommand {
},
PrintDebug(DaemonResponseSender),
PrintGraph(DaemonResponseSender),
PrintWindows(DaemonResponseSender),
ListWindows(DaemonResponseSender),
ListActiveWindows(DaemonResponseSender),
}
/// 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,
pub gtk_window: gtk::Window,
@ -104,8 +114,9 @@ pub struct App<B> {
pub display_backend: B,
pub scope_graph: Rc<RefCell<ScopeGraph>>,
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 instance_id_to_args: HashMap<String, WindowArguments>,
/// Window names that are supposed to be open, but failed.
/// When reloading the config, these should be opened again.
pub failed_windows: HashSet<String>,
@ -128,6 +139,7 @@ impl<B> std::fmt::Debug for App<B> {
.field("eww_config", &self.eww_config)
.field("open_windows", &self.open_windows)
.field("failed_windows", &self.failed_windows)
.field("window_arguments", &self.instance_id_to_args)
.field("paths", &self.paths)
.finish()
}
@ -178,14 +190,25 @@ impl<B: DisplayBackend> App<B> {
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 = 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);
@ -193,6 +216,7 @@ impl<B: DisplayBackend> App<B> {
}
DaemonCommand::OpenWindow {
window_name,
instance_id,
pos,
size,
anchor,
@ -200,13 +224,27 @@ impl<B: DisplayBackend> App<B> {
should_toggle,
duration,
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 {
self.close_window(&window_name)
self.close_window(&instance_id)
} 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)?;
}
DaemonCommand::CloseWindows { windows, sender } => {
@ -233,16 +271,12 @@ impl<B: DisplayBackend> App<B> {
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) => {
@ -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
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 with id '{instance_id}', but no such window was open"))?;
let scope_index = eww_window.scope_index;
eww_window.close();
@ -324,52 +358,54 @@ impl<B: DisplayBackend> App<B> {
self.script_var_handler.stop_for_variable(unused_var.clone());
}
self.instance_id_to_args.remove(instance_id);
Ok(())
}
fn open_window(
&mut self,
window_name: &str,
pos: Option<Coords>,
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);
fn open_window(&mut self, window_args: &WindowArguments) -> Result<()> {
let instance_id = &window_args.instance_id;
self.failed_windows.remove(instance_id);
log::info!("Opening window {} as '{}'", window_args.window_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.instance_id_to_args.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.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");
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 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(
window_name.to_string(),
instance_id.to_string(),
Some(root_index),
root_index,
HashMap::new(),
scoped_vars_literal,
)?;
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::<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);
// 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({
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 +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(());
}
}
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 +484,13 @@ impl<B: DisplayBackend> App<B> {
self.eww_config = config;
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();
for window_name in &window_names {
self.open_window(window_name, None, None, None, None, None)?;
for instance_id in &open_window_ids {
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(())
}
@ -480,19 +519,20 @@ impl<B: DisplayBackend> App<B> {
}
fn initialize_window<B: DisplayBackend>(
monitor_geometry: gdk::Rectangle,
window_init: &WindowInitiator,
monitor: Monitor,
root_widget: gtk::Widget,
window_def: WindowDefinition,
window_scope: ScopeIndex,
) -> Result<EwwWindow> {
let window = B::initialize_window(&window_def, monitor_geometry)
.with_context(|| format!("monitor {} is unavailable", window_def.monitor.clone().unwrap()))?;
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()))?;
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 +551,27 @@ fn initialize_window<B: DisplayBackend>(
#[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, 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.
@ -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
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 monitor = match identifier {
Some(ident) => {
@ -575,7 +621,7 @@ fn get_monitor_geometry(identifier: Option<MonitorIdentifier>) -> Result<gdk::Re
.primary_monitor()
.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.

View file

@ -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<gtk::Window>;
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle) -> Option<gtk::Window>;
}
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<gtk::Window> {
fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Rectangle) -> Option<gtk::Window> {
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<gtk::Window> {
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle) -> Option<gtk::Window> {
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,7 +112,9 @@ mod platform_wayland {
#[cfg(feature = "x11")]
mod platform_x11 {
use crate::window_initiator::WindowInitiator;
use anyhow::{Context, Result};
use gdk::Monitor;
use gtk::{self, prelude::*};
use x11rb::protocol::xproto::ConnectionExt;
@ -125,7 +126,7 @@ mod platform_x11 {
};
use yuck::config::{
backend_window_options::{Side, X11WindowType},
window_definition::{WindowDefinition, WindowStacking},
window_definition::WindowStacking,
};
use super::DisplayBackend;
@ -134,14 +135,14 @@ mod platform_x11 {
impl DisplayBackend for X11Backend {
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 =
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 +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()?;
backend.set_xprops_for(window, monitor, window_def)?;
backend.set_xprops_for(window, monitor, window_init)?;
Ok(())
}
@ -170,24 +171,23 @@ mod platform_x11 {
Ok(X11BackendConnection { conn, root_window: screen.root, atoms })
}
fn set_xprops_for(
&self,
window: &gtk::Window,
monitor_rect: gdk::Rectangle,
window_def: &WindowDefinition,
) -> Result<()> {
fn set_xprops_for(&self, window: &gtk::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")?;
let win_id =
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 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,
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,.....
@ -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
#[rustfmt::skip]
let strut_list: Vec<u8> = match strut_def.side {
Side::Left => vec![dist + monitor_rect.x() as u32, 0, 0, 0, monitor_rect.y() as u32, mon_end_y, 0, 0, 0, 0, 0, 0],
Side::Right => vec![0, root_window_geometry.width as u32 - mon_end_x + dist, 0, 0, 0, 0, monitor_rect.y() as u32, mon_end_y, 0, 0, 0, 0],
Side::Top => vec![0, 0, dist + monitor_rect.y() as u32, 0, 0, 0, 0, 0, monitor_rect.x() as u32, mon_end_x, 0, 0],
Side::Bottom => vec![0, 0, 0, root_window_geometry.height as u32 - mon_end_y + dist, 0, 0, 0, 0, 0, 0, monitor_rect.x() as u32, mon_end_x],
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();
@ -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,

View file

@ -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;
@ -36,6 +37,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();

View file

@ -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<String>,
/// The identifier of the monitor the window should open on
#[arg(long)]
screen: Option<MonitorIdentifier>,
@ -124,13 +128,23 @@ pub enum ActionWithServer {
/// Automatically close the window after a specified amount of time, i.e.: 1s
#[arg(long, value_parser=parse_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.
/// NOTE: This will in the future be part of eww open, and will then be removed.
#[command(name = "open-many")]
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
#[arg(long = "toggle")]
@ -165,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 `<window_id>: <window_name>`
#[command(name = "active-windows")]
ListActiveWindows,
/// 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)> {
let (name, value) = s
.split_once('=')
@ -219,12 +256,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, 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, args } => {
return with_response_channel(|sender| app::DaemonCommand::OpenWindow {
window_name,
instance_id: id,
pos,
size,
anchor,
@ -232,13 +270,15 @@ impl ActionWithServer {
should_toggle,
duration,
sender,
args,
})
}
ActionWithServer::CloseWindows { windows } => {
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 })
}

View file

@ -81,6 +81,7 @@ pub fn initialize_server<B: DisplayBackend>(
eww_config,
open_windows: HashMap::new(),
failed_windows: HashSet::new(),
instance_id_to_args: HashMap::new(),
css_provider: gtk::CssProvider::new(),
script_var_handler,
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 {
fn from(d: std::time::Duration) -> Self {
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 crate::{
error::DiagError,
error::{DiagError, DiagResult},
parser::{ast::Ast, from_ast::FromAst},
};
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))
}
}
/// 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 simplexpr::{
dynval::{DynVal, FromDynVal},
eval::EvalError,
SimplExpr,
};
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
/// 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
#[derive(Debug, Clone, serde::Serialize, PartialEq)]
pub struct BackendWindowOptions {
@ -21,25 +68,6 @@ pub struct BackendWindowOptions {
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)]
pub struct X11BackendWindowOptions {
pub wm_ignore: bool,
@ -48,6 +76,36 @@ pub struct X11BackendWindowOptions {
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)]
pub struct WlBackendWindowOptions {
pub exclusive: bool,
@ -55,6 +113,38 @@ pub struct WlBackendWindowOptions {
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
#[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)]
pub enum X11WindowType {
@ -105,18 +195,37 @@ impl std::str::FromStr for Side {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize)]
pub struct X11StrutDefinition {
pub side: Side,
pub dist: NumWithUnit,
/// Unevaluated form of [`X11StrutDefinition`]
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct X11StrutDefinitionExpr {
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";
fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {
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")?, 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 simplexpr::dynval::{ConversionError, DynVal};
/// The type of the identifier used to select a monitor
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -12,11 +17,34 @@ pub enum 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 {
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 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {

View file

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

View file

@ -33,13 +33,17 @@ impl Spanned for 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(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<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() {
validate_widget_definition(&config.widget_definitions, &var_names, def)?;

View file

@ -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<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 })
}
}
use super::{attributes::AttrSpec, widget_use::WidgetUse};
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct WidgetDefinition {

View file

@ -1,4 +1,4 @@
use std::{fmt::Display, str::FromStr};
use std::{collections::HashMap, fmt::Display};
use crate::{
config::monitor::MonitorIdentifier,
@ -9,26 +9,67 @@ use crate::{
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)]
pub struct WindowDefinition {
pub name: String,
pub geometry: Option<WindowGeometry>,
pub stacking: WindowStacking,
pub monitor: Option<MonitorIdentifier>,
pub widget: WidgetUse,
pub resizable: bool,
pub backend_options: BackendWindowOptions,
#[derive(Debug, thiserror::Error)]
pub enum WindowStackingConversionError {
#[error(transparent)]
EvalError(#[from] EvalError),
#[error(transparent)]
EnumParseError(#[from] EnumParseError),
}
impl FromAst for MonitorIdentifier {
fn from_ast(x: Ast) -> DiagResult<Self> {
match x {
Ast::Array(_, x) => Ok(Self::List(x.into_iter().map(MonitorIdentifier::from_ast).collect::<DiagResult<_>>()?)),
other => Ok(Self::from_str(&String::from_ast(other)?).unwrap()),
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct WindowDefinition {
pub name: String,
pub expected_args: Vec<AttrSpec>,
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> {
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 monitor = attrs.ast_optional::<MonitorIdentifier>("monitor")?;
let resizable = attrs.primitive_optional("resizable")?.unwrap_or(true);
let stacking = attrs.primitive_optional("stacking")?.unwrap_or(WindowStacking::Foreground);
let monitor = attrs.ast_optional("monitor")?;
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, 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::{
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::{
dynval::{DynVal, FromDynVal},
eval::EvalError,
SimplExpr,
};
#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, strum::Display)]
pub enum AnchorAlignment {
@ -102,34 +109,86 @@ 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,
/// Unevaluated variant of [`Coords`]
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct CoordsDef {
pub x: Option<SimplExpr>,
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";
fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {
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(),
},
offset: Coords {
x: attrs.primitive_optional("x")?.unwrap_or_default(),
y: attrs.primitive_optional("y")?.unwrap_or_default(),
},
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")? },
})
}
}
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 {
pub fn override_if_given(&self, anchor_point: Option<AnchorPoint>, offset: Option<Coords>, size: Option<Coords>) -> Self {
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!
## 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`
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.