Commit 57e81cf6ceb2
tarball12 files changed+807-765
@@ -0,0 +1,225 @@
1
+use crate::state::{ClientState, State};2
+use smithay::backend::allocator::dmabuf::Dmabuf;3
+use smithay::backend::renderer::utils::{import_surface_tree, on_commit_buffer_handler};4
+use smithay::desktop::PopupKind;5
+use smithay::reexports::calloop::Interest;6
+use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;7
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;8
+use smithay::reexports::wayland_server::{Client, Resource};9
+use smithay::utils::{Rectangle, Transform};10
+use smithay::wayland::buffer::BufferHandler;11
+use smithay::wayland::compositor::{12
+ BufferAssignment, CompositorClientState, CompositorHandler, CompositorState, Damage,13
+ SurfaceAttributes, add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface,14
+ with_states,15
+};16
+use smithay::wayland::dmabuf::{17
+ DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf,18
+};19
+use smithay::wayland::drm_syncobj::{DrmSyncobjCachedState, DrmSyncobjHandler, DrmSyncobjState};20
+use smithay::wayland::shm::{ShmHandler, ShmState, with_buffer_contents};21
+22
+impl CompositorHandler for State {23
+ fn compositor_state(&mut self) -> &mut CompositorState {24
+ &mut self.protocols.compositor_state25
+ }26
+27
+ fn new_surface(&mut self, surface: &WlSurface) {28
+ add_pre_commit_hook::<Self, _>(surface, |state, _dh, surface| {29
+ hold_until_readable(state, surface);30
+ });31
+ }32
+33
+ fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {34
+ if let Some(x) = client.get_data::<smithay::xwayland::XWaylandClientData>() {35
+ return &x.compositor_state;36
+ }37
+ &client38
+ .get_data::<ClientState>()39
+ .expect("every client has ClientState")40
+ .compositor_state41
+ }42
+43
+ fn commit(&mut self, surface: &WlSurface) {44
+ on_commit_buffer_handler::<Self>(surface);45
+ // Importing at commit keeps first-time dmabuf imports, which take46
+ // milliseconds on NVIDIA, out of the render racing the next vblank.47
+ if let Err(e) = import_surface_tree(&mut self.renderer, surface) {48
+ tracing::debug!("early import failed: {e}");49
+ }50
+ if !is_sync_subsurface(surface) {51
+ let mut root = surface.clone();52
+ while let Some(parent) = get_parent(&root) {53
+ root = parent;54
+ }55
+ let mapped = self.wm.id_of(&root).and_then(|id| self.wm.window(id));56
+ let window = mapped.or_else(|| {57
+ self.wm58
+ .unmapped59
+ .iter()60
+ .find(|w| crate::wm::root(w).as_ref() == Some(&root))61
+ });62
+ if let Some(window) = window {63
+ window.on_commit();64
+ }65
+ self.count_commit(&root);66
+ if let Some(id) = self.window_id(&root) {67
+ self.refresh_identity(id);68
+ if self.services.captures.wants_damage() {69
+ self.services.captures.window_damaged(id);70
+ }71
+ }72
+ }73
+ if let Some(i) = self74
+ .wm75
+ .unmapped76
+ .iter()77
+ .position(|w| w.toplevel().is_some_and(|t| t.wl_surface() == surface))78
+ {79
+ let window = self.wm.unmapped.remove(i);80
+ let toplevel = window.toplevel().cloned();81
+ self.map_window(window, None);82
+ if let Some(t) = toplevel {83
+ t.send_configure();84
+ }85
+ }86
+ self.on_buffer(surface);87
+ self.wm.popups.commit(surface);88
+ if let Some(PopupKind::Xdg(popup)) = self.wm.popups.find_popup(surface)89
+ && !popup.is_initial_configure_sent()90
+ {91
+ let _ = popup.send_configure();92
+ }93
+ self.layer_commit(surface);94
+ self.dirty.redraw = true;95
+ }96
+}97
+98
+impl BufferHandler for State {99
+ fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}100
+}101
+102
+impl ShmHandler for State {103
+ fn shm_state(&self) -> &ShmState {104
+ &self.protocols.shm_state105
+ }106
+}107
+108
+impl DmabufHandler for State {109
+ fn dmabuf_state(&mut self) -> &mut DmabufState {110
+ &mut self.protocols.dmabuf_state111
+ }112
+113
+ fn dmabuf_imported(114
+ &mut self,115
+ _global: &DmabufGlobal,116
+ dmabuf: Dmabuf,117
+ notifier: ImportNotifier,118
+ ) {119
+ self.import_for(&dmabuf, notifier);120
+ }121
+}122
+123
+impl DrmSyncobjHandler for State {124
+ fn drm_syncobj_state(&mut self) -> Option<&mut DrmSyncobjState> {125
+ self.backend.syncobj_mut()126
+ }127
+}128
+129
+/// Holds a dmabuf commit until the buffer can be read without the renderer130
+/// stalling on the client's GPU work: until its explicit acquire point131
+/// signals, or for implicitly synced buffers until their writes finish.132
+fn hold_until_readable(state: &mut State, surface: &WlSurface) {133
+ stage_shm(state, surface);134
+ let (acquire, dmabuf) = with_states(surface, |s| {135
+ let acquire = s136
+ .cached_state137
+ .get::<DrmSyncobjCachedState>()138
+ .pending()139
+ .acquire_point140
+ .clone();141
+ let dmabuf = match s142
+ .cached_state143
+ .get::<SurfaceAttributes>()144
+ .pending()145
+ .buffer146
+ .as_ref()147
+ {148
+ Some(BufferAssignment::NewBuffer(buffer)) => get_dmabuf(buffer).cloned().ok(),149
+ _ => None,150
+ };151
+ (acquire, dmabuf)152
+ });153
+ let (Some(dmabuf), Some(client)) = (dmabuf, surface.client()) else {154
+ return;155
+ };156
+ if let Some(blocker) = state.import_blocker(&dmabuf, client.clone()) {157
+ add_blocker(surface, blocker);158
+ }159
+ if let Some(point) = acquire160
+ && let Ok((blocker, source)) = point.generate_blocker()161
+ {162
+ let inserted = state.core.handle.insert_source(source, move |_, _, state| {163
+ let dh = state.core.display.clone();164
+ state165
+ .client_compositor_state(&client)166
+ .blocker_cleared(state, &dh);167
+ Ok(())168
+ });169
+ if inserted.is_ok() {170
+ add_blocker(surface, blocker);171
+ }172
+ return;173
+ }174
+ if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) {175
+ let inserted = state.core.handle.insert_source(source, move |_, _, state| {176
+ let dh = state.core.display.clone();177
+ state178
+ .client_compositor_state(&client)179
+ .blocker_cleared(state, &dh);180
+ Ok(())181
+ });182
+ if inserted.is_ok() {183
+ add_blocker(surface, blocker);184
+ }185
+ }186
+}187
+188
+/// Copies a committed shm buffer's damage on the worker, holding the commit189
+/// until it is staged. Surface damage equals buffer damage at scale 1 with no190
+/// transform, otherwise the whole buffer is staged so it covers whatever the191
+/// import uploads.192
+fn stage_shm(state: &mut State, surface: &WlSurface) {193
+ let job = with_states(surface, |s| {194
+ let mut cached = s.cached_state.get::<SurfaceAttributes>();195
+ let attrs = cached.pending();196
+ let Some(BufferAssignment::NewBuffer(buffer)) = attrs.buffer.as_ref() else {197
+ return None;198
+ };199
+ if get_dmabuf(buffer).is_ok() {200
+ return None;201
+ }202
+ let plain = attrs.buffer_scale == 1 && attrs.buffer_transform == Transform::Normal.into();203
+ let region = attrs204
+ .damage205
+ .iter()206
+ .map(|d| match d {207
+ Damage::Buffer(r) => *r,208
+ Damage::Surface(r) => {209
+ Rectangle::new((r.loc.x, r.loc.y).into(), (r.size.w, r.size.h).into())210
+ }211
+ })212
+ .reduce(|a, b| a.merge(b))213
+ .filter(|_| plain);214
+ Some((buffer.clone(), region))215
+ });216
+ let (Some((buffer, region)), Some(client)) = (job, surface.client()) else {217
+ return;218
+ };219
+ let Ok(size) = with_buffer_contents(&buffer, |_, _, d| (d.width, d.height)) else {220
+ return;221
+ };222
+ if let Some(blocker) = state.stage_blocker(&buffer, region, size, client) {223
+ add_blocker(surface, blocker);224
+ }225
+}@@ -37,7 +37,7 @@ use smithay::reexports::rustix::fs::OFlags;
3737
use smithay::utils::{DeviceFd, Point, Transform};3838
use smithay::wayland::drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd};3939
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};40
-use std::collections::{HashMap, VecDeque};40
+use std::collections::HashMap;4141
use std::path::{Path, PathBuf};4242
use std::time::Duration;4343
@@ -78,7 +78,7 @@ struct Surface {
7878
scheduled: Option<RegistrationToken>,7979
/// The vblank the frame being drawn or queued is meant for.8080
target_ns: Option<u64>,81
- budget: Budget,81
+ budget: crate::pacing::Budget,8282
/// Frames presented since `stats_since`, for the once-a-second debug line.8383
presented: u32,8484
stats_since: u64,@@ -92,79 +92,7 @@ impl Surface {
9292
/// The first vblank a render starting at `now` can still make, and the9393
/// latest moment to start it.9494
fn next_deadline(&self, now: u64) -> Option<(u64, u64)> {95
- let last = self.last_vblank_ns?;96
- let refresh = self.refresh_ns;97
- if refresh == 0 {98
- return None;99
- }100
- let budget = self.budget.ns().min(refresh);101
- let periods = (now + budget).saturating_sub(last).div_ceil(refresh).max(1);102
- let vblank = last + periods * refresh;103
- Some((vblank, vblank - budget))104
- }105
-}106
-107
-/// How long before a vblank rendering must start. A high percentile of108
-/// recent CPU render times covers the CPU side without one spike inflating109
-/// it, and a slack that grows on every missed vblank and shrinks over seconds110
-/// while frames land covers GPU time and the commit.111
-struct Budget {112
- renders: VecDeque<u64>,113
- slack_ns: u64,114
- /// The slack that last proved enough after a miss. Decay stops there115
- /// for `floor_frames` frames, so it does not keep rediscovering the same116
- /// limit one dropped frame at a time.117
- floor_ns: u64,118
- floor_frames: u64,119
-}120
-121
-impl Budget {122
- const KEEP: usize = 32;123
- const MIN_SLACK_NS: u64 = 1_000_000;124
- const STEP_NS: u64 = 500_000;125
- const DECAY_NS: u64 = 1_000;126
- const FLOOR_NS: u64 = 30_000_000_000;127
-128
- fn new() -> Self {129
- Self {130
- renders: VecDeque::with_capacity(Self::KEEP),131
- slack_ns: 2_000_000,132
- floor_ns: 0,133
- floor_frames: 0,134
- }135
- }136
-137
- fn ns(&self) -> u64 {138
- let mut renders: Vec<u64> = self.renders.iter().copied().collect();139
- renders.sort_unstable();140
- let p90 = renders141
- .get(renders.len().saturating_sub(1) * 9 / 10)142
- .copied()143
- .unwrap_or(0);144
- p90 + self.slack_ns145
- }146
-147
- fn record(&mut self, render_ns: u64) {148
- if self.renders.len() == Self::KEEP {149
- self.renders.pop_front();150
- }151
- self.renders.push_back(render_ns);152
- }153
-154
- fn missed(&mut self, refresh_ns: u64) {155
- self.slack_ns = (self.slack_ns + Self::STEP_NS).min(refresh_ns / 2);156
- self.floor_ns = self.slack_ns;157
- self.floor_frames = Self::FLOOR_NS / refresh_ns.max(1);158
- }159
-160
- fn landed(&mut self) {161
- self.floor_frames = self.floor_frames.saturating_sub(1);162
- let floor = if self.floor_frames > 0 {163
- self.floor_ns164
- } else {165
- Self::MIN_SLACK_NS166
- };167
- self.slack_ns = self.slack_ns.saturating_sub(Self::DECAY_NS).max(floor);95
+ crate::pacing::deadline(self.last_vblank_ns?, self.refresh_ns, self.budget.ns(), now)16896
}16997
}17098
@@ -424,7 +352,7 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
424352
last_vblank_ns: None,425353
scheduled: None,426354
target_ns: None,427
- budget: Budget::new(),355
+ budget: crate::pacing::Budget::new(),428356
presented: 0,429357
stats_since: 0,430358
vrr_capable,@@ -97,3 +97,5 @@ impl Protocols {
9797
}9898
}9999
}100
+101
+smithay::delegate_dispatch2!(State);@@ -1,463 +0,0 @@
1
-use crate::state::{ClientState, State};2
-use smithay::backend::allocator::dmabuf::Dmabuf;3
-use smithay::backend::renderer::utils::{import_surface_tree, on_commit_buffer_handler};4
-use smithay::desktop::{PopupKind, Window};5
-use smithay::input::dnd::{DndGrabHandler, GrabType, Source};6
-use smithay::input::pointer::CursorImageStatus;7
-use smithay::input::{Seat, SeatHandler, SeatState};8
-use smithay::reexports::calloop::Interest;9
-use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;10
-use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode;11
-use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;12
-use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;13
-use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;14
-use smithay::reexports::wayland_server::{Client, Resource};15
-use smithay::utils::Serial;16
-use smithay::wayland::buffer::BufferHandler;17
-use smithay::wayland::compositor::{18
- BufferAssignment, CompositorClientState, CompositorHandler, CompositorState, Damage,19
- SurfaceAttributes, add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface,20
- with_states,21
-};22
-use smithay::wayland::drm_syncobj::{DrmSyncobjCachedState, DrmSyncobjHandler, DrmSyncobjState};23
-use smithay::wayland::dmabuf::{24
- DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf,25
-};26
-use smithay::output::Output;27
-use smithay::wayland::output::OutputHandler;28
-use smithay::wayland::seat::WaylandFocus;29
-use smithay::wayland::selection::SelectionHandler;30
-use smithay::wayland::selection::primary_selection::{31
- PrimarySelectionHandler, PrimarySelectionState, set_primary_focus,32
-};33
-use smithay::wayland::selection::data_device::{34
- DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus,35
-};36
-use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;37
-use smithay::wayland::shell::xdg::{38
- PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,39
-};40
-use monolith_protocols::workspace::{Action, WorkspaceHandler, WorkspaceState};41
-use smithay::utils::{Rectangle, Transform};42
-use smithay::wayland::shm::{ShmHandler, ShmState, with_buffer_contents};43
-44
-impl CompositorHandler for State {45
- fn compositor_state(&mut self) -> &mut CompositorState {46
- &mut self.protocols.compositor_state47
- }48
-49
- fn new_surface(&mut self, surface: &WlSurface) {50
- add_pre_commit_hook::<Self, _>(surface, |state, _dh, surface| {51
- hold_until_readable(state, surface);52
- });53
- }54
-55
- fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {56
- if let Some(x) = client.get_data::<smithay::xwayland::XWaylandClientData>() {57
- return &x.compositor_state;58
- }59
- &client60
- .get_data::<ClientState>()61
- .expect("every client has ClientState")62
- .compositor_state63
- }64
-65
- fn commit(&mut self, surface: &WlSurface) {66
- on_commit_buffer_handler::<Self>(surface);67
- // Importing at commit keeps first-time dmabuf imports, which take68
- // milliseconds on NVIDIA, out of the render racing the next vblank.69
- if let Err(e) = import_surface_tree(&mut self.renderer, surface) {70
- tracing::debug!("early import failed: {e}");71
- }72
- if !is_sync_subsurface(surface) {73
- let mut root = surface.clone();74
- while let Some(parent) = get_parent(&root) {75
- root = parent;76
- }77
- let mapped = self.wm.id_of(&root).and_then(|id| self.wm.window(id));78
- let window = mapped.or_else(|| {79
- self.wm80
- .unmapped81
- .iter()82
- .find(|w| crate::wm::root(w).as_ref() == Some(&root))83
- });84
- if let Some(window) = window {85
- window.on_commit();86
- }87
- self.count_commit(&root);88
- if let Some(id) = self.window_id(&root) {89
- self.refresh_identity(id);90
- if self.services.captures.wants_damage() {91
- self.services.captures.window_damaged(id);92
- }93
- }94
- }95
- if let Some(i) = self96
- .wm97
- .unmapped98
- .iter()99
- .position(|w| w.toplevel().is_some_and(|t| t.wl_surface() == surface))100
- {101
- let window = self.wm.unmapped.remove(i);102
- let toplevel = window.toplevel().cloned();103
- self.map_window(window, None);104
- if let Some(t) = toplevel {105
- t.send_configure();106
- }107
- }108
- self.on_buffer(surface);109
- self.wm.popups.commit(surface);110
- if let Some(PopupKind::Xdg(popup)) = self.wm.popups.find_popup(surface)111
- && !popup.is_initial_configure_sent()112
- {113
- let _ = popup.send_configure();114
- }115
- self.layer_commit(surface);116
- self.dirty.redraw = true;117
- }118
-}119
-120
-impl BufferHandler for State {121
- fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}122
-}123
-124
-impl ShmHandler for State {125
- fn shm_state(&self) -> &ShmState {126
- &self.protocols.shm_state127
- }128
-}129
-130
-impl DmabufHandler for State {131
- fn dmabuf_state(&mut self) -> &mut DmabufState {132
- &mut self.protocols.dmabuf_state133
- }134
-135
- fn dmabuf_imported(136
- &mut self,137
- _global: &DmabufGlobal,138
- dmabuf: Dmabuf,139
- notifier: ImportNotifier,140
- ) {141
- self.import_for(&dmabuf, notifier);142
- }143
-}144
-145
-impl XdgShellHandler for State {146
- fn xdg_shell_state(&mut self) -> &mut XdgShellState {147
- &mut self.protocols.xdg_shell_state148
- }149
-150
- fn new_toplevel(&mut self, surface: ToplevelSurface) {151
- self.wm.unmapped.push(Window::new_wayland_window(surface));152
- }153
-154
- fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {155
- self.unmap_window(surface.wl_surface());156
- }157
-158
- fn fullscreen_request(&mut self, surface: ToplevelSurface, _output: Option<WlOutput>) {159
- self.set_fullscreen(surface.wl_surface(), true);160
- }161
-162
- fn unfullscreen_request(&mut self, surface: ToplevelSurface) {163
- self.set_fullscreen(surface.wl_surface(), false);164
- }165
-166
- fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {167
- surface.with_pending_state(|s| s.geometry = positioner.get_geometry());168
- let _ = self.wm.popups.track_popup(PopupKind::Xdg(surface));169
- }170
-171
- fn reposition_request(172
- &mut self,173
- surface: PopupSurface,174
- positioner: PositionerState,175
- token: u32,176
- ) {177
- surface.with_pending_state(|s| {178
- s.geometry = positioner.get_geometry();179
- s.positioner = positioner;180
- });181
- surface.send_repositioned(token);182
- }183
-184
- fn grab(&mut self, _surface: PopupSurface, _seat: WlSeat, _serial: Serial) {}185
-}186
-187
-impl SeatHandler for State {188
- type KeyboardFocus = crate::focus::Focus;189
- type PointerFocus = WlSurface;190
- type TouchFocus = WlSurface;191
-192
- fn seat_state(&mut self) -> &mut SeatState<State> {193
- &mut self.input.seat_state194
- }195
-196
- fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {197
- self.input.cursor_status = image;198
- self.dirty.redraw = true;199
- }200
-201
- fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&crate::focus::Focus>) {202
- let client = focused203
- .and_then(|f| f.wl_surface())204
- .and_then(|s| self.core.display.get_client(s.id()).ok());205
- set_data_device_focus(&self.core.display, seat, client.clone());206
- set_primary_focus(&self.core.display, seat, client);207
- }208
-}209
-210
-impl SelectionHandler for State {211
- type SelectionUserData = ();212
-}213
-214
-impl DataDeviceHandler for State {215
- fn data_device_state(&mut self) -> &mut DataDeviceState {216
- &mut self.protocols.data_device_state217
- }218
-}219
-220
-impl PrimarySelectionHandler for State {221
- fn primary_selection_state(&mut self) -> &mut PrimarySelectionState {222
- &mut self.protocols.primary_selection_state223
- }224
-}225
-226
-impl smithay::wayland::selection::wlr_data_control::DataControlHandler for State {227
- fn data_control_state(228
- &mut self,229
- ) -> &mut smithay::wayland::selection::wlr_data_control::DataControlState {230
- &mut self.protocols.wlr_data_control_state231
- }232
-}233
-234
-impl smithay::wayland::selection::ext_data_control::DataControlHandler for State {235
- fn data_control_state(236
- &mut self,237
- ) -> &mut smithay::wayland::selection::ext_data_control::DataControlState {238
- &mut self.protocols.ext_data_control_state239
- }240
-}241
-242
-impl DndGrabHandler for State {}243
-244
-impl smithay::input::tablet::TabletSeatHandler for State {245
- type ToolFocus = WlSurface;246
-}247
-248
-impl WaylandDndGrabHandler for State {249
- fn dnd_requested<S: Source>(250
- &mut self,251
- source: S,252
- _icon: Option<WlSurface>,253
- _seat: Seat<Self>,254
- _serial: Serial,255
- _type: GrabType,256
- ) {257
- source.cancel();258
- }259
-}260
-261
-impl OutputHandler for State {262
- /// Workspace and toplevel lists name outputs by the client's wl_output,263
- /// so a newly bound one needs announcing.264
- fn output_bound(&mut self, _output: Output, _wl_output: WlOutput) {265
- self.dirty.snapshot = true;266
- }267
-}268
-269
-impl smithay::wayland::pointer_constraints::PointerConstraintsHandler for State {270
- fn new_constraint(271
- &mut self,272
- _surface: &WlSurface,273
- _pointer: &smithay::input::pointer::PointerHandle<Self>,274
- ) {275
- }276
-277
- fn cursor_position_hint(278
- &mut self,279
- _surface: &WlSurface,280
- _pointer: &smithay::input::pointer::PointerHandle<Self>,281
- _location: smithay::utils::Point<f64, smithay::utils::Logical>,282
- ) {283
- }284
-}285
-286
-smithay::delegate_dispatch2!(State);287
-288
-impl DrmSyncobjHandler for State {289
- fn drm_syncobj_state(&mut self) -> Option<&mut DrmSyncobjState> {290
- self.backend.syncobj_mut()291
- }292
-}293
-294
-/// Holds a dmabuf commit until the buffer can be read without the renderer295
-/// stalling on the client's GPU work: until its explicit acquire point296
-/// signals, or for implicitly synced buffers until their writes finish.297
-fn hold_until_readable(state: &mut State, surface: &WlSurface) {298
- stage_shm(state, surface);299
- let (acquire, dmabuf) = with_states(surface, |s| {300
- let acquire = s301
- .cached_state302
- .get::<DrmSyncobjCachedState>()303
- .pending()304
- .acquire_point305
- .clone();306
- let dmabuf = match s307
- .cached_state308
- .get::<SurfaceAttributes>()309
- .pending()310
- .buffer311
- .as_ref()312
- {313
- Some(BufferAssignment::NewBuffer(buffer)) => get_dmabuf(buffer).cloned().ok(),314
- _ => None,315
- };316
- (acquire, dmabuf)317
- });318
- let (Some(dmabuf), Some(client)) = (dmabuf, surface.client()) else {319
- return;320
- };321
- if let Some(blocker) = state.import_blocker(&dmabuf, client.clone()) {322
- add_blocker(surface, blocker);323
- }324
- if let Some(point) = acquire325
- && let Ok((blocker, source)) = point.generate_blocker()326
- {327
- let inserted = state.core.handle.insert_source(source, move |_, _, state| {328
- let dh = state.core.display.clone();329
- state330
- .client_compositor_state(&client)331
- .blocker_cleared(state, &dh);332
- Ok(())333
- });334
- if inserted.is_ok() {335
- add_blocker(surface, blocker);336
- }337
- return;338
- }339
- if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) {340
- let inserted = state.core.handle.insert_source(source, move |_, _, state| {341
- let dh = state.core.display.clone();342
- state343
- .client_compositor_state(&client)344
- .blocker_cleared(state, &dh);345
- Ok(())346
- });347
- if inserted.is_ok() {348
- add_blocker(surface, blocker);349
- }350
- }351
-}352
-353
-/// Copies a committed shm buffer's damage on the worker, holding the commit354
-/// until it is staged. Surface damage equals buffer damage at scale 1 with no355
-/// transform, otherwise the whole buffer is staged so it covers whatever the356
-/// import uploads.357
-fn stage_shm(state: &mut State, surface: &WlSurface) {358
- let job = with_states(surface, |s| {359
- let mut cached = s.cached_state.get::<SurfaceAttributes>();360
- let attrs = cached.pending();361
- let Some(BufferAssignment::NewBuffer(buffer)) = attrs.buffer.as_ref() else {362
- return None;363
- };364
- if get_dmabuf(buffer).is_ok() {365
- return None;366
- }367
- let plain = attrs.buffer_scale == 1 && attrs.buffer_transform == Transform::Normal.into();368
- let region = attrs369
- .damage370
- .iter()371
- .map(|d| match d {372
- Damage::Buffer(r) => *r,373
- Damage::Surface(r) => {374
- Rectangle::new((r.loc.x, r.loc.y).into(), (r.size.w, r.size.h).into())375
- }376
- })377
- .reduce(|a, b| a.merge(b))378
- .filter(|_| plain);379
- Some((buffer.clone(), region))380
- });381
- let (Some((buffer, region)), Some(client)) = (job, surface.client()) else {382
- return;383
- };384
- let Ok(size) = with_buffer_contents(&buffer, |_, _, d| (d.width, d.height)) else {385
- return;386
- };387
- if let Some(blocker) = state.stage_blocker(&buffer, region, size, client) {388
- add_blocker(surface, blocker);389
- }390
-}391
-392
-impl XdgDecorationHandler for State {393
- fn new_decoration(&mut self, toplevel: ToplevelSurface) {394
- server_side(&toplevel);395
- }396
-397
- fn request_mode(&mut self, toplevel: ToplevelSurface, _mode: DecorationMode) {398
- server_side(&toplevel);399
- }400
-401
- fn unset_mode(&mut self, toplevel: ToplevelSurface) {402
- server_side(&toplevel);403
- }404
-}405
-406
-/// monolith draws no titlebars, and clients are told so whatever they ask for.407
-fn server_side(toplevel: &ToplevelSurface) {408
- toplevel.with_pending_state(|s| s.decoration_mode = Some(DecorationMode::ServerSide));409
- if toplevel.is_initial_configure_sent() {410
- toplevel.send_pending_configure();411
- }412
-}413
-414
-impl State {415
- fn set_fullscreen(&mut self, surface: &WlSurface, on: bool) {416
- if let Some(id) = self.window_id(surface)417
- && self418
- .wm419
- .layout420
- .window(id)421
- .is_some_and(|w| w.fullscreen != on)422
- {423
- self.wm.layout.toggle_fullscreen(id);424
- self.apply_layout();425
- }426
- }427
-}428
-429
-impl WorkspaceHandler for State {430
- fn workspace_state(&mut self) -> &mut WorkspaceState {431
- &mut self.protocols.workspaces432
- }433
-434
- fn workspace_actions(&mut self, actions: Vec<Action>) {435
- for action in actions {436
- let (group, tag) = match &action {437
- Action::Activate { group, workspace } | Action::Deactivate { group, workspace } => {438
- (group, workspace)439
- }440
- Action::Create { group, name } => (group, name),441
- };442
- let Some(o) = self443
- .wm444
- .layout445
- .outputs()446
- .find(|(_, out)| out.name == *group)447
- .map(|(o, _)| o)448
- else {449
- continue;450
- };451
- let t = self.wm.layout.tag(tag);452
- match action {453
- Action::Activate { .. } | Action::Create { .. } => self.wm.layout.view(o, t),454
- Action::Deactivate { .. } => {455
- if self.wm.layout.is_viewed(o, t) {456
- self.wm.layout.toggle_view(o, t);457
- }458
- }459
- }460
- }461
- self.apply_layout();462
- }463
-}@@ -3,12 +3,12 @@ mod anim;
33
mod backend;44
mod capture;55
mod commands;6
+mod commit;67
mod cursor;78
mod drm;89
mod focus;910
mod frame;1011
mod globals;11
-mod handlers;1212
mod idle;1313
mod import;1414
mod input;@@ -17,11 +17,16 @@ mod launch;
1717
mod layers;1818
mod lock;1919
mod nested;20
+mod pacing;2021
mod scene;22
+mod seat;2123
mod state;2224
mod stats;2325
mod toplevels;26
+mod windows;2427
mod wm;28
+mod workspaces;29
+mod xdg;2530
mod xwayland;2631
2732
#[cfg(feature = "spans")]@@ -63,10 +63,8 @@ impl Host {
6363
/// vblank after `now`.6464
pub fn predict(&self, now: u64) -> u64 {6565
match self.last_present_ns.filter(|_| self.monotonic) {66
- Some(last) if self.refresh_ns > 0 && last <= now => {67
- last + ((now - last) / self.refresh_ns + 1) * self.refresh_ns68
- }69
- _ => now,66
+ Some(last) => crate::pacing::next_vblank(last, self.refresh_ns, now),67
+ None => now,7068
}7169
}7270
}@@ -0,0 +1,87 @@
1
+//! Frame timing that needs no backend state, shared by DRM and nested.2
+3
+use std::collections::VecDeque;4
+5
+/// The first vblank after `last` that a render starting at `now` with6
+/// `budget` to spare can still make, and the latest moment to start it.7
+pub fn deadline(last: u64, refresh: u64, budget: u64, now: u64) -> Option<(u64, u64)> {8
+ if refresh == 0 {9
+ return None;10
+ }11
+ let budget = budget.min(refresh);12
+ let periods = (now + budget).saturating_sub(last).div_ceil(refresh).max(1);13
+ let vblank = last + periods * refresh;14
+ Some((vblank, vblank - budget))15
+}16
+17
+/// The first vblank strictly after `now`.18
+pub fn next_vblank(last: u64, refresh: u64, now: u64) -> u64 {19
+ if refresh == 0 || last > now {20
+ return now;21
+ }22
+ last + ((now - last) / refresh + 1) * refresh23
+}24
+25
+/// How long before a vblank rendering must start. A high percentile of26
+/// recent CPU render times covers the CPU side without one spike inflating27
+/// it, and a slack that grows on every missed vblank and shrinks over seconds28
+/// while frames land covers GPU time and the commit.29
+pub struct Budget {30
+ renders: VecDeque<u64>,31
+ slack_ns: u64,32
+ /// The slack that last proved enough after a miss. Decay stops there33
+ /// for `floor_frames` frames, so it does not keep rediscovering the same34
+ /// limit one dropped frame at a time.35
+ floor_ns: u64,36
+ floor_frames: u64,37
+}38
+39
+impl Budget {40
+ const KEEP: usize = 32;41
+ const MIN_SLACK_NS: u64 = 1_000_000;42
+ const STEP_NS: u64 = 500_000;43
+ const DECAY_NS: u64 = 1_000;44
+ const FLOOR_NS: u64 = 30_000_000_000;45
+46
+ pub fn new() -> Self {47
+ Self {48
+ renders: VecDeque::with_capacity(Self::KEEP),49
+ slack_ns: 2_000_000,50
+ floor_ns: 0,51
+ floor_frames: 0,52
+ }53
+ }54
+55
+ pub fn ns(&self) -> u64 {56
+ let mut renders: Vec<u64> = self.renders.iter().copied().collect();57
+ renders.sort_unstable();58
+ let p90 = renders59
+ .get(renders.len().saturating_sub(1) * 9 / 10)60
+ .copied()61
+ .unwrap_or(0);62
+ p90 + self.slack_ns63
+ }64
+65
+ pub fn record(&mut self, render_ns: u64) {66
+ if self.renders.len() == Self::KEEP {67
+ self.renders.pop_front();68
+ }69
+ self.renders.push_back(render_ns);70
+ }71
+72
+ pub fn missed(&mut self, refresh_ns: u64) {73
+ self.slack_ns = (self.slack_ns + Self::STEP_NS).min(refresh_ns / 2);74
+ self.floor_ns = self.slack_ns;75
+ self.floor_frames = Self::FLOOR_NS / refresh_ns.max(1);76
+ }77
+78
+ pub fn landed(&mut self) {79
+ self.floor_frames = self.floor_frames.saturating_sub(1);80
+ let floor = if self.floor_frames > 0 {81
+ self.floor_ns82
+ } else {83
+ Self::MIN_SLACK_NS84
+ };85
+ self.slack_ns = self.slack_ns.saturating_sub(Self::DECAY_NS).max(floor);86
+ }87
+}@@ -0,0 +1,106 @@
1
+use crate::state::State;2
+use smithay::input::dnd::{DndGrabHandler, GrabType, Source};3
+use smithay::input::pointer::CursorImageStatus;4
+use smithay::input::{Seat, SeatHandler, SeatState};5
+use smithay::reexports::wayland_server::Resource;6
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;7
+use smithay::utils::Serial;8
+use smithay::wayland::seat::WaylandFocus;9
+use smithay::wayland::selection::SelectionHandler;10
+use smithay::wayland::selection::data_device::{11
+ DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus,12
+};13
+use smithay::wayland::selection::primary_selection::{14
+ PrimarySelectionHandler, PrimarySelectionState, set_primary_focus,15
+};16
+17
+impl SeatHandler for State {18
+ type KeyboardFocus = crate::focus::Focus;19
+ type PointerFocus = WlSurface;20
+ type TouchFocus = WlSurface;21
+22
+ fn seat_state(&mut self) -> &mut SeatState<State> {23
+ &mut self.input.seat_state24
+ }25
+26
+ fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {27
+ self.input.cursor_status = image;28
+ self.dirty.redraw = true;29
+ }30
+31
+ fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&crate::focus::Focus>) {32
+ let client = focused33
+ .and_then(|f| f.wl_surface())34
+ .and_then(|s| self.core.display.get_client(s.id()).ok());35
+ set_data_device_focus(&self.core.display, seat, client.clone());36
+ set_primary_focus(&self.core.display, seat, client);37
+ }38
+}39
+40
+impl SelectionHandler for State {41
+ type SelectionUserData = ();42
+}43
+44
+impl DataDeviceHandler for State {45
+ fn data_device_state(&mut self) -> &mut DataDeviceState {46
+ &mut self.protocols.data_device_state47
+ }48
+}49
+50
+impl PrimarySelectionHandler for State {51
+ fn primary_selection_state(&mut self) -> &mut PrimarySelectionState {52
+ &mut self.protocols.primary_selection_state53
+ }54
+}55
+56
+impl smithay::wayland::selection::wlr_data_control::DataControlHandler for State {57
+ fn data_control_state(58
+ &mut self,59
+ ) -> &mut smithay::wayland::selection::wlr_data_control::DataControlState {60
+ &mut self.protocols.wlr_data_control_state61
+ }62
+}63
+64
+impl smithay::wayland::selection::ext_data_control::DataControlHandler for State {65
+ fn data_control_state(66
+ &mut self,67
+ ) -> &mut smithay::wayland::selection::ext_data_control::DataControlState {68
+ &mut self.protocols.ext_data_control_state69
+ }70
+}71
+72
+impl DndGrabHandler for State {}73
+74
+impl smithay::input::tablet::TabletSeatHandler for State {75
+ type ToolFocus = WlSurface;76
+}77
+78
+impl WaylandDndGrabHandler for State {79
+ fn dnd_requested<S: Source>(80
+ &mut self,81
+ source: S,82
+ _icon: Option<WlSurface>,83
+ _seat: Seat<Self>,84
+ _serial: Serial,85
+ _type: GrabType,86
+ ) {87
+ source.cancel();88
+ }89
+}90
+91
+impl smithay::wayland::pointer_constraints::PointerConstraintsHandler for State {92
+ fn new_constraint(93
+ &mut self,94
+ _surface: &WlSurface,95
+ _pointer: &smithay::input::pointer::PointerHandle<Self>,96
+ ) {97
+ }98
+99
+ fn cursor_position_hint(100
+ &mut self,101
+ _surface: &WlSurface,102
+ _pointer: &smithay::input::pointer::PointerHandle<Self>,103
+ _location: smithay::utils::Point<f64, smithay::utils::Logical>,104
+ ) {105
+ }106
+}@@ -1,15 +1,11 @@
1
-use crate::anim::{Animator, Ghost};1
+use crate::anim::Animator;22
use crate::commands::Binds;33
use crate::focus::Focus;44
use crate::globals::Protocols;5
-use crate::wm::{Kind, Wm, configure, identity, root};5
+use crate::wm::{Kind, Wm, configure};66
use monolith_config::Config;77
use monolith_layout::{Layout, OutputId, Rect, WindowId};8
-use monolith_protocols::workspace::{Group, Snapshot, Workspace};98
use monolith_render_vk::VulkanRenderer;10
-use monolith_render_vk::VulkanTexture;11
-use smithay::backend::renderer::Renderer;12
-use smithay::backend::renderer::utils::{import_surface_tree, with_renderer_surface_state};139
use smithay::desktop::utils::under_from_surface_tree;1410
use smithay::desktop::{Window, WindowSurfaceType};1511
use smithay::input::keyboard::{Keycode, XkbConfig};@@ -17,16 +13,15 @@ use smithay::input::pointer::CursorImageStatus;
1713
use smithay::input::{Seat, SeatState};1814
use smithay::reexports::calloop::LoopHandle;1915
use smithay::reexports::calloop::generic::Generic;20
-use smithay::reexports::calloop::timer::{TimeoutAction, Timer};2116
use smithay::reexports::calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction};2217
use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason};2318
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;24
-use smithay::reexports::wayland_server::{Display, DisplayHandle, Resource};25
-use smithay::utils::{Clock, Logical, Monotonic, Point, Rectangle, SERIAL_COUNTER, Size};19
+use smithay::reexports::wayland_server::{Display, DisplayHandle};20
+use smithay::utils::{Clock, Logical, Monotonic, Point, SERIAL_COUNTER, Size};2621
use smithay::wayland::compositor::CompositorClientState;2722
use smithay::wayland::shell::wlr_layer::Layer;2823
use smithay::wayland::socket::ListeningSocketSource;29
-use std::collections::{HashMap, HashSet};24
+use std::collections::HashMap;3025
use std::ffi::OsString;3126
use std::path::PathBuf;3227
use std::sync::Arc;@@ -230,180 +225,6 @@ impl State {
230225
}231226
}232227
233
- /// Adds a toplevel to the layout on its first commit, where its app_id234
- /// and title are known and the initial configure can carry its size.235
- pub fn map_window(&mut self, window: Window, float: Option<Rectangle<i32, Logical>>) {236
- let (app_id, title) = identity(&window);237
- let rule = self.settings.config.placement(&app_id, &title);238
- let pid = match window.x11_surface() {239
- Some(x) => x.pid(),240
- None => window241
- .toplevel()242
- .and_then(|t| t.wl_surface().client())243
- .and_then(|c| c.get_credentials(&self.core.display).ok())244
- .map(|c| c.pid as u32),245
- };246
- let launch = pid.and_then(|pid| self.services.launches.find(pid));247
- let output = rule248
- .output249
- .as_deref()250
- .and_then(|name| {251
- self.wm252
- .layout253
- .outputs()254
- .find(|(_, o)| o.name == name)255
- .map(|(id, _)| id)256
- })257
- .or(launch.and_then(|l| l.0));258
- let tag = match rule.tag.as_deref() {259
- Some(t) => Some(self.wm.layout.tag(t)),260
- None => launch.map(|l| l.1),261
- };262
- let float = float.map(|r| {263
- Rect::new(264
- f64::from(r.loc.x),265
- f64::from(r.loc.y),266
- f64::from(r.size.w),267
- f64::from(r.size.h),268
- )269
- });270
- let id = self.wm.layout.add_window(output, tag, float);271
- if rule.floating == Some(true) {272
- self.wm.layout.toggle_floating(id);273
- }274
- if rule.sticky == Some(true) {275
- self.wm.layout.toggle_sticky(id);276
- }277
- if let Some(name) = &rule.scratchpad {278
- self.wm.layout.scratchpad_add(id, name);279
- }280
- let by_rule = rule.tag.is_some() || rule.output.is_some();281
- self.wm.insert(id, window, by_rule, app_id, title);282
- self.await_first_buffer(id);283
- if self.wm.layout.visible(id) {284
- self.wm.layout.focus(id);285
- }286
- self.apply_layout();287
- }288
-289
- /// Holds a new window on its seed until its first buffer lands, so the290
- /// spawn animation never plays over an empty slot. Clients that are slow291
- /// to draw get it anyway after a while.292
- fn await_first_buffer(&mut self, id: WindowId) {293
- let timeout = Timer::from_duration(Duration::from_millis(300));294
- let _ = self.core.handle.insert_source(timeout, move |_, _, state| {295
- state.first_buffer(id);296
- TimeoutAction::Drop297
- });298
- }299
-300
- /// Called on commits, starts the spawn of a window that now has a buffer.301
- pub fn on_buffer(&mut self, surface: &WlSurface) {302
- if !self.wm.any_awaiting() {303
- return;304
- }305
- let has_buffer =306
- with_renderer_surface_state(surface, |s| s.buffer_size().is_some()).unwrap_or(false);307
- if has_buffer && let Some(id) = self.window_id(surface) {308
- self.first_buffer(id);309
- }310
- }311
-312
- fn first_buffer(&mut self, id: WindowId) {313
- let Some(w) = self.wm.get_mut(id) else {314
- return;315
- };316
- w.placed_by_rule = false;317
- if std::mem::take(&mut w.awaiting_buffer) {318
- self.wm.anim.spawned(id, now_ns());319
- self.apply_layout();320
- }321
- }322
-323
- pub fn unmap_window(&mut self, surface: &WlSurface) {324
- self.wm325
- .unmapped326
- .retain(|w| root(w).as_ref() != Some(surface));327
- if let Some(id) = self.window_id(surface) {328
- self.remove_window(id);329
- }330
- }331
-332
- pub fn remove_window(&mut self, id: WindowId) {333
- if let Some(state) = self.wm.remove(id) {334
- self.wm.space.unmap_elem(&state.window);335
- self.leave_ghost(id, &state.window);336
- }337
- self.wm.layout.remove_window(id);338
- self.apply_layout();339
- }340
-341
- /// Keeps a visible tiled window's last frame shrinking to its seed while342
- /// its neighbours close over it.343
- fn leave_ghost(&mut self, id: WindowId, window: &Window) {344
- let Some(o) = self.wm.layout.window(id).and_then(|w| w.output) else {345
- return;346
- };347
- let tiled = self348
- .wm349
- .layout350
- .output(o)351
- .is_some_and(|out| out.tree.contains(id));352
- if !tiled || !self.wm.layout.visible(id) {353
- return;354
- }355
- let Some(seed) = self356
- .wm357
- .layout358
- .arrange_hiding(o, &[id])359
- .tiled360
- .into_iter()361
- .find(|p| p.window == id)362
- else {363
- return;364
- };365
- let Some(surface) = root(window) else {366
- return;367
- };368
- let _ = import_surface_tree(&mut self.renderer, &surface);369
- let context = self.renderer.context_id();370
- let snapshot = with_renderer_surface_state(&surface, |s| {371
- s.texture::<VulkanTexture>(context.clone())372
- .cloned()373
- .map(|t| (t, s.buffer_scale(), s.buffer_transform()))374
- })375
- .flatten();376
- if let Some((texture, scale, transform)) = snapshot {377
- self.wm.anim.add_ghost(Ghost {378
- id,379
- output: o,380
- seed: seed.rect,381
- texture,382
- scale,383
- transform,384
- geometry: window.geometry(),385
- born: now_ns(),386
- });387
- }388
- }389
-390
- /// Picks up a changed title or app id, which clients send at any time.391
- pub fn refresh_identity(&mut self, id: WindowId) {392
- let Some(w) = self.wm.get_mut(id) else {393
- return;394
- };395
- let (app_id, title) = identity(&w.window);396
- if w.app_id != app_id || w.title != title {397
- w.app_id = app_id;398
- w.title = title;399
- self.dirty.snapshot = true;400
- }401
- }402
-403
- pub fn window_id(&self, surface: &WlSurface) -> Option<WindowId> {404
- self.wm.id_of(surface)405
- }406
-407228
/// Pushes the layout's target geometry into the space and to clients.408229
pub fn apply_layout(&mut self) {409230
let now = now_ns();@@ -556,43 +377,6 @@ impl State {
556377
}557378
}558379
559
- fn publish_workspaces(&mut self) {560
- let l = &self.wm.layout;561
- let occupied: HashSet<(OutputId, monolith_layout::TagId)> = l562
- .windows()563
- .filter(|(_, w)| w.scratchpad.is_none())564
- .filter_map(|(_, w)| Some((w.output?, w.tag)))565
- .collect();566
- let groups = l567
- .outputs()568
- .filter_map(|(o, _)| {569
- let output = self.backend.output(o)?;570
- // Bars read a tag's existence as having windows, as with571
- // workspaces that come and go, so empty unviewed tags are left out.572
- let workspaces = l573
- .tags()574
- .enumerate()575
- .filter(|&(_, (t, _))| {576
- occupied.contains(&(o, t)) || (t != l.homeless() && l.is_viewed(o, t))577
- })578
- .map(|(i, (t, tag))| Workspace {579
- key: tag.name.clone(),580
- name: tag.name.clone(),581
- coordinates: vec![i as u32],582
- active: l.is_viewed(o, t),583
- urgent: tag.urgent,584
- hidden: false,585
- })586
- .collect();587
- Some(Group { output, workspaces })588
- })589
- .collect();590
- let dh = self.core.display.clone();591
- self.protocols592
- .workspaces593
- .update::<State>(&dh, Snapshot { groups });594
- }595
-596380
pub fn reconfigure_keyboard(&mut self) {597381
let kb = self.settings.config.keyboard.clone();598382
if let Some(keyboard) = self.input.seat.get_keyboard() {@@ -0,0 +1,189 @@
1
+use crate::anim::Ghost;2
+use crate::state::{State, now_ns};3
+use crate::wm::{identity, root};4
+use monolith_layout::{Rect, WindowId};5
+use monolith_render_vk::VulkanTexture;6
+use smithay::backend::renderer::Renderer;7
+use smithay::backend::renderer::utils::{import_surface_tree, with_renderer_surface_state};8
+use smithay::desktop::Window;9
+use smithay::reexports::calloop::timer::{TimeoutAction, Timer};10
+use smithay::reexports::wayland_server::Resource;11
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;12
+use smithay::utils::{Logical, Rectangle};13
+use std::time::Duration;14
+15
+impl State {16
+ /// Adds a toplevel to the layout on its first commit, where its app_id17
+ /// and title are known and the initial configure can carry its size.18
+ pub fn map_window(&mut self, window: Window, float: Option<Rectangle<i32, Logical>>) {19
+ let (app_id, title) = identity(&window);20
+ let rule = self.settings.config.placement(&app_id, &title);21
+ let pid = match window.x11_surface() {22
+ Some(x) => x.pid(),23
+ None => window24
+ .toplevel()25
+ .and_then(|t| t.wl_surface().client())26
+ .and_then(|c| c.get_credentials(&self.core.display).ok())27
+ .map(|c| c.pid as u32),28
+ };29
+ let launch = pid.and_then(|pid| self.services.launches.find(pid));30
+ let output = rule31
+ .output32
+ .as_deref()33
+ .and_then(|name| {34
+ self.wm35
+ .layout36
+ .outputs()37
+ .find(|(_, o)| o.name == name)38
+ .map(|(id, _)| id)39
+ })40
+ .or(launch.and_then(|l| l.0));41
+ let tag = match rule.tag.as_deref() {42
+ Some(t) => Some(self.wm.layout.tag(t)),43
+ None => launch.map(|l| l.1),44
+ };45
+ let float = float.map(|r| {46
+ Rect::new(47
+ f64::from(r.loc.x),48
+ f64::from(r.loc.y),49
+ f64::from(r.size.w),50
+ f64::from(r.size.h),51
+ )52
+ });53
+ let id = self.wm.layout.add_window(output, tag, float);54
+ if rule.floating == Some(true) {55
+ self.wm.layout.toggle_floating(id);56
+ }57
+ if rule.sticky == Some(true) {58
+ self.wm.layout.toggle_sticky(id);59
+ }60
+ if let Some(name) = &rule.scratchpad {61
+ self.wm.layout.scratchpad_add(id, name);62
+ }63
+ let by_rule = rule.tag.is_some() || rule.output.is_some();64
+ self.wm.insert(id, window, by_rule, app_id, title);65
+ self.await_first_buffer(id);66
+ if self.wm.layout.visible(id) {67
+ self.wm.layout.focus(id);68
+ }69
+ self.apply_layout();70
+ }71
+72
+ /// Holds a new window on its seed until its first buffer lands, so the73
+ /// spawn animation never plays over an empty slot. Clients that are slow74
+ /// to draw get it anyway after a while.75
+ fn await_first_buffer(&mut self, id: WindowId) {76
+ let timeout = Timer::from_duration(Duration::from_millis(300));77
+ let _ = self.core.handle.insert_source(timeout, move |_, _, state| {78
+ state.first_buffer(id);79
+ TimeoutAction::Drop80
+ });81
+ }82
+83
+ /// Called on commits, starts the spawn of a window that now has a buffer.84
+ pub fn on_buffer(&mut self, surface: &WlSurface) {85
+ if !self.wm.any_awaiting() {86
+ return;87
+ }88
+ let has_buffer =89
+ with_renderer_surface_state(surface, |s| s.buffer_size().is_some()).unwrap_or(false);90
+ if has_buffer && let Some(id) = self.window_id(surface) {91
+ self.first_buffer(id);92
+ }93
+ }94
+95
+ fn first_buffer(&mut self, id: WindowId) {96
+ let Some(w) = self.wm.get_mut(id) else {97
+ return;98
+ };99
+ w.placed_by_rule = false;100
+ if std::mem::take(&mut w.awaiting_buffer) {101
+ self.wm.anim.spawned(id, now_ns());102
+ self.apply_layout();103
+ }104
+ }105
+106
+ pub fn unmap_window(&mut self, surface: &WlSurface) {107
+ self.wm108
+ .unmapped109
+ .retain(|w| root(w).as_ref() != Some(surface));110
+ if let Some(id) = self.window_id(surface) {111
+ self.remove_window(id);112
+ }113
+ }114
+115
+ pub fn remove_window(&mut self, id: WindowId) {116
+ if let Some(state) = self.wm.remove(id) {117
+ self.wm.space.unmap_elem(&state.window);118
+ self.leave_ghost(id, &state.window);119
+ }120
+ self.wm.layout.remove_window(id);121
+ self.apply_layout();122
+ }123
+124
+ /// Keeps a visible tiled window's last frame shrinking to its seed while125
+ /// its neighbours close over it.126
+ fn leave_ghost(&mut self, id: WindowId, window: &Window) {127
+ let Some(o) = self.wm.layout.window(id).and_then(|w| w.output) else {128
+ return;129
+ };130
+ let tiled = self131
+ .wm132
+ .layout133
+ .output(o)134
+ .is_some_and(|out| out.tree.contains(id));135
+ if !tiled || !self.wm.layout.visible(id) {136
+ return;137
+ }138
+ let Some(seed) = self139
+ .wm140
+ .layout141
+ .arrange_hiding(o, &[id])142
+ .tiled143
+ .into_iter()144
+ .find(|p| p.window == id)145
+ else {146
+ return;147
+ };148
+ let Some(surface) = root(window) else {149
+ return;150
+ };151
+ let _ = import_surface_tree(&mut self.renderer, &surface);152
+ let context = self.renderer.context_id();153
+ let snapshot = with_renderer_surface_state(&surface, |s| {154
+ s.texture::<VulkanTexture>(context.clone())155
+ .cloned()156
+ .map(|t| (t, s.buffer_scale(), s.buffer_transform()))157
+ })158
+ .flatten();159
+ if let Some((texture, scale, transform)) = snapshot {160
+ self.wm.anim.add_ghost(Ghost {161
+ id,162
+ output: o,163
+ seed: seed.rect,164
+ texture,165
+ scale,166
+ transform,167
+ geometry: window.geometry(),168
+ born: now_ns(),169
+ });170
+ }171
+ }172
+173
+ /// Picks up a changed title or app id, which clients send at any time.174
+ pub fn refresh_identity(&mut self, id: WindowId) {175
+ let Some(w) = self.wm.get_mut(id) else {176
+ return;177
+ };178
+ let (app_id, title) = identity(&w.window);179
+ if w.app_id != app_id || w.title != title {180
+ w.app_id = app_id;181
+ w.title = title;182
+ self.dirty.snapshot = true;183
+ }184
+ }185
+186
+ pub fn window_id(&self, surface: &WlSurface) -> Option<WindowId> {187
+ self.wm.id_of(surface)188
+ }189
+}@@ -0,0 +1,91 @@
1
+use crate::state::State;2
+use monolith_layout::OutputId;3
+use monolith_protocols::workspace::{Action, WorkspaceHandler, WorkspaceState};4
+use monolith_protocols::workspace::{Group, Snapshot, Workspace};5
+use smithay::output::Output;6
+use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;7
+use smithay::wayland::output::OutputHandler;8
+use std::collections::HashSet;9
+10
+impl OutputHandler for State {11
+ /// Workspace and toplevel lists name outputs by the client's wl_output,12
+ /// so a newly bound one needs announcing.13
+ fn output_bound(&mut self, _output: Output, _wl_output: WlOutput) {14
+ self.dirty.snapshot = true;15
+ }16
+}17
+18
+impl WorkspaceHandler for State {19
+ fn workspace_state(&mut self) -> &mut WorkspaceState {20
+ &mut self.protocols.workspaces21
+ }22
+23
+ fn workspace_actions(&mut self, actions: Vec<Action>) {24
+ for action in actions {25
+ let (group, tag) = match &action {26
+ Action::Activate { group, workspace } | Action::Deactivate { group, workspace } => {27
+ (group, workspace)28
+ }29
+ Action::Create { group, name } => (group, name),30
+ };31
+ let Some(o) = self32
+ .wm33
+ .layout34
+ .outputs()35
+ .find(|(_, out)| out.name == *group)36
+ .map(|(o, _)| o)37
+ else {38
+ continue;39
+ };40
+ let t = self.wm.layout.tag(tag);41
+ match action {42
+ Action::Activate { .. } | Action::Create { .. } => self.wm.layout.view(o, t),43
+ Action::Deactivate { .. } => {44
+ if self.wm.layout.is_viewed(o, t) {45
+ self.wm.layout.toggle_view(o, t);46
+ }47
+ }48
+ }49
+ }50
+ self.apply_layout();51
+ }52
+}53
+54
+impl State {55
+ pub(crate) fn publish_workspaces(&mut self) {56
+ let l = &self.wm.layout;57
+ let occupied: HashSet<(OutputId, monolith_layout::TagId)> = l58
+ .windows()59
+ .filter(|(_, w)| w.scratchpad.is_none())60
+ .filter_map(|(_, w)| Some((w.output?, w.tag)))61
+ .collect();62
+ let groups = l63
+ .outputs()64
+ .filter_map(|(o, _)| {65
+ let output = self.backend.output(o)?;66
+ // Bars read a tag's existence as having windows, as with67
+ // workspaces that come and go, so empty unviewed tags are left out.68
+ let workspaces = l69
+ .tags()70
+ .enumerate()71
+ .filter(|&(_, (t, _))| {72
+ occupied.contains(&(o, t)) || (t != l.homeless() && l.is_viewed(o, t))73
+ })74
+ .map(|(i, (t, tag))| Workspace {75
+ key: tag.name.clone(),76
+ name: tag.name.clone(),77
+ coordinates: vec![i as u32],78
+ active: l.is_viewed(o, t),79
+ urgent: tag.urgent,80
+ hidden: false,81
+ })82
+ .collect();83
+ Some(Group { output, workspaces })84
+ })85
+ .collect();86
+ let dh = self.core.display.clone();87
+ self.protocols88
+ .workspaces89
+ .update::<State>(&dh, Snapshot { groups });90
+ }91
+}@@ -0,0 +1,90 @@
1
+use crate::state::State;2
+use smithay::desktop::{PopupKind, Window};3
+use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode;4
+use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;5
+use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;6
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;7
+use smithay::utils::Serial;8
+use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;9
+use smithay::wayland::shell::xdg::{10
+ PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,11
+};12
+13
+impl XdgShellHandler for State {14
+ fn xdg_shell_state(&mut self) -> &mut XdgShellState {15
+ &mut self.protocols.xdg_shell_state16
+ }17
+18
+ fn new_toplevel(&mut self, surface: ToplevelSurface) {19
+ self.wm.unmapped.push(Window::new_wayland_window(surface));20
+ }21
+22
+ fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {23
+ self.unmap_window(surface.wl_surface());24
+ }25
+26
+ fn fullscreen_request(&mut self, surface: ToplevelSurface, _output: Option<WlOutput>) {27
+ self.set_fullscreen(surface.wl_surface(), true);28
+ }29
+30
+ fn unfullscreen_request(&mut self, surface: ToplevelSurface) {31
+ self.set_fullscreen(surface.wl_surface(), false);32
+ }33
+34
+ fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {35
+ surface.with_pending_state(|s| s.geometry = positioner.get_geometry());36
+ let _ = self.wm.popups.track_popup(PopupKind::Xdg(surface));37
+ }38
+39
+ fn reposition_request(40
+ &mut self,41
+ surface: PopupSurface,42
+ positioner: PositionerState,43
+ token: u32,44
+ ) {45
+ surface.with_pending_state(|s| {46
+ s.geometry = positioner.get_geometry();47
+ s.positioner = positioner;48
+ });49
+ surface.send_repositioned(token);50
+ }51
+52
+ fn grab(&mut self, _surface: PopupSurface, _seat: WlSeat, _serial: Serial) {}53
+}54
+55
+impl XdgDecorationHandler for State {56
+ fn new_decoration(&mut self, toplevel: ToplevelSurface) {57
+ server_side(&toplevel);58
+ }59
+60
+ fn request_mode(&mut self, toplevel: ToplevelSurface, _mode: DecorationMode) {61
+ server_side(&toplevel);62
+ }63
+64
+ fn unset_mode(&mut self, toplevel: ToplevelSurface) {65
+ server_side(&toplevel);66
+ }67
+}68
+69
+/// monolith draws no titlebars, and clients are told so whatever they ask for.70
+fn server_side(toplevel: &ToplevelSurface) {71
+ toplevel.with_pending_state(|s| s.decoration_mode = Some(DecorationMode::ServerSide));72
+ if toplevel.is_initial_configure_sent() {73
+ toplevel.send_pending_configure();74
+ }75
+}76
+77
+impl State {78
+ fn set_fullscreen(&mut self, surface: &WlSurface, on: bool) {79
+ if let Some(id) = self.window_id(surface)80
+ && self81
+ .wm82
+ .layout83
+ .window(id)84
+ .is_some_and(|w| w.fullscreen != on)85
+ {86
+ self.wm.layout.toggle_fullscreen(id);87
+ self.apply_layout();88
+ }89
+ }90
+}