Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add window arguments #431

Merged
merged 9 commits into from
Dec 20, 2023
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ All notable changes to eww will be listed here, starting at changes since versio

## [Unreleased]

### BREAKING CHANGES
- Remove `eww windows` command, replace with `eww active-windows` and `eww list-windows`

### Features
- Add `:namespace` window option
- Default to building with x11 and wayland support simultaneously
Expand Down Expand Up @@ -73,6 +76,7 @@ All notable changes to eww will be listed here, starting at changes since versio
- Add `:onaccept` to input field, add `:onclick` to eventbox
- Add `EWW_CMD`, `EWW_CONFIG_DIR`, `EWW_EXECUTABLE` magic variables
- Add `overlay` widget (By: viandoxdev)
- Add arguments option to `defwindow` (By: WilfSilver)

### Notable Internal changes
- Rework state management completely, now making local state and dynamic widget hierarchy changes possible.
Expand Down
188 changes: 117 additions & 71 deletions crates/eww/src/app.rs

Large diffs are not rendered by default.

80 changes: 40 additions & 40 deletions crates/eww/src/display_backend.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use yuck::config::window_definition::WindowDefinition;
use crate::window_initiator::WindowInitiator;

#[cfg(feature = "wayland")]
pub use platform_wayland::WaylandBackend;
Expand All @@ -8,26 +8,25 @@ pub use platform_x11::{set_xprops, X11Backend};

pub trait DisplayBackend: Send + Sync + 'static {
const IS_X11: bool;
fn initialize_window(window_def: &WindowDefinition, monitor: gdk::Rectangle) -> Option<gtk::Window>;

fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle) -> Option<gtk::Window>;
}

pub struct NoBackend;

impl DisplayBackend for NoBackend {
const IS_X11: bool = false;

fn initialize_window(_window_def: &WindowDefinition, _monitor: gdk::Rectangle) -> Option<gtk::Window> {
fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Rectangle) -> Option<gtk::Window> {
Some(gtk::Window::new(gtk::WindowType::Toplevel))
}
}

