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:
parent
4f1f853b5f
commit
65d622c81f
18 changed files with 863 additions and 210 deletions
|
@ -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.
|
||||||
|
|
|
@ -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: >k::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.
|
||||||
|
|
|
@ -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: >k::Window, monitor: gdk::Rectangle, window_def: &WindowDefinition) -> Result<()> {
|
pub fn set_xprops(window: >k::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: >k::Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
|
||||||
&self,
|
let monitor_rect = monitor.geometry();
|
||||||
window: >k::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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
90
crates/eww/src/window_arguments.rs
Normal file
90
crates/eww/src/window_arguments.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
52
crates/eww/src/window_initiator.rs
Normal file
52
crates/eww/src/window_initiator.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Add table
Reference in a new issue