| 1 | use crate::state::{ClientState, State}; |
| 2 | use smithay::backend::allocator::dmabuf::Dmabuf; |
| 3 | use smithay::backend::renderer::ImportDma; |
| 4 | use smithay::backend::renderer::utils::{import_surface_tree, on_commit_buffer_handler}; |
| 5 | use smithay::desktop::{PopupKind, Window}; |
| 6 | use smithay::input::dnd::{DndGrabHandler, GrabType, Source}; |
| 7 | use smithay::input::pointer::CursorImageStatus; |
| 8 | use smithay::input::{Seat, SeatHandler, SeatState}; |
| 9 | use smithay::reexports::calloop::Interest; |
| 10 | use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; |
| 11 | use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode; |
| 12 | use smithay::reexports::wayland_server::protocol::wl_output::WlOutput; |
| 13 | use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat; |
| 14 | use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; |
| 15 | use smithay::reexports::wayland_server::{Client, Resource}; |
| 16 | use smithay::utils::Serial; |
| 17 | use smithay::wayland::buffer::BufferHandler; |
| 18 | use smithay::wayland::compositor::{ |
| 19 | BufferAssignment, CompositorClientState, CompositorHandler, CompositorState, |
| 20 | SurfaceAttributes, add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface, |
| 21 | with_states, |
| 22 | }; |
| 23 | use smithay::wayland::drm_syncobj::{DrmSyncobjCachedState, DrmSyncobjHandler, DrmSyncobjState}; |
| 24 | use smithay::wayland::dmabuf::{ |
| 25 | DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf, |
| 26 | }; |
| 27 | use smithay::wayland::output::OutputHandler; |
| 28 | use smithay::wayland::selection::SelectionHandler; |
| 29 | use smithay::wayland::selection::data_device::{ |
| 30 | DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus, |
| 31 | }; |
| 32 | use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler; |
| 33 | use smithay::wayland::shell::xdg::{ |
| 34 | PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, |
| 35 | }; |
| 36 | use monolith_protocols::workspace::{Action, WorkspaceHandler, WorkspaceState}; |
| 37 | use smithay::wayland::shm::{ShmHandler, ShmState}; |
| 38 | |
| 39 | impl CompositorHandler for State { |
| 40 | fn compositor_state(&mut self) -> &mut CompositorState { |
| 41 | &mut self.compositor_state |
| 42 | } |
| 43 | |
| 44 | fn new_surface(&mut self, surface: &WlSurface) { |
| 45 | add_pre_commit_hook::<Self, _>(surface, |state, _dh, surface| { |
| 46 | hold_until_readable(state, surface); |
| 47 | }); |
| 48 | } |
| 49 | |
| 50 | fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { |
| 51 | &client |
| 52 | .get_data::<ClientState>() |
| 53 | .expect("every client has ClientState") |
| 54 | .compositor_state |
| 55 | } |
| 56 | |
| 57 | fn commit(&mut self, surface: &WlSurface) { |
| 58 | on_commit_buffer_handler::<Self>(surface); |
| 59 | // Importing at commit keeps first-time dmabuf imports, which take |
| 60 | // milliseconds on NVIDIA, out of the render racing the next vblank. |
| 61 | if let Err(e) = import_surface_tree(&mut self.renderer, surface) { |
| 62 | tracing::debug!("early import failed: {e}"); |
| 63 | } |
| 64 | if !is_sync_subsurface(surface) { |
| 65 | let mut root = surface.clone(); |
| 66 | while let Some(parent) = get_parent(&root) { |
| 67 | root = parent; |
| 68 | } |
| 69 | if let Some(window) = self |
| 70 | .windows |
| 71 | .values() |
| 72 | .chain(&self.unmapped) |
| 73 | .find(|w| w.toplevel().is_some_and(|t| t.wl_surface() == &root)) |
| 74 | { |
| 75 | window.on_commit(); |
| 76 | } |
| 77 | } |
| 78 | if let Some(i) = self |
| 79 | .unmapped |
| 80 | .iter() |
| 81 | .position(|w| w.toplevel().is_some_and(|t| t.wl_surface() == surface)) |
| 82 | { |
| 83 | let window = self.unmapped.remove(i); |
| 84 | let toplevel = window.toplevel().cloned(); |
| 85 | self.map_window(window); |
| 86 | if let Some(t) = toplevel { |
| 87 | t.send_configure(); |
| 88 | } |
| 89 | } |
| 90 | self.on_buffer(surface); |
| 91 | self.popups.commit(surface); |
| 92 | if let Some(PopupKind::Xdg(popup)) = self.popups.find_popup(surface) |
| 93 | && !popup.is_initial_configure_sent() |
| 94 | { |
| 95 | let _ = popup.send_configure(); |
| 96 | } |
| 97 | self.needs_redraw = true; |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | impl BufferHandler for State { |
| 102 | fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {} |
| 103 | } |
| 104 | |
| 105 | impl ShmHandler for State { |
| 106 | fn shm_state(&self) -> &ShmState { |
| 107 | &self.shm_state |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | impl DmabufHandler for State { |
| 112 | fn dmabuf_state(&mut self) -> &mut DmabufState { |
| 113 | &mut self.dmabuf_state |
| 114 | } |
| 115 | |
| 116 | fn dmabuf_imported( |
| 117 | &mut self, |
| 118 | _global: &DmabufGlobal, |
| 119 | dmabuf: Dmabuf, |
| 120 | notifier: ImportNotifier, |
| 121 | ) { |
| 122 | if self.renderer.import_dmabuf(&dmabuf, None).is_ok() { |
| 123 | let _ = notifier.successful::<State>(); |
| 124 | } else { |
| 125 | notifier.failed(); |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | impl XdgShellHandler for State { |
| 131 | fn xdg_shell_state(&mut self) -> &mut XdgShellState { |
| 132 | &mut self.xdg_shell_state |
| 133 | } |
| 134 | |
| 135 | fn new_toplevel(&mut self, surface: ToplevelSurface) { |
| 136 | self.unmapped.push(Window::new_wayland_window(surface)); |
| 137 | } |
| 138 | |
| 139 | fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { |
| 140 | self.unmap_window(surface.wl_surface()); |
| 141 | } |
| 142 | |
| 143 | fn fullscreen_request(&mut self, surface: ToplevelSurface, _output: Option<WlOutput>) { |
| 144 | self.set_fullscreen(surface.wl_surface(), true); |
| 145 | } |
| 146 | |
| 147 | fn unfullscreen_request(&mut self, surface: ToplevelSurface) { |
| 148 | self.set_fullscreen(surface.wl_surface(), false); |
| 149 | } |
| 150 | |
| 151 | fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) { |
| 152 | surface.with_pending_state(|s| s.geometry = positioner.get_geometry()); |
| 153 | let _ = self.popups.track_popup(PopupKind::Xdg(surface)); |
| 154 | } |
| 155 | |
| 156 | fn reposition_request( |
| 157 | &mut self, |
| 158 | surface: PopupSurface, |
| 159 | positioner: PositionerState, |
| 160 | token: u32, |
| 161 | ) { |
| 162 | surface.with_pending_state(|s| { |
| 163 | s.geometry = positioner.get_geometry(); |
| 164 | s.positioner = positioner; |
| 165 | }); |
| 166 | surface.send_repositioned(token); |
| 167 | } |
| 168 | |
| 169 | fn grab(&mut self, _surface: PopupSurface, _seat: WlSeat, _serial: Serial) {} |
| 170 | } |
| 171 | |
| 172 | impl SeatHandler for State { |
| 173 | type KeyboardFocus = WlSurface; |
| 174 | type PointerFocus = WlSurface; |
| 175 | type TouchFocus = WlSurface; |
| 176 | |
| 177 | fn seat_state(&mut self) -> &mut SeatState<State> { |
| 178 | &mut self.seat_state |
| 179 | } |
| 180 | |
| 181 | fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) { |
| 182 | self.cursor_status = image; |
| 183 | self.needs_redraw = true; |
| 184 | } |
| 185 | |
| 186 | fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) { |
| 187 | let client = focused.and_then(|s| self.display.get_client(s.id()).ok()); |
| 188 | set_data_device_focus(&self.display, seat, client); |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | impl SelectionHandler for State { |
| 193 | type SelectionUserData = (); |
| 194 | } |
| 195 | |
| 196 | impl DataDeviceHandler for State { |
| 197 | fn data_device_state(&mut self) -> &mut DataDeviceState { |
| 198 | &mut self.data_device_state |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | impl DndGrabHandler for State {} |
| 203 | |
| 204 | impl WaylandDndGrabHandler for State { |
| 205 | fn dnd_requested<S: Source>( |
| 206 | &mut self, |
| 207 | source: S, |
| 208 | _icon: Option<WlSurface>, |
| 209 | _seat: Seat<Self>, |
| 210 | _serial: Serial, |
| 211 | _type: GrabType, |
| 212 | ) { |
| 213 | source.cancel(); |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | impl OutputHandler for State {} |
| 218 | |
| 219 | impl smithay::wayland::pointer_constraints::PointerConstraintsHandler for State { |
| 220 | fn new_constraint( |
| 221 | &mut self, |
| 222 | _surface: &WlSurface, |
| 223 | _pointer: &smithay::input::pointer::PointerHandle<Self>, |
| 224 | ) { |
| 225 | } |
| 226 | |
| 227 | fn cursor_position_hint( |
| 228 | &mut self, |
| 229 | _surface: &WlSurface, |
| 230 | _pointer: &smithay::input::pointer::PointerHandle<Self>, |
| 231 | _location: smithay::utils::Point<f64, smithay::utils::Logical>, |
| 232 | ) { |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | smithay::delegate_dispatch2!(State); |
| 237 | |
| 238 | impl DrmSyncobjHandler for State { |
| 239 | fn drm_syncobj_state(&mut self) -> Option<&mut DrmSyncobjState> { |
| 240 | self.syncobj.as_mut() |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | /// Holds a dmabuf commit until the buffer can be read without the renderer |
| 245 | /// stalling on the client's GPU work: until its explicit acquire point |
| 246 | /// signals, or for implicitly synced buffers until their writes finish. |
| 247 | fn hold_until_readable(state: &mut State, surface: &WlSurface) { |
| 248 | let (acquire, dmabuf) = with_states(surface, |s| { |
| 249 | let acquire = s |
| 250 | .cached_state |
| 251 | .get::<DrmSyncobjCachedState>() |
| 252 | .pending() |
| 253 | .acquire_point |
| 254 | .clone(); |
| 255 | let dmabuf = match s |
| 256 | .cached_state |
| 257 | .get::<SurfaceAttributes>() |
| 258 | .pending() |
| 259 | .buffer |
| 260 | .as_ref() |
| 261 | { |
| 262 | Some(BufferAssignment::NewBuffer(buffer)) => get_dmabuf(buffer).cloned().ok(), |
| 263 | _ => None, |
| 264 | }; |
| 265 | (acquire, dmabuf) |
| 266 | }); |
| 267 | let (Some(dmabuf), Some(client)) = (dmabuf, surface.client()) else { |
| 268 | return; |
| 269 | }; |
| 270 | if let Some(point) = acquire |
| 271 | && let Ok((blocker, source)) = point.generate_blocker() |
| 272 | { |
| 273 | let inserted = state.handle.insert_source(source, move |_, _, state| { |
| 274 | let dh = state.display.clone(); |
| 275 | state |
| 276 | .client_compositor_state(&client) |
| 277 | .blocker_cleared(state, &dh); |
| 278 | Ok(()) |
| 279 | }); |
| 280 | if inserted.is_ok() { |
| 281 | add_blocker(surface, blocker); |
| 282 | } |
| 283 | return; |
| 284 | } |
| 285 | if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) { |
| 286 | let inserted = state.handle.insert_source(source, move |_, _, state| { |
| 287 | let dh = state.display.clone(); |
| 288 | state |
| 289 | .client_compositor_state(&client) |
| 290 | .blocker_cleared(state, &dh); |
| 291 | Ok(()) |
| 292 | }); |
| 293 | if inserted.is_ok() { |
| 294 | add_blocker(surface, blocker); |
| 295 | } |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | impl XdgDecorationHandler for State { |
| 300 | fn new_decoration(&mut self, toplevel: ToplevelSurface) { |
| 301 | server_side(&toplevel); |
| 302 | } |
| 303 | |
| 304 | fn request_mode(&mut self, toplevel: ToplevelSurface, _mode: DecorationMode) { |
| 305 | server_side(&toplevel); |
| 306 | } |
| 307 | |
| 308 | fn unset_mode(&mut self, toplevel: ToplevelSurface) { |
| 309 | server_side(&toplevel); |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | /// monolith draws no titlebars, and clients are told so whatever they ask for. |
| 314 | fn server_side(toplevel: &ToplevelSurface) { |
| 315 | toplevel.with_pending_state(|s| s.decoration_mode = Some(DecorationMode::ServerSide)); |
| 316 | if toplevel.is_initial_configure_sent() { |
| 317 | toplevel.send_pending_configure(); |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | impl State { |
| 322 | fn set_fullscreen(&mut self, surface: &WlSurface, on: bool) { |
| 323 | if let Some(id) = self.window_id(surface) |
| 324 | && self.layout.window(id).is_some_and(|w| w.fullscreen != on) |
| 325 | { |
| 326 | self.layout.toggle_fullscreen(id); |
| 327 | self.apply_layout(); |
| 328 | } |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | impl WorkspaceHandler for State { |
| 333 | fn workspace_state(&mut self) -> &mut WorkspaceState { |
| 334 | &mut self.workspaces |
| 335 | } |
| 336 | |
| 337 | fn workspace_actions(&mut self, actions: Vec<Action>) { |
| 338 | for action in actions { |
| 339 | let (group, tag) = match &action { |
| 340 | Action::Activate { group, workspace } | Action::Deactivate { group, workspace } => { |
| 341 | (group, workspace) |
| 342 | } |
| 343 | Action::Create { group, name } => (group, name), |
| 344 | }; |
| 345 | let Some(o) = self |
| 346 | .layout |
| 347 | .outputs() |
| 348 | .find(|(_, out)| out.name == *group) |
| 349 | .map(|(o, _)| o) |
| 350 | else { |
| 351 | continue; |
| 352 | }; |
| 353 | let t = self.layout.tag(tag); |
| 354 | match action { |
| 355 | Action::Activate { .. } | Action::Create { .. } => self.layout.view(o, t), |
| 356 | Action::Deactivate { .. } => { |
| 357 | if self.layout.is_viewed(o, t) { |
| 358 | self.layout.toggle_view(o, t); |
| 359 | } |
| 360 | } |
| 361 | } |
| 362 | } |
| 363 | self.apply_layout(); |
| 364 | } |
| 365 | } |