srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/input/pointer.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-08-15 23:22:00 +0200
committersrdusr <[email protected]>2025-08-15 23:22:00 +0200
commitcc22fe69a68e0fff027d833029aea850976488c8 (patch)
treeeb1350c24c61b5d4b30d763d64149a5c6504a925 /crates/wayland/src/input/pointer.rs
parent6694a2d9946d98d5752d84b22c45879cab5c9abe (diff)
downloadsrdwm-cc22fe69a68e0fff027d833029aea850976488c8.tar.gz
srdwm-cc22fe69a68e0fff027d833029aea850976488c8.zip
Add real desktop icons plus right-click desktop/icon context menus
Closes "right-click on bare desktop" - previously a true no-op, nothing rendered above the wallpaper at all. Requested directly: a real desktop "just like windows does" - Home/Computer/Trash plus one icon per real ~/Desktop entry, individually draggable with persisted grid positions, double-click to open, right-click menus (per-icon "Open" plus "Set as Wallpaper" for image files when general.wallpaper_command is set; bare desktop "New Folder"/"Refresh"). Architecture mirrors the existing context_menu.rs/snap_flyout.rs "compositor-owned floating UI" pattern: desktop_icons.rs (data model, grid layout, filesystem scan, hit-test), desktop_icons_state.rs (JSON persistence, same shape as monitor_layout.rs), desktop_menu.rs (the new right-click menu, reusing decoration::render_context_menu's existing rasterizer), state/desktop_icons.rs (CompState glue: rescan/select/drag/ open/persist), and a new decoration::render_desktop_icon rasterizer -- hand-drawn glyphs, since no PNG/SVG decoding capability exists anywhere in this workspace. Wired into both render loops (udev and winit) above the wallpaper and below every window, and into input/pointer.rs's button/ motion handlers for selection, drag, double-click, and both menus. Four new config keys: general.desktop_icons (default true - a directly requested, purely visual feature, unlike the opt-in-while-experimental general.gpu), general.file_manager, general.desktop_icon_single_click, general.wallpaper_command (all default off/empty). Deliberately out of scope for this pass, stated up front: move-to-trash and "Empty Trash" (destructive, no confirmation-dialog primitive to gate them on yet), filesystem watching, multi-select, per-mimetype icon art, icons on any monitor but the primary one. 124 wayland-crate tests (up from 106), full workspace build and clippy clean.
Diffstat (limited to 'crates/wayland/src/input/pointer.rs')
-rw-r--r--crates/wayland/src/input/pointer.rs59
1 files changed, 59 insertions, 0 deletions
diff --git a/crates/wayland/src/input/pointer.rs b/crates/wayland/src/input/pointer.rs
index d4c5678..f0e034e 100644
--- a/crates/wayland/src/input/pointer.rs
+++ b/crates/wayland/src/input/pointer.rs
@@ -228,6 +228,10 @@ pub(crate) fn handle_pointer_position(state: &mut CompState, pos: Point<f64, Log
return;
}
+ // 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));
+
// 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
// own doc comment), and `add_window`'s target-monitor fallback needs
@@ -520,6 +524,17 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic
}
return;
}
+ // Same rule again for the desktop-icon/bare-desktop menu.
+ 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();
+ state.close_desktop_menu();
+ state.run_desktop_menu_action(action);
+ } else {
+ state.close_desktop_menu();
+ }
+ return;
+ }
}
// Modifier+drag: with the modifier held, dragging *anywhere* in a window
@@ -613,6 +628,27 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic
if let Some(&id) = dwindow_wl_surface(&window).and_then(|s| state.surface_to_id.get(&s)) {
focus_window(state, id);
}
+ } else {
+ // Genuinely bare desktop (or below every window, which
+ // only ever means bare desktop - icons render below every
+ // window, see `desktop_icons.rs`'s own module doc
+ // 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()));
+ match icon_hit {
+ Some(id) => {
+ 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));
+ }
+ }
+ None => state.select_desktop_icon(None),
+ }
}
}
} else if pressed && (button == BTN_RIGHT || button == BTN_MIDDLE) {
@@ -635,9 +671,32 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic
// instead of the window menu - a plain left-click there still
// just toggles maximize, unchanged.
(BTN_RIGHT, Some((id, TitlebarHit::Maximize))) => state.open_snap_flyout(id, (pos.x as i32, pos.y as i32)),
+ // Right-click bare desktop (no titlebar/border hit, no window
+ // content, no bar/dock layer surface - same "genuinely bare"
+ // definition the left-click branch above uses): a desktop
+ // icon's own menu if the click landed on one, otherwise the
+ // "New Folder"/"Refresh" desktop menu. Previously a true
+ // no-op, the actual gap this whole feature exists to close --
+ // see this session's own TODO.md entry.
+ (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()));
+ match icon_hit {
+ Some(id) => {
+ state.select_desktop_icon(Some(&id));
+ state.open_desktop_icon_menu(&id, (pos.x as i32, pos.y as i32));
+ }
+ None => state.open_desktop_menu((pos.x as i32, pos.y as i32)),
+ }
+ }
_ => {}
}
} else if !pressed {
+ // A no-op via `Option::take()` when no icon drag was active --
+ // 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();
let mut wm = state.wm.borrow_mut();
let was_dragging = wm.is_dragging();
let was_resizing = wm.is_resizing();