atagen

monolith

public
Star0
Watch0
9e5c356ebe9ee22d044b029958abd0aef8a35946
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/protocols/src/toplevel.rsRust284 lines8.6 KB
1
//! wlr-foreign-toplevel-management-v1, for taskbars and window titles in
2
//! bars. Like ext-workspace, the compositor publishes a snapshot of its
3
//! 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) -> Self
74
    where
75
        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 update<D>(&mut self, dh: &DisplayHandle, snapshot: Snapshot)
89
    where
90
        D: Dispatch<ZwlrForeignToplevelHandleV1, HandleData> + 'static,
91
    {
92
        self.snapshot = snapshot;
93
        self.managers.retain(|m| m.handle.is_alive());
94
        for m in &mut self.managers {
95
            sync::<D>(dh, m, &self.snapshot);
96
        }
97
    }
98
}
99
100
fn sync<D>(dh: &DisplayHandle, m: &mut Manager, snap: &Snapshot)
101
where
102
    D: Dispatch<ZwlrForeignToplevelHandleV1, HandleData> + 'static,
103
{
104
    let Ok(client) = dh.get_client(m.handle.id()) else {
105
        return;
106
    };
107
    m.toplevels.retain(|t| {
108
        let keep = snap.toplevels.iter().any(|s| s.key == t.last.key);
109
        if !keep {
110
            t.handle.closed();
111
        }
112
        keep
113
    });
114
    for top in &snap.toplevels {
115
        let i = match m.toplevels.iter().position(|t| t.last.key == top.key) {
116
            Some(i) => i,
117
            None => {
118
                let Ok(handle) = client.create_resource::<ZwlrForeignToplevelHandleV1, _, D>(
119
                    dh,
120
                    m.handle.version(),
121
                    HandleData { key: top.key },
122
                ) else {
123
                    continue;
124
                };
125
                m.handle.toplevel(&handle);
126
                send_props(&handle, None, top);
127
                m.toplevels.push(Sent {
128
                    handle,
129
                    last: top.clone(),
130
                    outputs: Vec::new(),
131
                });
132
                let sent = m.toplevels.last_mut().expect("just pushed");
133
                sync_outputs(&client, sent, top);
134
                sent.handle.done();
135
                continue;
136
            }
137
        };
138
        let sent = &mut m.toplevels[i];
139
        let mut changed = sync_outputs(&client, sent, top);
140
        if sent.last != *top {
141
            send_props(&sent.handle, Some(&sent.last), top);
142
            sent.last = top.clone();
143
            changed = true;
144
        }
145
        if changed {
146
            sent.handle.done();
147
        }
148
    }
149
}
150
151
/// Sends output enter and leave for the client's wl_outputs. Returns
152
/// whether anything was sent.
153
fn sync_outputs(client: &Client, sent: &mut Sent, top: &Toplevel) -> bool {
154
    let want: Vec<WlOutput> = top
155
        .outputs
156
        .iter()
157
        .flat_map(|o| o.client_outputs(client))
158
        .collect();
159
    let mut changed = false;
160
    sent.outputs.retain(|o| {
161
        let keep = o.is_alive() && want.contains(o);
162
        if !keep && o.is_alive() {
163
            sent.handle.output_leave(o);
164
            changed = true;
165
        }
166
        keep
167
    });
168
    for wl in want {
169
        if !sent.outputs.contains(&wl) {
170
            sent.handle.output_enter(&wl);
171
            sent.outputs.push(wl);
172
            changed = true;
173
        }
174
    }
175
    changed
176
}
177
178
fn send_props(h: &ZwlrForeignToplevelHandleV1, old: Option<&Toplevel>, new: &Toplevel) {
179
    if old.is_none_or(|o| o.title != new.title) {
180
        h.title(new.title.clone());
181
    }
182
    if old.is_none_or(|o| o.app_id != new.app_id) {
183
        h.app_id(new.app_id.clone());
184
    }
185
    let states = |t: &Toplevel| {
186
        let mut s: Vec<u32> = Vec::new();
187
        if t.maximized {
188
            s.push(zwlr_foreign_toplevel_handle_v1::State::Maximized as u32);
189
        }
190
        if t.activated {
191
            s.push(zwlr_foreign_toplevel_handle_v1::State::Activated as u32);
192
        }
193
        if t.fullscreen && h.version() >= 2 {
194
            s.push(zwlr_foreign_toplevel_handle_v1::State::Fullscreen as u32);
195
        }
196
        s
197
    };
198
    if old.is_none_or(|o| states(o) != states(new)) {
199
        h.state(states(new).iter().flat_map(|s| s.to_ne_bytes()).collect());
200
    }
201
}
202
203
impl<D> GlobalDispatch2<ZwlrForeignToplevelManagerV1, D> for ManagerGlobal
204
where
205
    D: ForeignToplevelHandler
