srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/wayland/src')
-rw-r--r--crates/wayland/src/appmenu.rs124
-rw-r--r--crates/wayland/src/gtk_shell.rs33
2 files changed, 151 insertions, 6 deletions
diff --git a/crates/wayland/src/appmenu.rs b/crates/wayland/src/appmenu.rs
new file mode 100644
index 0000000..9abe826
--- /dev/null
+++ b/crates/wayland/src/appmenu.rs
@@ -0,0 +1,124 @@
+//! `org_kde_kwin_appmenu`: the Wayland-native equivalent of the `_GTK_*`/
+//! `_UNITY_OBJECT_PATH` X11 properties `xwayland.rs`'s `read_global_menu`
+//! reads - lets a client link a `wl_surface` straight to a
+//! `com.canonical.dbusmenu` D-Bus address, no XWayland/X11 property
+//! round-trip involved at all.
+//!
+//! This is the only real gap X11 property reading structurally can't
+//! close: `xwayland.rs::read_global_menu` only ever runs for a window that
+//! `x11_surface()` resolves (an XWayland client), so a genuinely
+//! Wayland-native GTK4/Qt6 window - no XWayland involved - could never
+//! have exported a menu srdwm would see, regardless of how correct the X11
+//! side is. There is no Wayland-native equivalent of `_GTK_MENUBAR_OBJECT_
+//! PATH`/GMenuModel (GTK's own menu export is X11-property-only, by
+//! GTK's own design, XWayland or not), but Qt/KDE's side of this - the
+//! same `com.canonical.dbusmenu` content `appmenu-qt5`'s Unity-registrar
+//! path exports over X11 - does have a real Wayland-native protocol for
+//! it, and it's what this file implements. Closes the other open half of
+//! the Qt global-menu gap found this session: `read_global_menu`'s
+//! `_UNITY_OBJECT_PATH` handling only ever reaches a Qt app that still
+//! goes through XWayland, which a `QT_QPA_PLATFORM=wayland` app (the
+//! default this session's `env.conf` port now sets) does not.
+//!
+//! Always `MenuSource::DbusMenu`, never `MenuSource::Unity`: this
+//! protocol's own description says exactly what it addresses - "a
+//! `com.canonical.dbusmenu` interface" - and despite the name, `Unity`
+//! does *not* mean dbusmenu content in this codebase (see that variant's
+//! own doc comment, corrected after an AGS peer session caught this exact
+//! mislabeling live: `appmenu-gtk-module`'s `_UNITY_OBJECT_PATH` points at
+//! an `org.gtk.Menus` object, not a dbusmenu one). `DbusMenu` is the one
+//! that means what this protocol carries. No classification heuristic
+//! needed the way `xwayland.rs::classify_menu_source` needs one for the
+//! X11 side, though: this protocol only ever carries the one kind of
+//! content.
+//!
+//! No smithay helper exists for this protocol (same as `gamma_control.rs`/
+//! `output_power.rs`/`screencopy.rs`), so the `GlobalDispatch`/`Dispatch`
+//! plumbing below is hand-written against the raw `wayland-protocols-
+//! plasma` server bindings.
+
+use smithay::reexports::wayland_server::backend::{ClientId, GlobalId};
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
+use smithay::reexports::wayland_server::{Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New};
+use wayland_protocols_plasma::appmenu::server::org_kde_kwin_appmenu::{self, OrgKdeKwinAppmenu};
+use wayland_protocols_plasma::appmenu::server::org_kde_kwin_appmenu_manager::{self, OrgKdeKwinAppmenuManager};
+
+use crate::state::CompState;
+
+/// The manager global. Held by `CompState` purely to keep the global alive
+/// for the compositor's lifetime - same reasoning as `GammaControlManagerState`.
+pub struct AppmenuManagerState {
+ _global: GlobalId,
+}
+
+impl AppmenuManagerState {
+ pub fn new<D>(dh: &DisplayHandle) -> Self
+ where
+ D: GlobalDispatch<OrgKdeKwinAppmenuManager, ()> + 'static,
+ {
+ Self { _global: dh.create_global::<D, OrgKdeKwinAppmenuManager, _>(2, ()) }
+ }
+}
+
+/// Which `wl_surface` an `org_kde_kwin_appmenu` object addresses, resolved
+/// once at creation - same reasoning as `GammaControlData`'s output.
+pub struct AppmenuData {
+ surface: WlSurface,
+}
+
+impl GlobalDispatch<OrgKdeKwinAppmenuManager, ()> for CompState {
+ fn bind(_state: &mut Self, _dh: &DisplayHandle, _client: &Client, manager: New<OrgKdeKwinAppmenuManager>, _data: &(), data_init: &mut DataInit<'_, Self>) {
+ data_init.init(manager, ());
+ }
+}
+
+impl Dispatch<OrgKdeKwinAppmenuManager, ()> for CompState {
+ fn request(
+ _state: &mut Self,
+ _client: &Client,
+ _manager: &OrgKdeKwinAppmenuManager,
+ request: org_kde_kwin_appmenu_manager::Request,
+ _data: &(),
+ _dh: &DisplayHandle,
+ data_init: &mut DataInit<'_, Self>,
+ ) {
+ let org_kde_kwin_appmenu_manager::Request::Create { id, surface } = request else { return };
+ data_init.init(id, AppmenuData { surface });
+ }
+}
+
+impl Dispatch<OrgKdeKwinAppmenu, AppmenuData> for CompState {
+ fn request(
+ state: &mut Self,
+ _client: &Client,
+ _resource: &OrgKdeKwinAppmenu,
+ request: org_kde_kwin_appmenu::Request,
+ data: &AppmenuData,
+ _dh: &DisplayHandle,
+ _data_init: &mut DataInit<'_, Self>,
+ ) {
+ let org_kde_kwin_appmenu::Request::SetAddress { service_name, object_path } = request else { return };
+ let Some(id) = state.surface_to_id.get(&data.surface).copied() else { return };
+ if let Some(w) = state.wm.borrow_mut().window_mut(id) {
+ w.global_menu = Some(srdwm_core::GlobalMenu {
+ bus_name: service_name,
+ menu_path: Some(object_path),
+ app_path: None,
+ window_path: None,
+ source: srdwm_core::MenuSource::DbusMenu,
+ });
+ }
+ }
+
+ /// The protocol's own doc comment: "If not applicable, clients should
+ /// remove this object" - releasing (or disconnecting) is a real,
+ /// expected way for a client to say "no menu after all", not just
+ /// cleanup, so the address needs actually clearing here rather than
+ /// left stale for whichever window this surface maps to.
+ fn destroyed(state: &mut Self, _client: ClientId, _resource: &OrgKdeKwinAppmenu, data: &AppmenuData) {
+ let Some(id) = state.surface_to_id.get(&data.surface).copied() else { return };
+ if let Some(w) = state.wm.borrow_mut().window_mut(id) {
+ w.global_menu = None;
+ }
+ }
+}
diff --git a/crates/wayland/src/gtk_shell.rs b/crates/wayland/src/gtk_shell.rs
index f4ae030..1b0e57b 100644
--- a/crates/wayland/src/gtk_shell.rs
+++ b/crates/wayland/src/gtk_shell.rs
@@ -86,16 +86,37 @@ impl Dispatch<GtkSurface1, GtkSurfaceData> for CompState {
// (or never had one) - matches `xwayland.rs`'s `read_global_menu`
// returning `None` for the same case, so a panel sees the same
// shape regardless of which backend a window came from.
- // Always `MenuSource::Gtk`: this protocol has no Unity-style
- // equivalent to carry, unlike XWayland's `_UNITY_OBJECT_PATH` --
- // see `xwayland.rs`'s `read_global_menu` for the case that needs
- // the other variant.
+ //
+ // `source` used to be hardcoded `MenuSource::Gtk` unconditionally
+ // here, on the reasoning that this protocol "has no Unity-style
+ // equivalent to carry" - true of the *wire message*, but not of
+ // what's actually behind it: `appmenu-gtk-module` is the same
+ // module regardless of whether it signals its address via X11
+ // atoms (`xwayland.rs`) or `gtk_surface1.set_dbus_properties`
+ // (here), and exports a plain `Gtk.Window`'s menu (no
+ // `GtkApplication`) through its Unity-compatibility shim --
+ // `unity.`-prefixed actions and all - on *either* path. That's
+ // exactly the misclassification `classify_menu_source` was written
+ // to fix for the X11 side (see its own doc comment and
+ // `xwayland.rs::read_global_menu`); this call site just never
+ // adopted it, so a native-Wayland GTK window hitting the same
+ // shim case stayed permanently mislabeled `Gtk` while its XWayland
+ // counterpart got the fix. Confirmed live: Nemo (native Wayland,
+ // not XWayland - only Spotify was in `_NET_CLIENT_LIST` at the
+ // time) reported `source: "gtk"` here, but its actual exported
+ // menu content, read directly off the bus, used `unity.`-prefixed
+ // actions throughout (File/Edit/View/Go/Bookmarks/Help) - the
+ // exact "every item renders, none of them are ever clickable"
+ // failure `classify_menu_source`'s own tests exist to catch.
+ let is_real_gtk_application = application_object_path.as_deref().is_some_and(|s| !s.is_empty()) || window_object_path.as_deref().is_some_and(|s| !s.is_empty());
+ let gtk_menu_path = menubar_path.filter(|s| !s.is_empty()).or_else(|| app_menu_path.filter(|s| !s.is_empty()));
+ let (menu_path, source) = srdwm_core::classify_menu_source(gtk_menu_path, is_real_gtk_application, None);
let menu = unique_bus_name.filter(|s| !s.is_empty()).map(|bus_name| srdwm_core::GlobalMenu {
bus_name,
- menu_path: menubar_path.filter(|s| !s.is_empty()).or_else(|| app_menu_path.filter(|s| !s.is_empty())),
+ menu_path,
app_path: application_object_path.filter(|s| !s.is_empty()),
window_path: window_object_path.filter(|s| !s.is_empty()),
- source: srdwm_core::MenuSource::Gtk,
+ source,
});
if let Some(w) = state.wm.borrow_mut().window_mut(id) {
w.global_menu = menu;