diff options
| author | srdusr <[email protected]> | 2024-05-30 16:10:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-05-30 16:10:00 +0200 |
| commit | 1c175642d073689ca11b9252411ea5f8446007d0 (patch) | |
| tree | 00b8f9062cf1261d2d2642c059a14ff5790522e5 /crates/ctl/src | |
| parent | 3d3057ae384ef7389284af8988410889e99c6bb9 (diff) | |
| download | srdwm-1c175642d073689ca11b9252411ea5f8446007d0.tar.gz srdwm-1c175642d073689ca11b9252411ea5f8446007d0.zip | |
Fix decoration drift during animated transitions; checkpoint IPC/global-menu/output-management work
Border/titlebar decoration was built from Window.geometry (the animation's
final target) in both wayland backends' render loops, while sync_geometry
already draws a window's actual content at window_anims' interpolated rect
during any maximize/fullscreen/open-slide tween. Border and content read two
different rectangles for the whole transition, so the border visibly
detached from the window it was outlining - reported as "borders aren't
flush." Both udev.rs and winit.rs now read the same animated rect for
titlebar placement, border-strip placement, and the occlusion test against
later windows in stacking order. Verified: cargo build --workspace, cargo
clippy (0 new warnings), cargo test -p srdwm-core (111/111).
Also checkpoints substantial protocol/IPC work from prior sessions that had
accumulated uncommitted: gtk-shell1 support (gtk_shell.rs, the vendored
gtk-shell.xml, xwayland.rs's X11-side mirror) backing the global app menu;
zwlr_foreign_toplevel_manager_v1 (foreign_toplevel.rs) broadcasting distinct
maximized/minimized/fullscreen/activated state per window; output_management
(ext-output-management + layer-shell exclusive-zone reservation tracking);
workspace.rs and context_menu.rs; a Unix-socket IPC crate (platform/src/
ipc.rs) and a `srd` control-CLI crate (crates/ctl); xkb_config.rs; and a
theme module (core/src/theme.rs). A peer session working the AGS shell
concurrently verified several of these live against a running srdwm: the
global menu rendering a real app's File/Edit menu over gtk-shell1, and
foreign-toplevel correctly reporting maximized and fullscreen as independent,
non-simultaneous states with the geometry each implies (maximize stops at a
reserved top bar and past a dock; fullscreen reaches the true monitor edge).
Diffstat (limited to 'crates/ctl/src')
| -rw-r--r-- | crates/ctl/src/main.rs | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/crates/ctl/src/main.rs b/crates/ctl/src/main.rs new file mode 100644 index 0000000..2c3c3db --- /dev/null +++ b/crates/ctl/src/main.rs @@ -0,0 +1,126 @@ +//! Control CLI for srdwm's control socket (`crates/platform/src/ipc.rs`, +//! shared by every backend that has one). Deliberately dumb: builds one +//! request line from argv, writes it, reads the response(s), prints them. +//! All real logic - and all JSON encoding - lives server-side; this +//! stays a thin pipe so it never needs a JSON dependency of its own. +//! +//! Usage: +//! srd clients list windows, one JSON object +//! srd subscribe like `clients`, then one JSON +//! object per line forever, each time +//! the window list actually changes +//! srd dispatch toggle_visibility ID hide/show a window +//! srd dispatch focus ID +//! srd dispatch close ID +//! +//! The socket is Unix-domain; on platforms without one this always fails +//! cleanly rather than not building at all, since it's one binary in a +//! workspace that otherwise targets Windows and macOS too. + +#[cfg(unix)] +fn main() { + let args: Vec<String> = std::env::args().skip(1).collect(); + let request = match build_request(&args) { + Ok(r) => r, + Err(msg) => { + eprintln!("srd: {msg}"); + print_usage(); + std::process::exit(2); + } + }; + + let result = if args.first().map(String::as_str) == Some("subscribe") { unix::stream(&request) } else { unix::send(&request).map(|r| println!("{r}")) }; + if let Err(e) = result { + eprintln!("srd: {e}"); + std::process::exit(1); + } +} + +#[cfg(not(unix))] +fn main() { + eprintln!("srd: srdwm's control socket is Unix-only; nothing to connect to on this platform"); + std::process::exit(1); +} + +fn build_request(args: &[String]) -> Result<String, String> { + match args.first().map(String::as_str) { + Some("clients") => Ok(r#"{"cmd":"clients"}"#.to_string()), + Some("monitors") => Ok(r#"{"cmd":"monitors"}"#.to_string()), + Some("subscribe") => Ok(r#"{"cmd":"subscribe"}"#.to_string()), + Some("dispatch") => { + let action = args.get(1).ok_or("dispatch needs an action (toggle_visibility/focus/close)")?; + if !matches!(action.as_str(), "toggle_visibility" | "focus" | "close") { + return Err(format!("unknown dispatch action '{action}'")); + } + let id: u64 = args.get(2).ok_or("dispatch needs a window id")?.parse().map_err(|_| "window id must be a number".to_string())?; + Ok(format!(r#"{{"cmd":"{action}","id":{id}}}"#)) + } + _ => Err("expected 'clients', 'monitors', 'subscribe', or 'dispatch <action> <id>'".to_string()), + } +} + +fn print_usage() { + eprintln!("usage:"); + eprintln!(" srd clients"); + eprintln!(" srd monitors"); + eprintln!(" srd subscribe"); + eprintln!(" srd dispatch toggle_visibility <id>"); + eprintln!(" srd dispatch focus <id>"); + eprintln!(" srd dispatch close <id>"); +} + +#[cfg(unix)] +mod unix { + use std::io::{BufRead, BufReader, Write}; + use std::os::unix::net::UnixStream; + use std::path::PathBuf; + + /// `<display>` in `srdwm-<display>.sock` is whichever env var the + /// compositor itself used to name it: `WAYLAND_DISPLAY` (Wayland + /// backend) or `DISPLAY` (X11 backend) - see `srdwm_platform:: + /// IpcServer::bind`'s callers in `crates/wayland`/`crates/x11` for the + /// matching naming choice on the server side. Reuses `srdwm_platform:: + /// detect()` rather than re-deriving the same Wayland-vs-X11 decision + /// a second, potentially-drifting way here. + fn socket_path() -> Result<PathBuf, String> { + let dir = std::env::var_os("XDG_RUNTIME_DIR").map(PathBuf::from).ok_or("XDG_RUNTIME_DIR is not set")?; + let display = match srdwm_platform::detect() { + srdwm_platform::PlatformKind::X11 => std::env::var("DISPLAY").map_err(|_| "DISPLAY is not set".to_string())?, + _ => std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".to_string()), + }; + Ok(dir.join(format!("srdwm-{display}.sock"))) + } + + fn connect() -> Result<UnixStream, String> { + let path = socket_path()?; + UnixStream::connect(&path) + .map_err(|e| format!("can't reach srdwm's control socket at {} ({e}) - is srdwm running, and are WAYLAND_DISPLAY/DISPLAY set correctly?", path.display())) + } + + pub fn send(request: &str) -> Result<String, String> { + let mut stream = connect()?; + stream.write_all(request.as_bytes()).map_err(|e| e.to_string())?; + stream.write_all(b"\n").map_err(|e| e.to_string())?; + let mut line = String::new(); + BufReader::new(&stream).read_line(&mut line).map_err(|e| e.to_string())?; + Ok(line.trim_end().to_string()) + } + + /// `subscribe`'s connection: unlike `send`, this never closes on its + /// own - the server keeps it open and writes one more line every time + /// the window list changes, so this prints each one as it arrives + /// until the process is killed or the compositor closes the socket. + pub fn stream(request: &str) -> Result<(), String> { + let mut conn = connect()?; + conn.write_all(request.as_bytes()).map_err(|e| e.to_string())?; + conn.write_all(b"\n").map_err(|e| e.to_string())?; + let reader = BufReader::new(conn); + for line in reader.lines() { + match line { + Ok(line) => println!("{line}"), + Err(e) => return Err(e.to_string()), + } + } + Ok(()) + } +} |