atagen

monolith

public
Star0
Watch0

Commit 211452a41a25

tarball

atagen <boss@atagen.co>2026-10-19 07:59 UTC

7 files changed+122-15
seat: start the dnd pointer grab and draw the drag icon

dnd_requested cancelled every source, so no drag ever reached a client. The grab is a smithay DnDGrab, and the icon is kept in Input::dnd_icon so frame::compose can draw it under the cursor and send_frame_callbacks can keep animated icons ticking. Touch drags stay cancelled since monolith has no touch input path.
Modified file: crates/monolith/src/commit.rs+15 additions
@@ -41,6 +41,21 @@ impl CompositorHandler for State {
4141
4242 fn commit(&mut self, surface: &WlSurface) {
4343 on_commit_buffer_handler::<Self>(surface);
44+ if let Some(icon) = self
45+ .input
46+ .dnd_icon
47+ .as_mut()
48+ .filter(|i| &i.surface == surface)
49+ {
50+ let delta = with_states(surface, |s| {
51+ s.cached_state
52+ .get::<SurfaceAttributes>()
53+ .current()
54+ .buffer_delta
55+ .take()
56+ });
57+ icon.offset += delta.unwrap_or_default();
58+ }
4459 // Importing at commit keeps first-time dmabuf imports, which take
4560 // milliseconds on NVIDIA, out of the render racing the next vblank.
4661 if let Err(e) = import_surface_tree(&mut self.renderer, surface) {
Modified file: crates/monolith/src/cursor.rs+26 additions
@@ -13,6 +13,7 @@ use smithay::backend::renderer::element::{Id, Kind};
1313 use smithay::backend::renderer::utils::CommitCounter;
1414 use smithay::input::pointer::CursorIcon;
1515 use smithay::input::pointer::{CursorImageAttributes, CursorImageStatus};
16+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
1617 use smithay::utils::{Logical, Physical, Point, Rectangle, Transform};
1718 use smithay::wayland::compositor::with_states;
1819 use std::collections::HashMap;
@@ -143,3 +144,28 @@ fn load_image(theme: &xcursor::CursorTheme, name: &str, size: u32) -> Image {
143144 );
144145 Some((buffer, (image.xhot as i32, image.yhot as i32).into()))
145146 }
147+
148+/// The surface a drag carries under the pointer. `offset` accumulates the
149+/// attach offsets the client commits, as the icon has no hotspot of its own.
150+pub struct DndIcon {
151+ pub surface: WlSurface,
152+ pub offset: Point<i32, Logical>,
153+}
154+
155+impl DndIcon {
156+ /// Elements for the icon with the pointer at `pos`, in output-local coordinates.
157+ pub fn elements(&self, renderer: &mut VulkanRenderer, pos: Point<f64, Logical>) -> Vec<Draw> {
158+ let p = pos + self.offset.to_f64();
159+ render_elements_from_surface_tree(
160+ renderer,
161+ &self.surface,
162+ (p.x.round() as i32, p.y.round() as i32),
163+ 1.0,
164+ 1.0,
165+ Kind::Unspecified,
166+ )
167+ .into_iter()
168+ .map(Draw::Surface)
169+ .collect()
170+ }
171+}
Modified file: crates/monolith/src/drm.rs+1 additions
@@ -1084,6 +1084,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
10841084 &surface.output,
10851085 now,
10861086 elapsed,
1087+ input.dnd_icon.as_ref(),
10871088 );
10881089 crate::frame::send_cursor_frames(wm, input, surface.id, &surface.output, elapsed);
10891090 (animating || failed) && !surface.queued
Modified file: crates/monolith/src/frame.rs+22 additions−6 deletions
@@ -1,6 +1,7 @@
11 //! What every output shows, shared by the backends and screen capture.
22 //! Backends only decide when to draw and how to present.
33
4+use crate::cursor::DndIcon;
45 use crate::lock::Lock;
56 use crate::scene::Draw;
67 use crate::state::Input;
@@ -37,15 +38,20 @@ pub fn compose(
3738 cursor: bool,
3839 ) -> Vec<Draw> {
3940 let mut elements = Vec::new();
40- if cursor && let Some(area) = ctx.wm.layout.output(o).map(|out| out.area) {
41+ if let Some(area) = ctx.wm.layout.output(o).map(|out| out.area) {
4142 let pos = ctx.input.pointer_pos;
4243 if area.contains(pos.x, pos.y) {
4344 let local = pos - Point::from((area.x, area.y));
44- elements.extend(ctx.input.cursor.elements(
45- ctx.renderer,
46- &ctx.input.cursor_status,
47- local,
48- ));
45+ if cursor {
46+ elements.extend(ctx.input.cursor.elements(
47+ ctx.renderer,
48+ &ctx.input.cursor_status,
49+ local,
50+ ));
51+ }
52+ if let Some(icon) = &ctx.input.dnd_icon {
53+ elements.extend(icon.elements(ctx.renderer, local));
54+ }
4955 }
5056 }
5157 match crate::lock::elements(ctx.lock, ctx.renderer, output) {
@@ -75,7 +81,17 @@ pub fn send_frame_callbacks(
7581 output: &Output,
7682 now: u64,
7783 elapsed: Duration,
84+ dnd_icon: Option<&DndIcon>,
7885 ) {
86+ if let Some(icon) = dnd_icon {
87+ send_frames_surface_tree(
88+ &icon.surface,
89+ output,
90+ elapsed,
91+ Some(Duration::ZERO),
92+ |_, _| Some(output.clone()),
93+ );
94+ }
7995 if lock.active() {
8096 crate::lock::send_frames(lock, output, elapsed);
8197 return;
Modified file: crates/monolith/src/nested/mod.rs+1 additions
@@ -265,6 +265,7 @@ pub fn render(state: &mut State) {
265265 &host.output,
266266 now,
267267 elapsed,
268+ input.dnd_icon.as_ref(),
268269 );
269270 if damage.is_some() {
270271 captures.output_damaged(&host.output);
Modified file: crates/monolith/src/seat.rs+55 additions−9 deletions
@@ -1,10 +1,11 @@
1+use crate::cursor::DndIcon;
12 use crate::state::State;
2-use smithay::input::dnd::{DndGrabHandler, GrabType, Source};
3-use smithay::input::pointer::CursorImageStatus;
3+use smithay::input::dnd::{DnDGrab, DndGrabHandler, DndTarget, GrabType, Source};
4+use smithay::input::pointer::{CursorImageStatus, Focus};
45 use smithay::input::{Seat, SeatHandler, SeatState};
56 use smithay::reexports::wayland_server::Resource;
67 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
7-use smithay::utils::Serial;
8+use smithay::utils::{Logical, Point, Serial};
89 use smithay::wayland::seat::WaylandFocus;
910 use smithay::wayland::selection::data_device::{
1011 DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus,
@@ -103,7 +104,28 @@ impl smithay::wayland::selection::ext_data_control::DataControlHandler for State
103104 }
104105 }
105106
106-impl DndGrabHandler for State {}
107+impl DndGrabHandler for State {
108+ fn dropped(
109+ &mut self,
110+ _target: Option<DndTarget<'_, Self>>,
111+ _validated: bool,
112+ _seat: Seat<Self>,
113+ _location: Point<f64, Logical>,
114+ ) {
115+ self.end_dnd();
116+ }
117+
118+ fn cancelled(&mut self, _seat: Seat<Self>, _location: Point<f64, Logical>) {
119+ self.end_dnd();
120+ }
121+}
122+
123+impl State {
124+ fn end_dnd(&mut self) {
125+ self.input.dnd_icon = None;
126+ self.dirty.redraw = true;
127+ }
128+}
107129
108130 impl smithay::input::tablet::TabletSeatHandler for State {
109131 type ToolFocus = WlSurface;
@@ -113,12 +135,36 @@ impl WaylandDndGrabHandler for State {
113135 fn dnd_requested<S: Source>(
114136 &mut self,
115137 source: S,
116- _icon: Option<WlSurface>,
117- _seat: Seat<Self>,
118- _serial: Serial,
119- _type: GrabType,
138+ icon: Option<WlSurface>,
139+ seat: Seat<Self>,
140+ serial: Serial,
141+ type_: GrabType,
120142 ) {
121- source.cancel();
143+ self.input.dnd_icon = icon.map(|surface| DndIcon {
144+ surface,
145+ offset: Point::default(),
146+ });
147+ self.dirty.redraw = true;
148+ let dh = self.core.display.clone();
149+ match type_ {
150+ GrabType::Pointer => {
151+ let Some(start_data) = seat
152+ .get_pointer()
153+ .and_then(|pointer| pointer.grab_start_data())
154+ else {
155+ source.cancel();
156+ return self.end_dnd();
157+ };
158+ let grab = DnDGrab::new_pointer(&dh, start_data, source, seat.clone());
159+ if let Some(pointer) = seat.get_pointer() {
160+ pointer.set_grab(self, grab, serial, Focus::Keep);
161+ }
162+ }
163+ GrabType::Touch => {
164+ source.cancel();
165+ self.end_dnd();
166+ }
167+ }
122168 }
123169 }
124170
Modified file: crates/monolith/src/state.rs+2 additions
@@ -59,6 +59,7 @@ pub struct Input {
5959 pub warping: bool,
6060 pub cursor: crate::cursor::Cursor,
6161 pub cursor_status: CursorImageStatus,
62+ pub dnd_icon: Option<crate::cursor::DndIcon>,
6263 }
6364
6465 /// What needs doing after this batch of events.
@@ -189,6 +190,7 @@ impl State {
189190 warping: false,
190191 cursor: crate::cursor::Cursor::load(),
191192 cursor_status: CursorImageStatus::default_named(),
193+ dnd_icon: None,
192194 },
193195 dirty: Dirty {
194196 redraw: true,