srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/udev/platform.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-12-24 20:44:00 +0200
committersrdusr <[email protected]>2024-12-24 20:44:00 +0200
commit3d73ed0f057a555ded67ad58dbe02b36cd4d2d1f (patch)
tree71e9bab462b9073444183e2a969ba512cb94559f /crates/wayland/src/udev/platform.rs
parent331a1a1d3a0d8dfd4c285b755c2e13785c95ee2a (diff)
downloadsrdwm-3d73ed0f057a555ded67ad58dbe02b36cd4d2d1f.tar.gz
srdwm-3d73ed0f057a555ded67ad58dbe02b36cd4d2d1f.zip
Accumulate wayland-crate additions: capture rendering, focus/Space sync, VT resume, plumbing
Bundles the remaining wayland-crate changes built up here, touching both backends (udev and winit) and the shared input/rendering code: - udev/capture.rs: off-screen Pixman render of an arbitrary (not necessarily on-screen) workspace's window content to a PPM file -- what crates/core's capture-request queue drives, for a workspace switcher's thumbnail previews. wlr-screencopy structurally can't do this (it can only see what an output is presenting), which is why this exists as a separate render path rather than reusing it. - input::focus_window now also raises the window in smithay's own Space, not just core's stacking order - Space is what actually renders on top and what pointer hit-testing reads, so any focus path that skipped this (an IPC "focus" dispatch, concretely) left a window genuinely focused while still rendering, and receiving clicks, underneath whatever was already topmost. Both backends' poll loops now re-sync this after any IPC mutation. - udev/session.rs's VT-switch resume fix (drains a stale pending page flip before reasserting CRTCs) already has its own earlier, cleanly isolated commit - not duplicated here. - Assorted decoration/cursor/rounded-corners/output-management/ screencopy/XWayland changes and their cross-backend wiring. Coarser than the repo's usual one-purpose-per-commit convention, deliberately - see the core-crate sweep commit's own message for why.
Diffstat (limited to 'crates/wayland/src/udev/platform.rs')
-rw-r--r--crates/wayland/src/udev/platform.rs111
1 files changed, 111 insertions, 0 deletions
diff --git a/crates/wayland/src/udev/platform.rs b/crates/wayland/src/udev/platform.rs
index f562da2..8676a5a 100644
--- a/crates/wayland/src/udev/platform.rs
+++ b/crates/wayland/src/udev/platform.rs
@@ -113,6 +113,7 @@ impl UdevPlatform {
heads,
active: true,
pointer_pos: (width as f64 / 2.0, height as f64 / 2.0).into(),
+ session: session.clone(),
};
let state = CompState {
@@ -141,6 +142,7 @@ impl UdevPlatform {
),
_screencopy_state: crate::screencopy::ScreencopyState::new::<CompState>(&display_handle),
screencopy_pending: Vec::new(),
+ _appmenu_state: crate::appmenu::AppmenuManagerState::new::<CompState>(&display_handle),
_foreign_toplevel_state: crate::foreign_toplevel::ForeignToplevelState::new::<CompState>(&display_handle),
foreign_toplevel_managers: Vec::new(),
foreign_toplevel_handles: HashMap::new(),
@@ -170,15 +172,20 @@ impl UdevPlatform {
cursor_status: smithay::input::pointer::CursorImageStatus::default_named(),
cursor_buffers: crate::cursor::make_buffers(),
last_titlebar_click: None,
+ gesture_swipe: None,
context_menu: None,
context_menu_buffer: None,
+ snap_flyout: None,
+ snap_flyout_buffer: None,
wm: wm.clone(),
surface_to_id: HashMap::new(),
id_to_window: HashMap::new(),
dead_layer_surfaces: HashSet::new(),
+ hidden_layer_surfaces: HashMap::new(),
decorations: HashMap::new(),
border_top_decorations: HashMap::new(),
border_bottom_decorations: HashMap::new(),
+ decoration_signatures: HashMap::new(),
shadow_buffers: HashMap::new(),
rounded_corners_program: None,
content_epoch: HashMap::new(),
@@ -196,6 +203,7 @@ impl UdevPlatform {
xwayland_windows: HashMap::new(),
xwayland_pending: Vec::new(),
ewmh: None,
+ appmenu_registrar: None,
};
let listener = ListeningSocket::bind_auto("wayland", 0..32).map_err(err)?;
@@ -262,9 +270,92 @@ impl Platform for UdevPlatform {
self.state.tick_repeat();
self.display.dispatch_clients(&mut self.state).map_err(err)?;
self.display.flush_clients().map_err(err)?;
+ self.state.apply_registrar_events();
if let Some(ipc) = self.ipc.as_mut() {
if ipc.poll(&self.state.wm) {
self.pending.borrow_mut().push(CoreEvent::WorkspaceChanged);
+ // `ipc.rs`'s `handle_request` (`"focus"`, `"toggle
+ // visibility"`, ...) only ever touches core's `WindowManager`
+ // - it has no handle to `state.space`, which is what
+ // actually renders on top *and* what `space.element_under`
+ // hit-tests against (see `input::focus_window`'s own doc
+ // comment, which fixed every *other* focus path this same
+ // way). Left alone, a dock/AGS "focus" click over IPC moved
+ // core's idea of focus while the window kept rendering, and
+ // hit-testing, underneath whatever was already topmost --
+ // reproduced live: `srd dispatch focus` on a covered Firefox
+ // window raised it in the taskbar/keyboard sense but a
+ // click at its own visible location still landed on the
+ // window still actually on top. Re-syncing here rather than
+ // in `ipc.rs` itself since core is platform-agnostic and
+ // cannot see `state.space`; cheap and safe to call
+ // unconditionally on any IPC mutation, not just ones that
+ // are definitely focus changes - raising an already-topmost
+ // element is a no-op reinsertion.
+ let focused = self.state.wm.borrow().focused_id();
+ if let Some(id) = focused {
+ crate::input::focus_window(&mut self.state, id);
+ }
+ }
+ }
+ // Starts srdwm's own lock UI if `srd dispatch lock` queued a
+ // request since the last poll - see `WindowManager::request_lock`'s
+ // own doc comment for why this crosses the core/backend boundary
+ // as a drained request rather than a direct call. A no-op if
+ // already locked (native or external), same guard `begin_native_
+ // lock` applies itself.
+ if self.state.wm.borrow_mut().drain_lock_request() {
+ self.state.begin_native_lock();
+ }
+ // Same drained-request pattern as the lock check just above, for
+ // `srd capture workspace` - see `WindowManager::request_capture_
+ // workspace`'s own doc comment for why this needs the backend at
+ // all rather than being answerable from core state.
+ let capture_requests = self.state.wm.borrow_mut().drain_capture_requests();
+ if !capture_requests.is_empty() {
+ self.state.service_capture_requests(capture_requests);
+ }
+ // Checks whether a background PAM authentication spawned by a
+ // native lock's own `Return` handling finished since the last
+ // poll - see `native_lock.rs`'s module doc comment for why this
+ // runs on a background thread rather than blocking here.
+ self.state.poll_native_lock_auth();
+ // Applies any `srd set_output_position` IPC requests queued since
+ // the last poll - see `WindowManager::request_output_position`'s
+ // own doc comment for why this indirection exists at all (core has
+ // no real output handle to move itself). `id` is this head's index
+ // into `udev.heads` *as of the platform's last `monitors()` query*
+ // (see that function's own construction of `Monitor::new(i as u32,
+ // ...)`) - stale if a hotplug reordered heads in between, same
+ // trade-off `wlr-output-management-v1`'s own `apply_or_test`
+ // guards against with a serial check. Not guarded the same way
+ // here: this is a first pass at the primitive a display-settings
+ // panel needs to build real monitor mirroring on top of, not yet
+ // hardened against a hotplug racing an in-flight request - worth
+ // adding if that turns out to matter in practice.
+ let output_requests = self.state.wm.borrow_mut().drain_output_position_requests();
+ if !output_requests.is_empty() {
+ let mut any_applied = false;
+ for (id, x, y) in output_requests {
+ let Some(output) = self.state.udev.as_ref().and_then(|u| u.heads.get(id as usize)).map(|h| h.output.clone()) else {
+ log::warn!("udev: set_output_position: no head at index {id}");
+ continue;
+ };
+ crate::output_management::apply_output_position(&mut self.state, &output, (x, y).into());
+ any_applied = true;
+ }
+ if any_applied {
+ crate::output_management::broadcast_dirty_outputs(&mut self.state);
+ // Core's own `Monitor` list is a passive mirror of whatever
+ // the backend last reported (see `monitors()` above) --
+ // without re-triggering a query, `Window.geometry`/
+ // placement would keep using the pre-move rect until some
+ // unrelated event happened to refresh it. `MonitorAdded`'s
+ // payload is discarded unread on this path (`main.rs`
+ // re-queries the full list rather than trusting it), same
+ // as every other "just go recompute" use of this event
+ // elsewhere in this codebase.
+ self.pending.borrow_mut().push(CoreEvent::MonitorAdded(srdwm_core::Monitor::new(0, "", srdwm_core::Rect::new(0, 0, 0, 0))));
}
}
self.state.render_udev_frame();
@@ -313,6 +404,7 @@ impl Platform for UdevPlatform {
// the resulting geometry back over IPC, not just from
// reading this code.
m.full_geometry = srdwm_core::Rect::new(head.location.x, head.location.y, head.size.0 as u32, head.size.1 as u32);
+ m.maximize_geometry = crate::input::maximize_geometry_for(&head.output, m.full_geometry);
m.primary = i == 0;
m
})
@@ -396,4 +488,23 @@ impl Platform for UdevPlatform {
fn ungrab_keyboard(&mut self) -> PlatformResult<()> {
Ok(())
}
+
+ fn keyboard_layout(&mut self) -> PlatformResult<String> {
+ let Some(keyboard) = self.state.seat.get_keyboard() else { return Ok(String::new()) };
+ Ok(keyboard.with_xkb_state(&mut self.state, |ctx| {
+ let xkb = ctx.xkb().lock().unwrap();
+ let layout = xkb.active_layout();
+ xkb.layout_name(layout).to_string()
+ }))
+ }
+
+ fn cycle_keyboard_layout(&mut self) -> PlatformResult<String> {
+ let Some(keyboard) = self.state.seat.get_keyboard() else { return Ok(String::new()) };
+ Ok(keyboard.with_xkb_state(&mut self.state, |mut ctx| {
+ ctx.cycle_next_layout();
+ let xkb = ctx.xkb().lock().unwrap();
+ let layout = xkb.active_layout();
+ xkb.layout_name(layout).to_string()
+ }))
+ }
}