Commit 17d5669ab74b
tarball5 files changed+437-0
@@ -16,6 +16,7 @@ mod lock;
1616
mod nested;1717
mod scene;1818
mod state;19
+mod toplevels;1920
mod xwayland;2021
2122
#[cfg(feature = "spans")]@@ -84,6 +84,9 @@ pub struct State {
8484
pub ipc: crate::ipc::Server,8585
pub launches: crate::launch::Launches,8686
pub workspaces: WorkspaceState,87
+ pub foreign_toplevels: monolith_protocols::toplevel::ForeignToplevelState,88
+ pub toplevel_list: smithay::wayland::foreign_toplevel_list::ForeignToplevelListState,89
+ pub list_handles: crate::toplevels::ListHandles,8790
pub anim: Animator,8891
pub borders: crate::scene::Borders,8992
/// How each shown window was placed by the last layout pass, to spot@@ -208,6 +211,13 @@ impl State {
208211
ipc: crate::ipc::Server::default(),209212
launches: Default::default(),210213
workspaces: WorkspaceState::new::<State>(&dh),214
+ foreign_toplevels: monolith_protocols::toplevel::ForeignToplevelState::new::<State>(215
+ &dh,216
+ ),217
+ toplevel_list: smithay::wayland::foreign_toplevel_list::ForeignToplevelListState::new::<218
+ State,219
+ >(&dh),220
+ list_handles: Default::default(),211221
anim: Animator::new(animation),212222
borders: Default::default(),213223
last_kind: HashMap::new(),@@ -269,6 +279,7 @@ impl State {
269279
self.complete_captures();270280
crate::ipc::broadcast(self);271281
self.publish_workspaces();282
+ self.publish_toplevels();272283
let _ = self.display.flush_clients();273284
}274285
@@ -0,0 +1,136 @@
1
+//! Window lists for other clients: ext-foreign-toplevel-list for listing,2
+//! and wlr-foreign-toplevel-management for taskbars that also act on them.3
+4
+use crate::state::{State, identity};5
+use monolith_layout::WindowId;6
+use monolith_protocols::toplevel::{7
+ Action, ForeignToplevelHandler, ForeignToplevelState, Snapshot, Toplevel,8
+};9
+use smithay::wayland::foreign_toplevel_list::{10
+ ForeignToplevelHandle, ForeignToplevelListHandler, ForeignToplevelListState,11
+};12
+use std::collections::HashMap;13
+14
+#[derive(Default)]15
+pub struct ListHandles(HashMap<WindowId, ForeignToplevelHandle>);16
+17
+pub fn key(id: WindowId) -> u64 {18
+ slotmap::Key::data(&id).as_ffi()19
+}20
+21
+impl ForeignToplevelListHandler for State {22
+ fn foreign_toplevel_list_state(&mut self) -> &mut ForeignToplevelListState {23
+ &mut self.toplevel_list24
+ }25
+}26
+27
+impl ForeignToplevelHandler for State {28
+ fn foreign_toplevel_state(&mut self) -> &mut ForeignToplevelState {29
+ &mut self.foreign_toplevels30
+ }31
+32
+ fn foreign_toplevel_action(&mut self, action: Action) {33
+ let id_of = |k: u64| self.windows.keys().copied().find(|&id| key(id) == k);34
+ match action {35
+ Action::Activate(k) => {36
+ let Some(id) = id_of(k) else { return };37
+ if !self.layout.visible(id)38
+ && let Some(w) = self.layout.window(id)39
+ && let Some(o) = w.output40
+ && w.scratchpad.is_none()41
+ {42
+ let tag = w.tag;43
+ self.layout.toggle_view(o, tag);44
+ }45
+ self.layout.focus(id);46
+ }47
+ Action::Close(k) => {48
+ let Some(window) = id_of(k).and_then(|id| self.windows.get(&id)) else {49
+ return;50
+ };51
+ match (window.toplevel(), window.x11_surface()) {52
+ (Some(t), _) => t.send_close(),53
+ (_, Some(x)) => {54
+ let _ = x.close();55
+ }56
+ _ => {}57
+ }58
+ return;59
+ }60
+ Action::Maximize(k, on) => {61
+ let Some(id) = id_of(k) else { return };62
+ if self.layout.maximized(id) != on {63
+ self.layout.focus(id);64
+ self.layout.expand(monolith_layout::ExpandScope::All);65
+ }66
+ }67
+ Action::Fullscreen(k, on) => {68
+ let Some(id) = id_of(k) else { return };69
+ if self.layout.window(id).is_some_and(|w| w.fullscreen != on) {70
+ self.layout.toggle_fullscreen(id);71
+ }72
+ }73
+ }74
+ self.apply_layout();75
+ }76
+}77
+78
+impl State {79
+ /// Brings both window lists in line with the windows.80
+ pub fn publish_toplevels(&mut self) {81
+ let mut handles = std::mem::take(&mut self.list_handles.0);82
+ handles.retain(|id, h| {83
+ let keep = self.windows.contains_key(id);84
+ if !keep {85
+ self.toplevel_list.remove_toplevel(h);86
+ }87
+ keep88
+ });89
+ for (id, window) in &self.windows {90
+ let (app_id, title) = identity(window);91
+ match handles.get(id) {92
+ Some(h) => {93
+ if h.title() != title || h.app_id() != app_id {94
+ h.send_title(&title);95
+ h.send_app_id(&app_id);96
+ h.send_done();97
+ }98
+ }99
+ None => {100
+ let h = self.toplevel_list.new_toplevel::<State>(title, app_id);101
+ handles.insert(*id, h);102
+ }103
+ }104
+ }105
+ self.list_handles.0 = handles;106
+107
+ if !self.foreign_toplevels.has_clients() {108
+ return;109
+ }110
+ let focused = self.layout.focused();111
+ let toplevels = self112
+ .windows113
+ .iter()114
+ .map(|(&id, window)| {115
+ let (app_id, title) = identity(window);116
+ let w = self.layout.window(id);117
+ Toplevel {118
+ key: key(id),119
+ title,120
+ app_id,121
+ outputs: w122
+ .and_then(|w| w.output)123
+ .and_then(|o| self.smithay_output(o))124
+ .into_iter()125
+ .collect(),126
+ activated: focused == Some(id),127
+ maximized: self.layout.maximized(id),128
+ fullscreen: w.is_some_and(|w| w.fullscreen),129
+ }130
+ })131
+ .collect();132
+ let dh = self.display.clone();133
+ self.foreign_toplevels134
+ .update::<State>(&dh, Snapshot { toplevels });135
+ }136
+}@@ -1,3 +1,4 @@
11
//! Wayland protocols monolith implements itself.22
3
+pub mod toplevel;34
pub mod workspace;@@ -0,0 +1,288 @@
1
+//! wlr-foreign-toplevel-management-v1, for taskbars and window titles in2
+//! bars. Like ext-workspace, the compositor publishes a snapshot of its3
+//! windows and each client is sent the difference from what it has seen.4
+5
+use smithay::output::Output;6
+use smithay::reexports::wayland_protocols_wlr::foreign_toplevel::v1::server::{7
+ zwlr_foreign_toplevel_handle_v1::{self, ZwlrForeignToplevelHandleV1},8
+ zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},9
+};10
+use smithay::reexports::wayland_server::backend::{ClientId, GlobalId};11
+use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;12
+use smithay::reexports::wayland_server::{13
+ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,14
+};15
+use smithay::wayland::{Dispatch2, GlobalDispatch2};16
+17
+#[derive(Clone, Debug, Default, PartialEq)]18
+pub struct Snapshot {19
+ pub toplevels: Vec<Toplevel>,20
+}21
+22
+#[derive(Clone, Debug, PartialEq)]23
+pub struct Toplevel {24
+ /// Stable for the window's lifetime.25
+ pub key: u64,26
+ pub title: String,27
+ pub app_id: String,28
+ pub outputs: Vec<Output>,29
+ pub activated: bool,30
+ pub maximized: bool,31
+ pub fullscreen: bool,32
+}33
+34
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]35
+pub enum Action {36
+ Activate(u64),37
+ Close(u64),38
+ Maximize(u64, bool),39
+ Fullscreen(u64, bool),40
+}41
+42
+pub trait ForeignToplevelHandler {43
+ fn foreign_toplevel_state(&mut self) -> &mut ForeignToplevelState;44
+ fn foreign_toplevel_action(&mut self, action: Action);45
+}46
+47
+struct Sent {48
+ handle: ZwlrForeignToplevelHandleV1,49
+ last: Toplevel,50
+ outputs: Vec<WlOutput>,51
+}52
+53
+struct Manager {54
+ handle: ZwlrForeignToplevelManagerV1,55
+ toplevels: Vec<Sent>,56
+}57
+58
+pub struct ForeignToplevelState {59
+ global: GlobalId,60
+ managers: Vec<Manager>,61
+ snapshot: Snapshot,62
+}63
+64
+pub struct ManagerGlobal;65
+66
+pub struct ManagerData;67
+68
+pub struct HandleData {69
+ key: u64,70
+}71
+72
+impl ForeignToplevelState {73
+ pub fn new<D>(dh: &DisplayHandle) -> Self74
+ where75
+ D: GlobalDispatch<ZwlrForeignToplevelManagerV1, ManagerGlobal> + 'static,76
+ {77
+ Self {78
+ global: dh.create_global::<D, ZwlrForeignToplevelManagerV1, _>(3, ManagerGlobal),79
+ managers: Vec::new(),80
+ snapshot: Snapshot::default(),81
+ }82
+ }83
+84
+ pub fn global(&self) -> GlobalId {85
+ self.global.clone()86
+ }87
+88
+ pub fn has_clients(&self) -> bool {89
+ !self.managers.is_empty()90
+ }91
+92
+ pub fn update<D>(&mut self, dh: &DisplayHandle, snapshot: Snapshot)93
+ where94
+ D: Dispatch<ZwlrForeignToplevelHandleV1, HandleData> + 'static,95
+ {96
+ self.snapshot = snapshot;97
+ self.managers.retain(|m| m.handle.is_alive());98
+ for m in &mut self.managers {99
+ sync::<D>(dh, m, &self.snapshot);100
+ }101
+ }102
+}103
+104
+fn sync<D>(dh: &DisplayHandle, m: &mut Manager, snap: &Snapshot)105
+where106
+ D: Dispatch<ZwlrForeignToplevelHandleV1, HandleData> + 'static,107
+{108
+ let Ok(client) = dh.get_client(m.handle.id()) else {109
+ return;110
+ };111
+ m.toplevels.retain(|t| {112
+ let keep = snap.toplevels.iter().any(|s| s.key == t.last.key);113
+ if !keep {114
+ t.handle.closed();115
+ }116
+ keep117
+ });118
+ for top in &snap.toplevels {119
+ let i = match m.toplevels.iter().position(|t| t.last.key == top.key) {120
+ Some(i) => i,121
+ None => {122
+ let Ok(handle) = client.create_resource::<ZwlrForeignToplevelHandleV1, _, D>(123
+ dh,124
+ m.handle.version(),125
+ HandleData { key: top.key },126
+ ) else {127
+ continue;128
+ };129
+ m.handle.toplevel(&handle);130
+ send_props(&handle, None, top);131
+ m.toplevels.push(Sent {132
+ handle,133
+ last: top.clone(),134
+ outputs: Vec::new(),135
+ });136
+ let sent = m.toplevels.last_mut().expect("just pushed");137
+ sync_outputs(&client, sent, top);138
+ sent.handle.done();139
+ continue;140
+ }141
+ };142
+ let sent = &mut m.toplevels[i];143
+ let mut changed = sync_outputs(&client, sent, top);144
+ if sent.last != *top {145
+ send_props(&sent.handle, Some(&sent.last), top);146
+ sent.last = top.clone();147
+ changed = true;148
+ }149
+ if changed {150
+ sent.handle.done();151
+ }152
+ }153
+}154
+155
+/// Sends output enter and leave for the client's wl_outputs. Returns156
+/// whether anything was sent.157
+fn sync_outputs(client: &Client, sent: &mut Sent, top: &Toplevel) -> bool {158
+ let want: Vec<WlOutput> = top159
+ .outputs160
+ .iter()161
+ .flat_map(|o| o.client_outputs(client))162
+ .collect();163
+ let mut changed = false;164
+ sent.outputs.retain(|o| {165
+ let keep = o.is_alive() && want.contains(o);166
+ if !keep && o.is_alive() {167
+ sent.handle.output_leave(o);168
+ changed = true;169
+ }170
+ keep171
+ });172
+ for wl in want {173
+ if !sent.outputs.contains(&wl) {174
+ sent.handle.output_enter(&wl);175
+ sent.outputs.push(wl);176
+ changed = true;177
+ }178
+ }179
+ changed180
+}181
+182
+fn send_props(h: &ZwlrForeignToplevelHandleV1, old: Option<&Toplevel>, new: &Toplevel) {183
+ if old.is_none_or(|o| o.title != new.title) {184
+ h.title(new.title.clone());185
+ }186
+ if old.is_none_or(|o| o.app_id != new.app_id) {187
+ h.app_id(new.app_id.clone());188
+ }189
+ let states = |t: &Toplevel| {190
+ let mut s: Vec<u32> = Vec::new();191
+ if t.maximized {192
+ s.push(zwlr_foreign_toplevel_handle_v1::State::Maximized as u32);193
+ }194
+ if t.activated {195
+ s.push(zwlr_foreign_toplevel_handle_v1::State::Activated as u32);196
+ }197
+ if t.fullscreen && h.version() >= 2 {198
+ s.push(zwlr_foreign_toplevel_handle_v1::State::Fullscreen as u32);199
+ }200
+ s201
+ };202
+ if old.is_none_or(|o| states(o) != states(new)) {203
+ h.state(states(new).iter().flat_map(|s| s.to_ne_bytes()).collect());204
+ }205
+}206
+207
+impl<D> GlobalDispatch2<ZwlrForeignToplevelManagerV1, D> for ManagerGlobal208
+where209
+ D: ForeignToplevelHandler210
+ + Dispatch<ZwlrForeignToplevelManagerV1, ManagerData>211
+ + Dispatch<ZwlrForeignToplevelHandleV1, HandleData>212
+ + 'static,213
+{214
+ fn bind(215
+ &self,216
+ state: &mut D,217
+ dh: &DisplayHandle,218
+ _client: &Client,219
+ resource: New<ZwlrForeignToplevelManagerV1>,220
+ data_init: &mut DataInit<'_, D>,221
+ ) {222
+ let handle = data_init.init(resource, ManagerData);223
+ let s = state.foreign_toplevel_state();224
+ let mut m = Manager {225
+ handle,226
+ toplevels: Vec::new(),227
+ };228
+ sync::<D>(dh, &mut m, &s.snapshot);229
+ s.managers.push(m);230
+ }231
+}232
+233
+impl<D: ForeignToplevelHandler> Dispatch2<ZwlrForeignToplevelManagerV1, D> for ManagerData {234
+ fn request(235
+ &self,236
+ state: &mut D,237
+ _client: &Client,238
+ resource: &ZwlrForeignToplevelManagerV1,239
+ request: zwlr_foreign_toplevel_manager_v1::Request,240
+ _dh: &DisplayHandle,241
+ _data_init: &mut DataInit<'_, D>,242
+ ) {243
+ if let zwlr_foreign_toplevel_manager_v1::Request::Stop = request {244
+ resource.finished();245
+ state246
+ .foreign_toplevel_state()247
+ .managers248
+ .retain(|m| m.handle != *resource);249
+ }250
+ }251
+252
+ fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ZwlrForeignToplevelManagerV1) {253
+ state254
+ .foreign_toplevel_state()255
+ .managers256
+ .retain(|m| m.handle != *resource);257
+ }258
+}259
+260
+impl<D: ForeignToplevelHandler> Dispatch2<ZwlrForeignToplevelHandleV1, D> for HandleData {261
+ fn request(262
+ &self,263
+ state: &mut D,264
+ _client: &Client,265
+ _resource: &ZwlrForeignToplevelHandleV1,266
+ request: zwlr_foreign_toplevel_handle_v1::Request,267
+ _dh: &DisplayHandle,268
+ _data_init: &mut DataInit<'_, D>,269
+ ) {270
+ use zwlr_foreign_toplevel_handle_v1::Request;271
+ let action = match request {272
+ Request::Activate { .. } => Action::Activate(self.key),273
+ Request::Close => Action::Close(self.key),274
+ Request::SetMaximized => Action::Maximize(self.key, true),275
+ Request::UnsetMaximized => Action::Maximize(self.key, false),276
+ Request::SetFullscreen { .. } => Action::Fullscreen(self.key, true),277
+ Request::UnsetFullscreen => Action::Fullscreen(self.key, false),278
+ _ => return,279
+ };280
+ state.foreign_toplevel_action(action);281
+ }282
+283
+ fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ZwlrForeignToplevelHandleV1) {284
+ for m in &mut state.foreign_toplevel_state().managers {285
+ m.toplevels.retain(|t| t.handle != *resource);286
+ }287
+ }288
+}