srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/xwayland.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/xwayland.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/xwayland.rs')
-rw-r--r--crates/wayland/src/xwayland.rs112
1 files changed, 62 insertions, 50 deletions
diff --git a/crates/wayland/src/xwayland.rs b/crates/wayland/src/xwayland.rs
index 67ca44a..d8c56a3 100644
--- a/crates/wayland/src/xwayland.rs
+++ b/crates/wayland/src/xwayland.rs
@@ -29,7 +29,7 @@ use smithay::xwayland::xwm::{Reorder, ResizeEdge as X11ResizeEdge, WmWindowPrope
use smithay::xwayland::{X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler};
use smithay::{delegate_xwayland_shell, desktop::Window as DWindow};
-use srdwm_core::{Event as CoreEvent, ResizeEdge, Window as CoreWindow, TITLEBAR_HEIGHT};
+use srdwm_core::{classify_menu_source, Event as CoreEvent, ResizeEdge, Window as CoreWindow, TITLEBAR_HEIGHT};
use crate::state::CompState;
@@ -79,6 +79,7 @@ pub(crate) fn spawn(handle: &LoopHandle<'static, CompState>, display_handle: &sm
data.xwm = Some(wm);
fix_wm_name(display_number);
data.ewmh = EwmhState::connect(display_number);
+ data.appmenu_registrar = Some(srdwm_platform::AppmenuRegistrarState::new());
}
Err(e) => log::error!("failed to start X11 window manager for XWayland: {e}"),
}
@@ -190,6 +191,18 @@ pub(crate) struct EwmhState {
gtk_menubar_object_path: Option<u32>,
gtk_app_menu_object_path: Option<u32>,
unity_object_path: Option<u32>,
+ /// KWin's own global-menu property pair - what `libdbusmenu-qt`'s KDE
+ /// integration sets, and (unlike every other atom here) not something
+ /// `classify_menu_source` needs to disambiguate at all: unlike the GTK/
+ /// Unity atoms, which can legitimately overlap on one window (the
+ /// `appmenu-gtk-module` shim case), these two together are already a
+ /// complete, unambiguous `com.canonical.dbusmenu` address on their own
+ /// - checked first in `read_global_menu`, before the GTK/Unity atoms,
+ /// so a Qt app running under a KDE Plasma session (which sets these,
+ /// never any `_GTK_*` atom) isn't rejected by `bus_name`'s hard
+ /// requirement on `_GTK_UNIQUE_BUS_NAME` before ever reaching them.
+ kde_appmenu_service_name: Option<u32>,
+ kde_appmenu_object_path: Option<u32>,
}
impl EwmhState {
@@ -220,6 +233,8 @@ impl EwmhState {
let gtk_menubar_object_path = intern("_GTK_MENUBAR_OBJECT_PATH");
let gtk_app_menu_object_path = intern("_GTK_APP_MENU_OBJECT_PATH");
let unity_object_path = intern("_UNITY_OBJECT_PATH");
+ let kde_appmenu_service_name = intern("_KDE_NET_WM_APPMENU_SERVICE_NAME");
+ let kde_appmenu_object_path = intern("_KDE_NET_WM_APPMENU_OBJECT_PATH");
let state = Self {
conn,
root,
@@ -232,6 +247,8 @@ impl EwmhState {
gtk_menubar_object_path,
gtk_app_menu_object_path,
unity_object_path,
+ kde_appmenu_service_name,
+ kde_appmenu_object_path,
};
// `_NET_CLIENT_LIST`/`_STACKING` are properties on the X root window,
// which XWayland recreates fresh on every launch - but nothing
@@ -284,6 +301,15 @@ impl EwmhState {
String::from_utf8(reply.value).ok().filter(|s| !s.is_empty())
};
+ // Checked before anything GTK-atom-related: these two, together,
+ // are already a complete address on their own - no classification
+ // needed - and a Qt app running under a KDE Plasma session never
+ // sets `_GTK_UNIQUE_BUS_NAME` at all, so falling through to that
+ // atom's hard requirement below would reject it outright.
+ if let (Some(bus_name), Some(menu_path)) = (read_string(self.kde_appmenu_service_name), read_string(self.kde_appmenu_object_path)) {
+ return Some(srdwm_core::GlobalMenu { bus_name, menu_path: Some(menu_path), app_path: None, window_path: None, source: srdwm_core::MenuSource::DbusMenu });
+ }
+
let bus_name = read_string(self.gtk_unique_bus_name)?;
let app_path = read_string(self.gtk_application_object_path);
let window_path = read_string(self.gtk_window_object_path);
@@ -344,22 +370,6 @@ impl EwmhState {
}
}
-/// The decision `read_global_menu` needs, pulled out as a pure function so
-/// it's unit-testable without a real X connection (matching this codebase's
-/// existing "no smithay/X11 dependency" convention for logic that doesn't
-/// actually need one, e.g. `decoration.rs`) - see that method's own doc
-/// comment for the full reasoning behind why `is_real_gtk_application`
-/// overrides "a GTK path exists" rather than the reverse.
-fn classify_menu_source(gtk_menu_path: Option<String>, is_real_gtk_application: bool, unity_path: Option<String>) -> (Option<String>, srdwm_core::MenuSource) {
- match gtk_menu_path {
- Some(path) if !is_real_gtk_application => (Some(path), srdwm_core::MenuSource::Unity),
- Some(path) => (Some(path), srdwm_core::MenuSource::Gtk),
- None => match unity_path {
- Some(path) => (Some(path), srdwm_core::MenuSource::Unity),
- None => (None, srdwm_core::MenuSource::Gtk),
- },
- }
-}
impl CompState {
/// Call on every focus change (from `set_keyboard_focus`, the single
@@ -389,6 +399,38 @@ impl CompState {
}
}
+ /// Drains `AppmenuRegistrarState`'s channel and applies every event to
+ /// the matching `Window.global_menu` - call once per event-loop tick
+ /// (`poll_events`), same as `IpcServer::poll`.
+ ///
+ /// `RegisterWindow`/`UnregisterWindow`'s `window_id` is a raw X11 XID,
+ /// with nothing here already mapping XID back to `WindowId` (`xwm`'s
+ /// own maps go the other way) - a linear scan over `id_to_window` is
+ /// fine for it: this only runs when a registrar event actually arrives,
+ /// not every tick, and the number of open windows is never large enough
+ /// for a scan to matter.
+ pub(crate) fn apply_registrar_events(&mut self) {
+ let Some(registrar) = &self.appmenu_registrar else { return };
+ let events = registrar.drain_events();
+ if events.is_empty() {
+ return;
+ }
+ for event in events {
+ let (window_id, menu) = match event {
+ srdwm_platform::RegistrarEvent::Registered { window_id, bus_name, menu_path } => (
+ window_id,
+ Some(srdwm_core::GlobalMenu { bus_name, menu_path: Some(menu_path), app_path: None, window_path: None, source: srdwm_core::MenuSource::DbusMenu }),
+ ),
+ srdwm_platform::RegistrarEvent::Unregistered { window_id } => (window_id, None),
+ };
+ let id = self.id_to_window.iter().find(|(_, w)| w.x11_surface().map(|x| x.window_id()) == Some(window_id)).map(|(id, _)| *id);
+ let Some(id) = id else { continue };
+ if let Some(w) = self.wm.borrow_mut().window_mut(id) {
+ w.global_menu = menu;
+ }
+ }
+ }
+
/// Call whenever the set of mapped windows changes (X11 window map,
/// unmap, or destroy - see the `XwmHandler` methods below).
pub(crate) fn update_net_client_list(&self) {
@@ -457,39 +499,6 @@ mod tests {
assert_eq!(shell_single_quote("/usr/bin/Xwayland"), "'/usr/bin/Xwayland'");
assert_eq!(shell_single_quote("/it's/here"), r"'/it'\''s/here'");
}
-
- #[test]
- fn appmenu_gtk_module_shim_is_classified_as_unity_not_gtk() {
- // The exact live case that motivated this: a GTK menubar path
- // present, but no application/window object path - confirmed by
- // an AGS peer session reading the actual exported menu content off
- // the bus and finding `unity.`-prefixed actions despite the GTK
- // atom being what resolved the path.
- let (path, source) = classify_menu_source(Some("/org/appmenu/gtk/window/0".to_string()), false, None);
- assert_eq!(path.as_deref(), Some("/org/appmenu/gtk/window/0"), "the path itself is still correct - only the label was wrong");
- assert_eq!(source, srdwm_core::MenuSource::Unity);
- }
-
- #[test]
- fn real_gtk_application_export_is_still_classified_as_gtk() {
- let (path, source) = classify_menu_source(Some("/org/gtk/menus/window/1".to_string()), true, None);
- assert_eq!(path.as_deref(), Some("/org/gtk/menus/window/1"));
- assert_eq!(source, srdwm_core::MenuSource::Gtk);
- }
-
- #[test]
- fn plain_unity_object_path_with_no_gtk_atom_is_unaffected() {
- let (path, source) = classify_menu_source(None, false, Some("/com/canonical/menu/1".to_string()));
- assert_eq!(path.as_deref(), Some("/com/canonical/menu/1"));
- assert_eq!(source, srdwm_core::MenuSource::Unity);
- }
-
- #[test]
- fn neither_path_present_is_none() {
- let (path, source) = classify_menu_source(None, false, None);
- assert_eq!(path, None);
- assert_eq!(source, srdwm_core::MenuSource::Gtk);
- }
}
fn to_core_resize_edge(edge: X11ResizeEdge) -> ResizeEdge {
@@ -579,6 +588,9 @@ impl CompState {
if self.context_menu.as_ref().is_some_and(|m| m.window == id) {
self.close_context_menu();
}
+ if self.snap_flyout.as_ref().is_some_and(|f| f.window == id) {
+ self.close_snap_flyout();
+ }
self.wm.borrow_mut().remove_window(id);
self.pending.borrow_mut().push(CoreEvent::WindowDestroyed(id));
crate::foreign_toplevel::window_closed(self, id);