206
        + Dispatch<ZwlrForeignToplevelManagerV1, ManagerData>
207
        + Dispatch<ZwlrForeignToplevelHandleV1, HandleData>
208
        + 'static,
209
{
210
    fn bind(
211
        &self,
212
        state: &mut D,
213
        dh: &DisplayHandle,
214
        _client: &Client,
215
        resource: New<ZwlrForeignToplevelManagerV1>,
216
        data_init: &mut DataInit<'_, D>,
217
    ) {
218
        let handle = data_init.init(resource, ManagerData);
219
        let s = state.foreign_toplevel_state();
220
        let mut m = Manager {
221
            handle,
222
            toplevels: Vec::new(),
223
        };
224
        sync::<D>(dh, &mut m, &s.snapshot);
225
        s.managers.push(m);
226
    }
227
}
228
229
impl<D: ForeignToplevelHandler> Dispatch2<ZwlrForeignToplevelManagerV1, D> for ManagerData {
230
    fn request(
231
        &self,
232
        state: &mut D,
233
        _client: &Client,
234
        resource: &ZwlrForeignToplevelManagerV1,
235
        request: zwlr_foreign_toplevel_manager_v1::Request,
236
        _dh: &DisplayHandle,
237
        _data_init: &mut DataInit<'_, D>,
238
    ) {
239
        if let zwlr_foreign_toplevel_manager_v1::Request::Stop = request {
240
            resource.finished();
241
            state
242
                .foreign_toplevel_state()
243
                .managers
244
                .retain(|m| m.handle != *resource);
245
        }
246
    }
247
248
    fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ZwlrForeignToplevelManagerV1) {
249
        state
250
            .foreign_toplevel_state()
251
            .managers
252
            .retain(|m| m.handle != *resource);
253
    }
254
}
255
256
impl<D: ForeignToplevelHandler> Dispatch2<ZwlrForeignToplevelHandleV1, D> for HandleData {
257
    fn request(
258
        &self,
259
        state: &mut D,
260
        _client: &Client,
261
        _resource: &ZwlrForeignToplevelHandleV1,
262
        request: zwlr_foreign_toplevel_handle_v1::Request,
263
        _dh: &DisplayHandle,
264
        _data_init: &mut DataInit<'_, D>,
265
    ) {
266
        use zwlr_foreign_toplevel_handle_v1::Request;
267
        let action = match request {
268
            Request::Activate { .. } => Action::Activate(self.key),
269
            Request::Close => Action::Close(self.key),
270
            Request::SetMaximized => Action::Maximize(self.key, true),
271
            Request::UnsetMaximized => Action::Maximize(self.key, false),
272
            Request::SetFullscreen { .. } => Action::Fullscreen(self.key, true),
273
            Request::UnsetFullscreen => Action::Fullscreen(self.key, false),
274
            _ => return,
275
        };
276
        state.foreign_toplevel_action(action);
277
    }
278
279
    fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ZwlrForeignToplevelHandleV1) {
280
        for m in &mut state.foreign_toplevel_state().managers {
281
            m.toplevels.retain(|t| t.handle != *resource);
282
        }
283
    }
284
}