diff options
| author | srdusr <[email protected]> | 2024-07-31 16:22:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-07-31 16:22:00 +0200 |
| commit | c180bfc34e4c74ba7cac9e0220e9c172c239fe71 (patch) | |
| tree | 45550c7eb7ae819a9c3a415acb9cfa5be1cfd03b /crates/x11/src/platform/window.rs | |
| parent | 38683f069a039f9da6d7ddc64167850560fd07ed (diff) | |
| download | srdwm-c180bfc34e4c74ba7cac9e0220e9c172c239fe71.tar.gz srdwm-c180bfc34e4c74ba7cac9e0220e9c172c239fe71.zip | |
Split crates/x11/src/lib.rs (925 lines) into platform/
Pure reorganization, no behavior change - verified by diffing the
function-name and struct/trait-name sets before/after (both identical)
plus a full cargo test pass. lib.rs is now a thin shim (mod
declaration + pub use), same pattern crates/config used, since a crate
root can't itself become a directory. platform/mod.rs keeps the atom
table, Frame/X11Platform's struct definitions, the small free-function
helpers (err, modmask_for_keycode_in_mod_slots, rgb_to_pixel), and the
ClonedForRender trait+impl. The rest splits by concern:
- connect.rs: connect, keymap/modifier helpers, grab_keybindings.
- window.rs: manage_new_window and the other per-client lifecycle
methods (window_title/class, supports_wm_delete, unmanage,
frame_for).
- events.rs: handle_event, the X11 event-dispatch loop.
- actions.rs: raise_and_focus/request_close/sync_geometry/
redraw_all_decorations.
- trait_impl.rs: `impl Platform for X11Platform` - named to avoid
clippy's module_inception lint, since the containing directory is
already named `platform`.
- tests.rs: unsplit, same reasoning as every other split this pass.
A handful of X11Platform methods (frame_for, manage_new_window,
unmanage, raise_and_focus, request_close, sync_geometry,
keycode_to_keysym, modifiers_from_state, handle_event) went from
private to pub(super): called across what are now sibling submodules,
which Rust's privacy model doesn't let see each other's private items.
Diffstat (limited to 'crates/x11/src/platform/window.rs')
| -rw-r--r-- | crates/x11/src/platform/window.rs | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/crates/x11/src/platform/window.rs b/crates/x11/src/platform/window.rs new file mode 100644 index 0000000..997981f --- /dev/null +++ b/crates/x11/src/platform/window.rs @@ -0,0 +1,167 @@ +use super::*; + +impl X11Platform { + + pub(super) fn manage_new_window(&mut self, client: XWindow) -> PlatformResult<Option<Event>> { + let geom = self.conn.get_geometry(client).map_err(err)?.reply().map_err(err)?; + let title = self.window_title(client).unwrap_or_default(); + let (instance, class) = self.window_class(client); + let supports_delete = self.supports_wm_delete(client); + + let id = { + let mut wm = self.wm.borrow_mut(); + let id = wm.alloc_window_id(); + let mut w = CoreWindow::new(id, title); + w.app_id = class; + w.instance = instance; + w.geometry = Rect::new(geom.x as i32, geom.y as i32, geom.width as u32, geom.height as u32 + TITLEBAR_HEIGHT); + wm.add_window(w); + id + }; + let placed = self.wm.borrow().window(id).map(|w| w.geometry).unwrap_or(Rect::new(0, 0, 640, 480)); + + let frame = self.conn.generate_id().map_err(err)?; + let aux = CreateWindowAux::new() + .event_mask( + EventMask::SUBSTRUCTURE_REDIRECT + | EventMask::SUBSTRUCTURE_NOTIFY + | EventMask::BUTTON_PRESS + | EventMask::BUTTON_RELEASE + | EventMask::POINTER_MOTION + | EventMask::EXPOSURE, + ) + .background_pixel(self.conn.setup().roots[0].white_pixel); + // `Window.border_color`/`border_width` were tracked in + // `srdwm_core::Window` and settable via `srd.window.set_border_*`, + // but nothing ever actually drew a border with them on this + // backend - `set_border_color`/`set_border_width` below only + // updated the stored struct field. X11 windows have a native + // server-drawn border (`border_pixel`/the `create_window` + // `border-width` parameter, both unconditionally 0 here before), + // so this uses that rather than hand-rendering one - the X server + // draws it, no extra composite work needed. + let border_color = self.wm.borrow().window(id).map(|w| w.border_color).unwrap_or((0x31, 0x32, 0x44)); + let border_width = self.wm.borrow().window(id).map(|w| w.border_width).unwrap_or(0); + let aux = aux.border_pixel(rgb_to_pixel(border_color)); + self.conn + .create_window( + COPY_DEPTH_FROM_PARENT, + frame, + self.root, + placed.x as i16, + placed.y as i16, + placed.width as u16, + placed.height as u16, + border_width as u16, + WindowClass::INPUT_OUTPUT, + 0, + &aux, + ) + .map_err(err)?; + + self.conn.reparent_window(client, frame, 0, TITLEBAR_HEIGHT as i16).map_err(err)?; + self.conn + .configure_window(client, &ConfigureWindowAux::new().width(placed.width).height(placed.height.saturating_sub(TITLEBAR_HEIGHT))) + .map_err(err)?; + + // Passive-grab button1 on the client so our first click focuses/raises + // it, then replay the click through to the app - the standard + // click-to-focus pattern used by dwm/openbox/etc. + self.conn + .grab_button( + false, + client, + EventMask::BUTTON_PRESS, + GrabMode::SYNC, + GrabMode::ASYNC, + x11rb::NONE, + x11rb::NONE, + ButtonIndex::M1, + ModMask::ANY, + ) + .map_err(err)?; + + self.conn.map_window(client).map_err(err)?; + self.conn.map_window(frame).map_err(err)?; + self.conn + .change_property32(x11rb::protocol::xproto::PropMode::APPEND, self.root, self.atoms._NET_CLIENT_LIST, x11rb::protocol::xproto::AtomEnum::WINDOW, &[client]) + .map_err(err)?; + self.conn.flush().map_err(err)?; + + self.xid_to_core.insert(client, id); + self.frames.insert(id, Frame { frame, client, supports_delete }); + + let w = self.wm.borrow().window(id).cloned_for_render(); + if let Some(w) = w { + let _ = self.redraw_decoration(id, &w, true); + } + + Ok(Some(Event::WindowCreated(id))) + } + + fn window_title(&self, client: XWindow) -> Option<String> { + let reply = self + .conn + .get_property(false, client, self.atoms._NET_WM_NAME, self.atoms.UTF8_STRING, 0, 1024) + .ok()? + .reply() + .ok()?; + if reply.value_len > 0 { + return String::from_utf8(reply.value).ok(); + } + let reply = self + .conn + .get_property(false, client, x11rb::protocol::xproto::AtomEnum::WM_NAME, x11rb::protocol::xproto::AtomEnum::STRING, 0, 1024) + .ok()? + .reply() + .ok()?; + String::from_utf8(reply.value).ok() + } + + /// Reads `WM_CLASS` and splits it into `(instance, class)` - the + /// property is two NUL-terminated strings back to back, instance first + /// (ICCCM 4.1.2.5). Was never read at all before this: `manage_new_window` + /// only ever set `Window::title`, leaving `app_id` permanently empty on + /// every X11 window - meaning every `srd.rule({ class = ... }, ...)` + /// silently failed to match anything on this backend, the same root + /// cause `with_toplevel_app_id`'s doc comment describes already having + /// been found and fixed for native Wayland windows earlier. Returns + /// `("", "")` if the property is missing or malformed rather than an + /// `Option`, since both halves are used unconditionally either way. + fn window_class(&self, client: XWindow) -> (String, String) { + let Ok(cookie) = self.conn.get_property(false, client, x11rb::protocol::xproto::AtomEnum::WM_CLASS, x11rb::protocol::xproto::AtomEnum::STRING, 0, 1024) + else { + return (String::new(), String::new()); + }; + let Ok(reply) = cookie.reply() else { return (String::new(), String::new()) }; + let mut parts = reply.value.split(|&b| b == 0).map(|s| String::from_utf8_lossy(s).into_owned()); + let instance = parts.next().unwrap_or_default(); + let class = parts.next().unwrap_or_default(); + (instance, class) + } + + fn supports_wm_delete(&self, client: XWindow) -> bool { + let Ok(cookie) = self.conn.get_property(false, client, self.atoms.WM_PROTOCOLS, x11rb::protocol::xproto::AtomEnum::ATOM, 0, 32) else { + return false; + }; + let Ok(reply) = cookie.reply() else { return false }; + reply + .value32() + .map(|mut it| it.any(|a| a == self.atoms.WM_DELETE_WINDOW)) + .unwrap_or(false) + } + + pub(super) fn unmanage(&mut self, client: XWindow) -> Option<Event> { + let id = self.xid_to_core.remove(&client)?; + if let Some(frame) = self.frames.remove(&id) { + let _ = self.conn.destroy_window(frame.frame); + } + self.wm.borrow_mut().remove_window(id); + let _ = self.conn.flush(); + Some(Event::WindowDestroyed(id)) + } + + pub(super) fn frame_for(&self, id: WindowId) -> Option<XWindow> { + self.frames.get(&id).map(|f| f.frame) + } +} |