#[cfg(feature = "wayland")]
mod platform_wayland {
use crate::window_initiator::WindowInitiator;
use gtk::prelude::*;
use yuck::config::{
window_definition::{WindowDefinition, WindowStacking},
window_geometry::AnchorAlignment,
};
use yuck::config::{window_definition::WindowStacking, window_geometry::AnchorAlignment};

use super::DisplayBackend;

Expand All @@ -36,37 +35,37 @@ mod platform_wayland {
impl DisplayBackend for WaylandBackend {
const IS_X11: bool = false;

fn initialize_window(window_def: &WindowDefinition, monitor: gdk::Rectangle) -> Option<gtk::Window> {
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle) -> Option<gtk::Window> {
let window = gtk::Window::new(gtk::WindowType::Toplevel);
// Initialising a layer shell surface
gtk_layer_shell::init_for_window(&window);
// Sets the monitor where the surface is shown
if let Some(ident) = window_def.monitor.clone() {
if let Some(ident) = window_init.monitor.clone() {
let display = gdk::Display::default().expect("could not get default display");
if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) {
gtk_layer_shell::set_monitor(&window, &monitor);
} else {
return None;
}
};
window.set_resizable(window_def.resizable);
window.set_resizable(window_init.resizable);

// Sets the layer where the layer shell surface will spawn
match window_def.stacking {
match window_init.stacking {
WindowStacking::Foreground => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Top),
WindowStacking::Background => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Background),
WindowStacking::Bottom => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Bottom),
WindowStacking::Overlay => gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Overlay),
}

if let Some(namespace) = &window_def.backend_options.wayland.namespace {
if let Some(namespace) = &window_init.backend_options.wayland.namespace {
gtk_layer_shell::set_namespace(&window, namespace);
}

// Sets the keyboard interactivity
gtk_layer_shell::set_keyboard_interactivity(&window, window_def.backend_options.wayland.focusable);
gtk_layer_shell::set_keyboard_interactivity(&window, window_init.backend_options.wayland.focusable);

if let Some(geometry) = window_def.geometry {
if let Some(geometry) = window_init.geometry {
// Positioning surface
let mut top = false;
let mut left = false;
Expand Down Expand Up @@ -103,7 +102,7 @@ mod platform_wayland {
gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Top, yoffset);
}
}
if window_def.backend_options.wayland.exclusive {
if window_init.backend_options.wayland.exclusive {
gtk_layer_shell::auto_exclusive_zone_enable(&window);
}
Some(window)
Expand All @@ -113,7 +112,9 @@ mod platform_wayland {

#[cfg(feature = "x11")]
mod platform_x11 {
use crate::window_initiator::WindowInitiator;
use anyhow::{Context, Result};
use gdk::Monitor;
use gtk::{self, prelude::*};
use x11rb::protocol::xproto::ConnectionExt;

Expand All @@ -125,7 +126,7 @@ mod platform_x11 {
};
use yuck::config::{
backend_window_options::{Side, X11WindowType},
window_definition::{WindowDefinition, WindowStacking},
window_definition::WindowStacking,
};

use super::DisplayBackend;
Expand All @@ -134,14 +135,14 @@ mod platform_x11 {
impl DisplayBackend for X11Backend {
const IS_X11: bool = true;

fn initialize_window(window_def: &WindowDefinition, _monitor: gdk::Rectangle) -> Option<gtk::Window> {
fn initialize_window(window_init: &WindowInitiator, _monitor: gdk::Rectangle) -> Option<gtk::Window> {
let window_type =
if window_def.backend_options.x11.wm_ignore { gtk::WindowType::Popup } else { gtk::WindowType::Toplevel };
if window_init.backend_options.x11.wm_ignore { gtk::WindowType::Popup } else { gtk::WindowType::Toplevel };
let window = gtk::Window::new(window_type);
window.set_resizable(window_def.resizable);
window.set_keep_above(window_def.stacking == WindowStacking::Foreground);
window.set_keep_below(window_def.stacking == WindowStacking::Background);
if window_def.backend_options.x11.sticky {
window.set_resizable(window_init.resizable);
window.set_keep_above(window_init.stacking == WindowStacking::Foreground);
window.set_keep_below(window_init.stacking == WindowStacking::Background);
if window_init.backend_options.x11.sticky {
window.stick();
} else {
window.unstick();
Expand All @@ -150,9 +151,9 @@ mod platform_x11 {
}
}

pub fn set_xprops(window: &gtk::Window, monitor: gdk::Rectangle, window_def: &WindowDefinition) -> Result<()> {
pub fn set_xprops(window: &gtk::Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
let backend = X11BackendConnection::new()?;
backend.set_xprops_for(window, monitor, window_def)?;
backend.set_xprops_for(window, monitor, window_init)?;
Ok(())
}

Expand All @@ -170,35 +171,34 @@ mod platform_x11 {
Ok(X11BackendConnection { conn, root_window: screen.root, atoms })
}

fn set_xprops_for(
&self,
window: &gtk::Window,
monitor_rect: gdk::Rectangle,
window_def: &WindowDefinition,
) -> Result<()> {
fn set_xprops_for(&self, window: &gtk::Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
let monitor_rect = monitor.geometry();
let scale_factor = monitor.scale_factor() as u32;
let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?;
let win_id =
gdk_window.downcast_ref::<gdkx11::X11Window>().context("Failed to get x11 window for gtk window")?.xid() as u32;
let strut_def = window_def.backend_options.x11.struts;
let strut_def = window_init.backend_options.x11.struts;
let root_window_geometry = self.conn.get_geometry(self.root_window)?.reply()?;

let mon_end_x = (monitor_rect.x() + monitor_rect.width()) as u32 - 1u32;
let mon_end_y = (monitor_rect.y() + monitor_rect.height()) as u32 - 1u32;
let mon_x = scale_factor * monitor_rect.x() as u32;
let mon_y = scale_factor * monitor_rect.y() as u32;
let mon_end_x = scale_factor * (monitor_rect.x() + monitor_rect.width()) as u32 - 1u32;
let mon_end_y = scale_factor * (monitor_rect.y() + monitor_rect.height()) as u32 - 1u32;

let dist = match strut_def.side {
Side::Left | Side::Right => strut_def.dist.pixels_relative_to(monitor_rect.width()) as u32,
Side::Top | Side::Bottom => strut_def.dist.pixels_relative_to(monitor_rect.height()) as u32,
Side::Left | Side::Right => strut_def.distance.pixels_relative_to(monitor_rect.width()) as u32,
Side::Top | Side::Bottom => strut_def.distance.pixels_relative_to(monitor_rect.height()) as u32,
};

// don't question it,.....
// it's how the X gods want it to be.
// left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x
#[rustfmt::skip]
let strut_list: Vec<u8> = match strut_def.side {
Side::Left => vec![dist + monitor_rect.x() as u32, 0, 0, 0, monitor_rect.y() as u32, mon_end_y, 0, 0, 0, 0, 0, 0],
Side::Right => vec![0, root_window_geometry.width as u32 - mon_end_x + dist, 0, 0, 0, 0, monitor_rect.y() as u32, mon_end_y, 0, 0, 0, 0],
Side::Top => vec![0, 0, dist + monitor_rect.y() as u32, 0, 0, 0, 0, 0, monitor_rect.x() as u32, mon_end_x, 0, 0],
Side::Bottom => vec![0, 0, 0, root_window_geometry.height as u32 - mon_end_y + dist, 0, 0, 0, 0, 0, 0, monitor_rect.x() as u32, mon_end_x],
Side::Left => vec![dist + mon_x, 0, 0, 0, mon_x, mon_end_y, 0, 0, 0, 0, 0, 0],
Side::Right => vec![0, root_window_geometry.width as u32 - mon_end_x + dist, 0, 0, 0, 0, mon_x, mon_end_y, 0, 0, 0, 0],
Side::Top => vec![0, 0, dist + mon_y as u32, 0, 0, 0, 0, 0, mon_x, mon_end_x, 0, 0],
Side::Bottom => vec![0, 0, 0, root_window_geometry.height as u32 - mon_end_y + dist, 0, 0, 0, 0, 0, 0, mon_x as u32, mon_end_x],
// This should never happen but if it does the window will be anchored on the
// right of the screen
}.iter().flat_map(|x| x.to_le_bytes().to_vec()).collect();
Expand Down Expand Up @@ -233,7 +233,7 @@ mod platform_x11 {
win_id,
self.atoms._NET_WM_WINDOW_TYPE,
self.atoms.ATOM,
&[match window_def.backend_options.x11.window_type {
&[match window_init.backend_options.x11.window_type {
X11WindowType::Dock => self.atoms._NET_WM_WINDOW_TYPE_DOCK,
X11WindowType::Normal => self.atoms._NET_WM_WINDOW_TYPE_NORMAL,
X11WindowType::Dialog => self.atoms._NET_WM_WINDOW_TYPE_DIALOG,
Expand Down
3 changes: 3 additions & 0 deletions crates/eww/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#![feature(slice_concat_trait)]
#![feature(try_blocks)]
#![feature(hash_extract_if)]
#![feature(let_chains)]
#![allow(rustdoc::private_intra_doc_links)]

extern crate gtk;
Expand Down Expand Up @@ -36,6 +37,8 @@ mod server;
mod state;
mod util;
mod widgets;
mod window_arguments;
mod window_initiator;

fn main() {
let eww_binary_name = std::env::args().next().unwrap();
Expand Down
56 changes: 48 additions & 8 deletions crates/eww/src/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ pub enum ActionWithServer {
/// Name of the window you want to open.
window_name: String,

// The id of the window instance
#[arg(long)]
id: Option<String>,

/// The identifier of the monitor the window should open on
#[arg(long)]
screen: Option<MonitorIdentifier>,
Expand All @@ -124,13 +128,23 @@ pub enum ActionWithServer {
/// Automatically close the window after a specified amount of time, i.e.: 1s
#[arg(long, value_parser=parse_duration)]
duration: Option<std::time::Duration>,

/// Define a variable for the window, i.e.: `--arg "var_name=value"`
#[arg(long = "arg", value_parser = parse_var_update_arg)]
args: Option<Vec<(VarName, DynVal)>>,
},

/// Open multiple windows at once.
/// NOTE: This will in the future be part of eww open, and will then be removed.
#[command(name = "open-many")]
OpenMany {
windows: Vec<String>,
/// List the windows to open, optionally including their id, i.e.: `--window "window_name:window_id"`
#[arg(value_parser = parse_window_config_and_id)]
windows: Vec<(String, String)>,

/// Define a variable for the window, i.e.: `--arg "window_id:var_name=value"`
#[arg(long = "arg", value_parser = parse_window_id_args)]
args: Vec<(String, VarName, DynVal)>,

/// If a window is already open, close it instead
#[arg(long = "toggle")]
Expand Down Expand Up @@ -165,9 +179,13 @@ pub enum ActionWithServer {
#[command(name = "get")]
GetVar { name: String },

/// Print the names of all configured windows. Windows with a * in front of them are currently opened.
#[command(name = "windows")]
ShowWindows,
/// List the names of active windows
#[command(name = "list-windows")]
ListWindows,

/// Show active window IDs, formatted linewise `<window_id>: <window_name>`
#[command(name = "active-windows")]
ListActiveWindows,

/// Print out the widget structure as seen by eww.
///
Expand Down Expand Up @@ -195,6 +213,25 @@ impl From<RawOpt> for Opt {
}
}

/// Parse a window-name:window-id pair of the form `name:id` or `name` into a tuple of `(name, id)`.
fn parse_window_config_and_id(s: &str) -> Result<(String, String)> {
let (name, id) = s.split_once(':').unwrap_or((s, s));

Ok((name.to_string(), id.to_string()))
}

/// Parse a window-id specific variable value declaration with the syntax `window-id:variable_name="new_value"`
/// into a tuple of `(id, variable_name, new_value)`.
fn parse_window_id_args(s: &str) -> Result<(String, VarName, DynVal)> {
// Parse the = first so we know if an id has not been given
let (name, value) = parse_var_update_arg(s)?;

let (id, var_name) = name.0.split_once(':').unwrap_or(("", &name.0));

Ok((id.to_string(), var_name.into(), value))
}

/// Split the input string at `=`, parsing the value into a [`DynVal`].
fn parse_var_update_arg(s: &str) -> Result<(VarName, DynVal)> {
let (name, value) = s
.split_once('=')
Expand All @@ -219,26 +256,29 @@ impl ActionWithServer {
let _ = send.send(DaemonResponse::Success("pong".to_owned()));
return (app::DaemonCommand::NoOp, Some(recv));
}
ActionWithServer::OpenMany { windows, should_toggle } => {
return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, should_toggle, sender });
ActionWithServer::OpenMany { windows, args, should_toggle } => {
return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, args, should_toggle, sender });
}
ActionWithServer::OpenWindow { window_name, pos, size, screen, anchor, should_toggle, duration } => {
ActionWithServer::OpenWindow { window_name, id, pos, size, screen, anchor, should_toggle, duration, args } => {
return with_response_channel(|sender| app::DaemonCommand::OpenWindow {
window_name,
instance_id: id,
pos,
size,
anchor,
screen,
should_toggle,
duration,
sender,
args,
})
}
ActionWithServer::CloseWindows { windows } => {
return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, sender });
}
ActionWithServer::Reload => return with_response_channel(app::DaemonCommand::ReloadConfigAndCss),
ActionWithServer::ShowWindows => return with_response_channel(app::DaemonCommand::PrintWindows),
ActionWithServer::ListWindows => return with_response_channel(app::DaemonCommand::ListWindows),
ActionWithServer::ListActiveWindows => return with_response_channel(app::DaemonCommand::ListActiveWindows),
ActionWithServer::ShowState { all } => {
return with_response_channel(|sender| app::DaemonCommand::PrintState { all, sender })
}
Expand Down
1 change: 1 addition & 0 deletions crates/eww/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub fn initialize_server<B: DisplayBackend>(
eww_config,
open_windows: HashMap::new(),
failed_windows: HashSet::new(),
instance_id_to_args: HashMap::new(),
css_provider: gtk::CssProvider::new(),
script_var_handler,
app_evt_send: ui_send.clone(),
Expand Down
Loading
Loading