diff options
| author | srdusr <[email protected]> | 2025-09-10 11:24:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2025-09-10 11:24:00 +0200 |
| commit | 5ee627928d0cb017e59d796c71793c35d5b65d33 (patch) | |
| tree | 3285a7df82a04b178fb41fae220d60fe1dd38bd7 /crates/wayland/src/input | |
| parent | 4a55182ce17fd4474f8be1e8819a41b7ccd11045 (diff) | |
| download | srdwm-5ee627928d0cb017e59d796c71793c35d5b65d33.tar.gz srdwm-5ee627928d0cb017e59d796c71793c35d5b65d33.zip | |
Wayland backend: desktop icons v2, menu rebuild, layer-shell/scale fixes,
XWayland stability, GPU rendering, and multi-cursor Phase 2
The bulk of a multi-session shift's real work landed in crates/wayland.
Full root-cause/verification narrative for every item below lives in
docs/TODO.md (each has its own dated entry); this is the summary:
Desktop shell:
- Real desktop icons v2 (state/desktop_icons.rs, desktop_icons.rs):
fixed origin-baked-before-the-bar-connects, fixed-icon sort order, and
a proper Rename/Delete-to-Trash menu (window_memory.rs backs the
rename-persistence side). Rubber-band marquee multi-select.
- icon_theme.rs: real freedesktop icon-theme lookup (inherits chain,
hicolor fallback) rendering actual theme SVGs via resvg/tiny-skia,
replacing the hand-drawn placeholder glyphs.
- Context/desktop menus (decoration.rs, desktop_menu.rs, state/menu.rs)
rebuilt to match the project's own AGS panel styling: rounded floating
panel, tinted-fill row highlight, real separators, a much fuller
titlebar window-menu action set.
Layer-shell / multi-monitor:
- Layer-shell hit-testing and render positioning (input/pointer.rs,
udev/render.rs's element placement) now correctly convert LayerMap's
logical geometry into physical pixels on a fractionally-scaled output
- root cause of a bottom-anchored dock being unclickable and
unpainted while a top-anchored bar on the same output worked.
udev/outputs.rs's relayout_outputs gained the same physical/logical
split for cross-output positioning, now backed by a real unit test
(next_logical_x) built from the original measured incident numbers.
- state/geometry.rs: a window's border/decoration no longer briefly
clips when moved between differently-scaled monitors mid-drag.
XWayland / stability:
- xwayland.rs, udev/session.rs, udev/platform.rs: fixed a 100%-
reproducible cold-start XKEYBOARD crash-loop (XWayland's own stdin
inherited a real, already-owned VT; env passthrough and idle-callback
spawn timing were both real, independent gaps) that had silently taken
down all X11-app support and the global-menu registrar every session.
- state/toplevel.rs, state/lifecycle.rs: XWayland dialog detection via
WM_TRANSIENT_FOR, not just a native xdg_toplevel parent.
Rendering:
- udev/render.rs, decoration.rs: real GPU (GBM+EGL+DrmCompositor)
window-content and cursor rendering on the udev backend, falling back
to the untouched Pixman path automatically on any init failure.
- decoration/tests.rs, state/mod.rs: rounded-corner/border fixes for
interactive resize lag and cross-monitor moves.
Multi-cursor Phase 2 (virtual_pointer.rs, new; state/mod.rs, udev/
platform.rs, winit/nested_platform.rs): pins a zwlr_virtual_pointer_
unstable_v1 object to a specific window, bypassing the shared seat/
focus/pointer_pos path entirely via hand-rolled wl_pointer.enter/motion/
button/frame/leave against every WlPointer the target client has bound
(PointerHandle::client_pointers). Lets an agent operate one window while
a human uses another, genuinely simultaneously, with zero client
cooperation and no second wl_seat (confirmed a dead end: real clients
only ever bind the first seat advertised).
Full workspace build/test/clippy clean.
Diffstat (limited to 'crates/wayland/src/input')
| -rw-r--r-- | crates/wayland/src/input/pointer.rs | 45 |
1 files changed, 40 insertions, 5 deletions
diff --git a/crates/wayland/src/input/pointer.rs b/crates/wayland/src/input/pointer.rs index f0e034e..602f613 100644 --- a/crates/wayland/src/input/pointer.rs +++ b/crates/wayland/src/input/pointer.rs @@ -231,6 +231,8 @@ pub(crate) fn handle_pointer_position(state: &mut CompState, pos: Point<f64, Log // A no-op whenever no desktop icon is currently being dragged - see // `CompState::update_desktop_icon_drag`'s own doc comment. state.update_desktop_icon_drag((pos.x as i32, pos.y as i32)); + // Likewise a no-op whenever no marquee selection is in progress. + state.update_desktop_marquee((pos.x as i32, pos.y as i32)); // Tells core which monitor the pointer is physically over right now -- // core has no pointer of its own to know this (see `pointer_monitor`'s @@ -505,6 +507,17 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic if let Some(menu) = state.context_menu.take() { if let Some(row) = menu.row_at(pos.x as i32, pos.y as i32) { let (_, action) = menu.items[row]; + // A separator row occupies real space (`row_at` resolves a + // click on it same as any other) but isn't a real action -- + // same "click does nothing, menu stays open" convention any + // native menu's own divider follows, rather than either + // running a no-op action or dismissing the whole menu on + // what was very possibly a slightly-off click at a real + // item just above/below it. + if matches!(action, crate::context_menu::MenuAction::Separator) { + state.context_menu = Some(menu); + return; + } state.close_context_menu(); state.run_context_menu_action(menu.window, action); } else { @@ -528,6 +541,10 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic if let Some(menu) = state.desktop_menu.take() { if let Some(row) = menu.row_at(pos.x as i32, pos.y as i32) { let (_, action) = menu.items[row].clone(); + if matches!(action, crate::desktop_menu::DesktopMenuAction::Separator) { + state.desktop_menu = Some(menu); + return; + } state.close_desktop_menu(); state.run_desktop_menu_action(action); } else { @@ -635,19 +652,28 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic // comment): a desktop icon here, single- or double-click // per `general.desktop_icon_single_click`, otherwise clear // whatever was selected. - let icon_hit = state.desktop_icons.as_ref().and_then(|icons| icons.icon_at(pos.x as i32, pos.y as i32).map(|i| icons.icons[i].id.clone())); + let icon_hit = state + .desktop_icons + .as_ref() + .and_then(|icons| icons.icon_at(pos.x as i32, pos.y as i32).map(|(i, origin)| (icons.icons[i].id.clone(), origin))); match icon_hit { - Some(id) => { + Some((id, origin)) => { let single_click_opens = state.wm.borrow().desktop_icon_single_click; if single_click_opens || state.is_double_click_icon(&id, time) { state.select_desktop_icon(Some(&id)); state.open_desktop_icon(&id); } else { state.select_desktop_icon(Some(&id)); - state.start_desktop_icon_drag(&id, (pos.x as i32, pos.y as i32)); + state.start_desktop_icon_drag(&id, origin, (pos.x as i32, pos.y as i32)); } } - None => state.select_desktop_icon(None), + // Genuinely bare desktop, not just "no icon under the + // pointer" - starts a rubber-band selection instead of + // only clearing whatever was selected before. The one + // "click and drag" desktop interaction this compositor + // never had (reported live next to "missing click and + // drag stuff like from windows"). + None => state.start_desktop_marquee((pos.x as i32, pos.y as i32)), } } } @@ -681,7 +707,7 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic (BTN_RIGHT, None) if layer_surface_under(state, pos).is_none() && !state.space.element_under(pos).is_some_and(|(w, _)| dwindow_is_visible(state, w)) => { - let icon_hit = state.desktop_icons.as_ref().and_then(|icons| icons.icon_at(pos.x as i32, pos.y as i32).map(|i| icons.icons[i].id.clone())); + let icon_hit = state.desktop_icons.as_ref().and_then(|icons| icons.icon_at(pos.x as i32, pos.y as i32).map(|(i, _origin)| icons.icons[i].id.clone())); match icon_hit { Some(id) => { state.select_desktop_icon(Some(&id)); @@ -697,6 +723,7 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic // always checked on release, same as `was_dragging`/`was_resizing` // below, just for a desktop icon instead of a window. state.end_desktop_icon_drag(); + state.end_desktop_marquee(); let mut wm = state.wm.borrow_mut(); let was_dragging = wm.is_dragging(); let was_resizing = wm.is_resizing(); @@ -712,6 +739,14 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic } else if was_resizing { wm.end_resize(); } + // Persists whatever `end_drag`/`end_resize` just updated in + // `remembered_geometry` - a real user action (button released + // after a drag/resize), not a per-frame event, so writing the + // whole small table each time is cheap and needs no separate + // dirty-tracking. See `window_memory.rs`'s own doc comment. + if was_dragging || was_resizing { + crate::window_memory::save_all(wm.all_remembered_geometry()); + } drop(wm); // `end_drag` can snap the geometry one more time (edge/top-of- // screen snapping, `SmartPlacement::snap_zone`) *after* the last |