1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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;
}
}
}
|