srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/platform/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/platform/src')
-rw-r--r--crates/platform/src/ipc/dispatch.rs45
-rw-r--r--crates/platform/src/ipc/tests.rs86
-rw-r--r--crates/platform/src/ipc/types.rs48
3 files changed, 179 insertions, 0 deletions
diff --git a/crates/platform/src/ipc/dispatch.rs b/crates/platform/src/ipc/dispatch.rs
index 918b7e0..3dcca57 100644
--- a/crates/platform/src/ipc/dispatch.rs
+++ b/crates/platform/src/ipc/dispatch.rs
@@ -28,6 +28,14 @@ pub(crate) fn handle_request(line: &[u8], wm: &std::rc::Rc<std::cell::RefCell<Wi
reading_mode: wm.color_filter == srdwm_core::ColorFilter::ReadingMode,
phone_mode: wm.phone_mode,
multi_cursor: wm.multi_cursor_enabled,
+ border_width: wm.theme.default_border_width,
+ border_color: srdwm_core::format_hex_color(wm.theme.default_border_color),
+ corner_radius: wm.theme.default_corner_radius,
+ decoration_mode_server: wm.theme.default_decorated,
+ gap_inner: wm.tiling.gap_inner,
+ gap_outer: wm.tiling.gap_outer,
+ master_ratio: wm.tiling.master_ratio,
+ master_count: wm.tiling.master_count,
};
(serde_json::to_vec(&settings).unwrap_or_default(), false)
}
@@ -127,6 +135,14 @@ pub(crate) fn handle_request(line: &[u8], wm: &std::rc::Rc<std::cell::RefCell<Wi
wm.borrow_mut().request_pin_input(pid as i32, id);
(ok(), true)
}
+ // `{"cmd":"pinned_inputs"}` - every pid currently pinned and which
+ // window, read from `WindowManager::all_pinned_windows` (the
+ // backend's own confirmation that a `pin_input` request was
+ // genuinely applied, not the one-shot request queue itself).
+ "pinned_inputs" => {
+ let pinned: Vec<PinnedInputInfo> = wm.borrow().all_pinned_windows().map(|(pid, id)| PinnedInputInfo { pid, id }).collect();
+ (serde_json::to_vec(&PinnedInputsResponse { pinned }).unwrap_or_default(), false)
+ }
// `{"cmd":"create_fake_monitor","name":<string>,"width":<u32>,
// "height":<u32>}` - a fully virtual `wl_output` with no real
// hardware behind it, applied by whichever backend owns real
@@ -498,6 +514,35 @@ fn handle_set(req: &serde_json::Value, wm: &std::rc::Rc<std::cell::RefCell<Windo
wm.borrow_mut().tiling.gap_outer = v as u32;
(ok(), true)
}
+ // `srd set master_ratio <0.0..1.0>` / `srd set master_count <n>` --
+ // the two `TilingConfig` fields a resize-drag on the master/stack
+ // boundary (`WindowManager::adjust_master_ratio_for_drag`) already
+ // live-adjusts interactively; this is the same thing for a
+ // keybinding or script instead of a mouse drag, e.g. dwm's
+ // `mod+h`/`mod+l` grow/shrink-master or `mod+i`/`mod+d` add/remove-
+ // a-master-window conventions. Unlike `gap_inner`/`gap_outer`
+ // above, which accept "takes effect whenever the workspace next
+ // re-arranges anyway" (a cosmetic preference under no time
+ // pressure), this re-arranges the current workspace immediately --
+ // a keybind pressed to grow the master column is expected to show
+ // the result at once, the same instant feedback the drag path
+ // already gives.
+ "master_ratio" => {
+ let Some(v) = value.and_then(|v| v.as_f64()) else { return (err("master_ratio needs a numeric value"), false) };
+ let mut wm = wm.borrow_mut();
+ wm.tiling.master_ratio = (v as f32).clamp(0.1, 0.9);
+ let current = wm.current_workspace();
+ wm.arrange_workspace(current);
+ (ok(), true)
+ }
+ "master_count" => {
+ let Some(v) = value.and_then(|v| v.as_u64()) else { return (err("master_count needs a numeric value"), false) };
+ let mut wm = wm.borrow_mut();
+ wm.tiling.master_count = (v as usize).max(1);
+ let current = wm.current_workspace();
+ wm.arrange_workspace(current);
+ (ok(), true)
+ }
"shadows" => {
let Some(v) = value.and_then(|v| v.as_bool()) else { return (err("shadows needs a boolean value"), false) };
wm.borrow_mut().shadows_enabled = v;
diff --git a/crates/platform/src/ipc/tests.rs b/crates/platform/src/ipc/tests.rs
index 109a9ec..13ab4c2 100644
--- a/crates/platform/src/ipc/tests.rs
+++ b/crates/platform/src/ipc/tests.rs
@@ -735,3 +735,89 @@ fn a_oneshot_clients_request_still_closes_the_connection_as_before() {
Err(e) => assert_eq!(e.kind(), ErrorKind::WouldBlock),
}
}
+
+#[test]
+fn settings_reports_the_readback_fields_flagged_as_missing() {
+ // Confirms the whole batch at once rather than one test per field --
+ // these were all added together for the same reason (a settings
+ // panel could set any of them blind but never read the current value
+ // back).
+ let dir = tempfile::tempdir().unwrap();
+ let mut server = IpcServer::bind_in(dir.path(), "test").unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+
+ let mut client = UnixStream::connect(&server.path).unwrap();
+ client.write_all(b"{\"cmd\":\"settings\"}\n").unwrap();
+ server.poll(&wm);
+ let line = read_line(&mut std::io::BufReader::new(client));
+ let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
+ for field in ["border_width", "border_color", "corner_radius", "decoration_mode_server", "gap_inner", "gap_outer", "master_ratio", "master_count"] {
+ assert!(parsed.get(field).is_some(), "settings response is missing '{field}'");
+ }
+ assert_eq!(parsed["border_color"].as_str().unwrap().chars().next(), Some('#'), "border_color must be a hex string, matching what srd set border_color itself accepts");
+}
+
+#[test]
+fn set_master_ratio_is_reflected_immediately_by_settings() {
+ let dir = tempfile::tempdir().unwrap();
+ let mut server = IpcServer::bind_in(dir.path(), "test").unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ wm.borrow_mut().set_monitors(vec![srdwm_core::Monitor::new(0, "eDP-1", srdwm_core::Rect::new(0, 0, 1920, 1080))]);
+
+ let mut set_client = UnixStream::connect(&server.path).unwrap();
+ set_client.write_all(b"{\"cmd\":\"set\",\"key\":\"master_ratio\",\"value\":0.7}\n").unwrap();
+ server.poll(&wm);
+ let _ = read_line(&mut std::io::BufReader::new(set_client));
+
+ let mut settings_client = UnixStream::connect(&server.path).unwrap();
+ settings_client.write_all(b"{\"cmd\":\"settings\"}\n").unwrap();
+ server.poll(&wm);
+ let line = read_line(&mut std::io::BufReader::new(settings_client));
+ assert!(line.contains(r#""master_ratio":0.7"#));
+}
+
+#[test]
+fn set_master_ratio_clamps_to_a_sane_range() {
+ let dir = tempfile::tempdir().unwrap();
+ let mut server = IpcServer::bind_in(dir.path(), "test").unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+
+ let mut client = UnixStream::connect(&server.path).unwrap();
+ client.write_all(b"{\"cmd\":\"set\",\"key\":\"master_ratio\",\"value\":1.5}\n").unwrap();
+ server.poll(&wm);
+ let _ = read_line(&mut std::io::BufReader::new(client));
+
+ assert_eq!(wm.borrow().tiling.master_ratio, 0.9, "a value past the sane range must clamp, not be accepted verbatim");
+}
+
+#[test]
+fn pinned_inputs_lists_nothing_before_any_pin_is_applied() {
+ let dir = tempfile::tempdir().unwrap();
+ let mut server = IpcServer::bind_in(dir.path(), "test").unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+
+ let mut client = UnixStream::connect(&server.path).unwrap();
+ client.write_all(b"{\"cmd\":\"pinned_inputs\"}\n").unwrap();
+ server.poll(&wm);
+ let line = read_line(&mut std::io::BufReader::new(client));
+ assert_eq!(line.trim_end(), r#"{"pinned":[]}"#);
+}
+
+#[test]
+fn pinned_inputs_reports_a_pin_once_the_backend_has_applied_it() {
+ // `pin_input` only ever queues a *request* - `WindowManager::
+ // set_pinned_window` is the backend's own confirmation that it was
+ // genuinely applied, called directly here to simulate that (the real
+ // caller is `CompState::set_virtual_pointer_pin` in the wayland
+ // crate, unreachable from a platform-crate test).
+ let dir = tempfile::tempdir().unwrap();
+ let mut server = IpcServer::bind_in(dir.path(), "test").unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ wm.borrow_mut().set_pinned_window(12345, Some(7));
+
+ let mut client = UnixStream::connect(&server.path).unwrap();
+ client.write_all(b"{\"cmd\":\"pinned_inputs\"}\n").unwrap();
+ server.poll(&wm);
+ let line = read_line(&mut std::io::BufReader::new(client));
+ assert_eq!(line.trim_end(), r#"{"pinned":[{"pid":12345,"id":7}]}"#);
+}
diff --git a/crates/platform/src/ipc/types.rs b/crates/platform/src/ipc/types.rs
index df11592..24e3148 100644
--- a/crates/platform/src/ipc/types.rs
+++ b/crates/platform/src/ipc/types.rs
@@ -127,6 +127,26 @@ pub(crate) struct WorkspacesResponse {
pub(crate) workspaces: Vec<WorkspaceInfo>,
}
+/// One `pid`/pinned-window pair - `WindowManager::all_pinned_windows`'s
+/// own doc comment. `id` matches the plain `WindowId` every other
+/// dispatch already reads/writes, not a separate type.
+#[derive(Serialize)]
+pub(crate) struct PinnedInputInfo {
+ pub(crate) pid: i32,
+ pub(crate) id: WindowId,
+}
+
+/// `"pinned_inputs"`'s one-shot reply - every pid Multi-cursor Phase 2
+/// (`srd dispatch pin input`) currently has pinned to a window, and which
+/// one. Added because pinning had no readback at all: a caller could ask
+/// to pin a window blind, but never confirm the pin actually took, or
+/// list what's pinned right now without already knowing which pids to
+/// ask about.
+#[derive(Serialize)]
+pub(crate) struct PinnedInputsResponse {
+ pub(crate) pinned: Vec<PinnedInputInfo>,
+}
+
/// `"settings"`'s one-shot reply - the live-settable toggles `"set"`
/// accepts, so a migrated toggle script (night-light, reading-mode,
/// hypr-performance-profile) can read current state back instead of
@@ -150,6 +170,34 @@ pub(crate) struct SettingsResponse {
pub(crate) phone_mode: bool,
/// `WindowManager::multi_cursor_enabled`'s own doc comment.
pub(crate) multi_cursor: bool,
+ /// The theme/tiling values `srd set` can already change live
+ /// (`border_width`, `border_color`, `corner_radius`, `decoration_
+ /// mode`, `gap_inner`, `gap_outer`, `master_ratio`, `master_count`)
+ /// had no way to read the *current* value back at all - a settings
+ /// panel could set any of these blind, but not honestly show its own
+ /// control's starting position, or confirm a set actually took.
+ /// Flagged directly by the AGS peer session as the common shape behind
+ /// several separate gaps at once: "a control whose value cannot be
+ /// read back is a control that lies on every restart."
+ pub(crate) border_width: u32,
+ /// `#rrggbb`, matching the exact string shape `srd set border_color`
+ /// itself accepts (`srdwm_core::parse_hex_color`'s own format) - a
+ /// caller can feed this straight back into another `set` unchanged.
+ pub(crate) border_color: String,
+ pub(crate) corner_radius: u32,
+ /// `true` when new windows default to a server-drawn titlebar
+ /// (`general.decoration_mode`/`srd set decoration_mode`'s own "server"
+ /// value), `false` for "client" (CSD-only default).
+ pub(crate) decoration_mode_server: bool,
+ pub(crate) gap_inner: u32,
+ pub(crate) gap_outer: u32,
+ /// `TilingConfig::master_ratio`/`master_count` - see `WindowManager::
+ /// adjust_master_ratio_for_drag`'s own doc comment for the live
+ /// interactive path (a resize-drag on the master/stack boundary) that
+ /// also mutates this, in addition to `srd set master_ratio`/
+ /// `master_count`.
+ pub(crate) master_ratio: f32,
+ pub(crate) master_count: usize,
}
/// `"keyboard_layout"`'s one-shot reply shape - the active XKB layout's