From a7f00a77cab47883356aff357fb3ccbea9e25756 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 30 Dec 2022 22:03:10 +0100 Subject: [PATCH] touch: support drag and drop --- river/Cursor.zig | 24 +++++++++++++-- river/DragIcon.zig | 32 ++++++++++---------- river/Root.zig | 2 -- river/Seat.zig | 74 +++++++++++++++++++++++++++++----------------- river/render.zig | 28 +++++++++++++----- 5 files changed, 107 insertions(+), 53 deletions(-) diff --git a/river/Cursor.zig b/river/Cursor.zig index 6b72c03..d73c376 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -85,6 +85,11 @@ const Image = enum { const default_size = 24; +const LayoutPoint = struct { + lx: f64, + ly: f64, +}; + const log = std.log.scoped(.cursor); /// Current cursor mode as well as any state needed to implement that mode @@ -107,6 +112,10 @@ hide_cursor_timer: *wl.EventSource, hidden: bool = false, may_need_warp: bool = false, +/// Keeps track of the last known location of all touch points in layout coordinates. +/// This information is necessary for proper touch dnd support if there are multiple touch points. +touch_points: std.AutoHashMapUnmanaged(i32, LayoutPoint) = .{}, + axis: wl.Listener(*wlr.Pointer.event.Axis) = wl.Listener(*wlr.Pointer.event.Axis).init(handleAxis), frame: wl.Listener(*wlr.Cursor) = wl.Listener(*wlr.Cursor).init(handleFrame), button: wl.Listener(*wlr.Pointer.event.Button) = @@ -459,6 +468,8 @@ fn handleTouchUp( self.seat.handleActivity(); + _ = self.touch_points.remove(event.touch_id); + self.seat.wlr_seat.touchNotifyUp(event.time_msec, event.touch_id); } @@ -474,6 +485,10 @@ fn handleTouchDown( var ly: f64 = undefined; self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly); + self.touch_points.putNoClobber(util.gpa, event.touch_id, .{ .lx = lx, .ly = ly }) catch { + log.err("out of memory", .{}); + }; + if (surfaceAtCoords(lx, ly)) |result| { self.updateKeyboardFocus(result); @@ -503,6 +518,10 @@ fn handleTouchMotion( var ly: f64 = undefined; self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly); + self.touch_points.put(util.gpa, event.touch_id, .{ .lx = lx, .ly = ly }) catch { + log.err("out of memory", .{}); + }; + if (surfaceAtCoords(lx, ly)) |result| { self.seat.wlr_seat.touchNotifyMotion(event.time_msec, event.touch_id, result.sx, result.sy); } @@ -1025,8 +1044,9 @@ fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, } pub fn checkFocusFollowsCursor(self: *Self) void { - // Don't do focus-follows-cursor if a drag is in progress as focus change can't occur - if (self.seat.pointer_drag) return; + // Don't do focus-follows-cursor if a pointer drag is in progress as focus + // change can't occur. + if (self.seat.drag == .pointer) return; if (server.config.focus_follows_cursor == .disabled) return; if (self.surfaceAt()) |result| { if (server.config.focus_follows_cursor == .always or diff --git a/river/DragIcon.zig b/river/DragIcon.zig index 535facb..7500e62 100644 --- a/river/DragIcon.zig +++ b/river/DragIcon.zig @@ -29,14 +29,16 @@ const Subsurface = @import("Subsurface.zig"); seat: *Seat, wlr_drag_icon: *wlr.Drag.Icon, +// Accumulated x/y surface offset from the cursor/touch point position. +sx: i32 = 0, +sy: i32 = 0, + // Always active destroy: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleDestroy), map: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleMap), unmap: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleUnmap), -new_subsurface: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleNewSubsurface), - -// Only active while mapped commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), +new_subsurface: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleNewSubsurface), pub fn init(drag_icon: *DragIcon, seat: *Seat, wlr_drag_icon: *wlr.Drag.Icon) void { drag_icon.* = .{ .seat = seat, .wlr_drag_icon = wlr_drag_icon }; @@ -44,6 +46,7 @@ pub fn init(drag_icon: *DragIcon, seat: *Seat, wlr_drag_icon: *wlr.Drag.Icon) vo wlr_drag_icon.events.destroy.add(&drag_icon.destroy); wlr_drag_icon.events.map.add(&drag_icon.map); wlr_drag_icon.events.unmap.add(&drag_icon.unmap); + wlr_drag_icon.surface.events.commit.add(&drag_icon.commit); wlr_drag_icon.surface.events.new_subsurface.add(&drag_icon.new_subsurface); if (wlr_drag_icon.mapped) handleMap(&drag_icon.map, wlr_drag_icon); @@ -54,36 +57,35 @@ pub fn init(drag_icon: *DragIcon, seat: *Seat, wlr_drag_icon: *wlr.Drag.Icon) vo fn handleDestroy(listener: *wl.Listener(*wlr.Drag.Icon), wlr_drag_icon: *wlr.Drag.Icon) void { const drag_icon = @fieldParentPtr(DragIcon, "destroy", listener); - const node = @fieldParentPtr(std.SinglyLinkedList(DragIcon).Node, "data", drag_icon); - server.root.drag_icons.remove(node); + drag_icon.seat.drag_icon = null; drag_icon.destroy.link.remove(); drag_icon.map.link.remove(); drag_icon.unmap.link.remove(); + drag_icon.commit.link.remove(); drag_icon.new_subsurface.link.remove(); Subsurface.destroySubsurfaces(wlr_drag_icon.surface); - util.gpa.destroy(node); + util.gpa.destroy(drag_icon); } -fn handleMap(listener: *wl.Listener(*wlr.Drag.Icon), wlr_drag_icon: *wlr.Drag.Icon) void { - const drag_icon = @fieldParentPtr(DragIcon, "map", listener); - - wlr_drag_icon.surface.events.commit.add(&drag_icon.commit); +fn handleMap(_: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void { var it = server.root.outputs.first; while (it) |node| : (it = node.next) node.data.damage.?.addWhole(); } -fn handleUnmap(listener: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void { - const drag_icon = @fieldParentPtr(DragIcon, "unmap", listener); - - drag_icon.commit.link.remove(); +fn handleUnmap(_: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void { var it = server.root.outputs.first; while (it) |node| : (it = node.next) node.data.damage.?.addWhole(); } -fn handleCommit(_: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void { +fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { + const drag_icon = @fieldParentPtr(DragIcon, "commit", listener); + + drag_icon.sx += surface.current.dx; + drag_icon.sy += surface.current.dy; + var it = server.root.outputs.first; while (it) |node| : (it = node.next) node.data.damage.?.addWhole(); } diff --git a/river/Root.zig b/river/Root.zig index b578a25..e5adad6 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -56,8 +56,6 @@ outputs: std.TailQueue(Output) = .{}, /// It is not advertised to clients. noop_output: Output = undefined, -drag_icons: std.SinglyLinkedList(DragIcon) = .{}, - /// This list stores all "override redirect" Xwayland windows. This needs to be in root /// since X is like the wild west and who knows where these things will place themselves. xwayland_override_redirect_views: if (build_options.xwayland) diff --git a/river/Seat.zig b/river/Seat.zig index 9518864..c08aad9 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -87,15 +87,20 @@ focus_stack: ViewStack(*View) = .{}, /// List of status tracking objects relaying changes to this seat to clients. status_trackers: std.SinglyLinkedList(SeatStatus) = .{}, -/// True if a pointer drag is currently in progress -pointer_drag: bool = false, +/// The currently in progress drag operation type. +drag: enum { + none, + pointer, + touch, +} = .none, +drag_icon: ?*DragIcon = null, request_set_selection: wl.Listener(*wlr.Seat.event.RequestSetSelection) = wl.Listener(*wlr.Seat.event.RequestSetSelection).init(handleRequestSetSelection), request_start_drag: wl.Listener(*wlr.Seat.event.RequestStartDrag) = wl.Listener(*wlr.Seat.event.RequestStartDrag).init(handleRequestStartDrag), start_drag: wl.Listener(*wlr.Drag) = wl.Listener(*wlr.Drag).init(handleStartDrag), -pointer_drag_destroy: wl.Listener(*wlr.Drag) = wl.Listener(*wlr.Drag).init(handlePointerDragDestroy), +drag_destroy: wl.Listener(*wlr.Drag) = wl.Listener(*wlr.Drag).init(handleDragDestroy), request_set_primary_selection: wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection) = wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection).init(handleRequestSetPrimarySelection), @@ -141,7 +146,7 @@ pub fn deinit(self: *Self) void { self.request_set_selection.link.remove(); self.request_start_drag.link.remove(); self.start_drag.link.remove(); - if (self.pointer_drag) self.pointer_drag_destroy.link.remove(); + if (self.drag != .none) self.drag_destroy.link.remove(); self.request_set_primary_selection.link.remove(); } @@ -535,50 +540,65 @@ fn handleRequestStartDrag( ) void { const self = @fieldParentPtr(Self, "request_start_drag", listener); - if (!self.wlr_seat.validatePointerGrabSerial(event.origin, event.serial)) { - log.debug("ignoring request to start drag, " ++ - "failed to validate pointer serial {}", .{event.serial}); - if (event.drag.source) |source| source.destroy(); + // The start_drag request is ignored by wlroots if a drag is currently in progress. + assert(self.drag == .none); + + if (self.wlr_seat.validatePointerGrabSerial(event.origin, event.serial)) { + log.debug("starting pointer drag", .{}); + self.wlr_seat.startPointerDrag(event.drag, event.serial); return; } - if (self.pointer_drag) { - log.debug("ignoring request to start pointer drag, " ++ - "another pointer drag is already in progress", .{}); + var point: *wlr.TouchPoint = undefined; + if (self.wlr_seat.validateTouchGrabSerial(event.origin, event.serial, &point)) { + log.debug("starting touch drag", .{}); + self.wlr_seat.startTouchDrag(event.drag, event.serial, point); return; } - log.debug("starting pointer drag", .{}); - self.wlr_seat.startPointerDrag(event.drag, event.serial); + log.debug("ignoring request to start drag, " ++ + "failed to validate pointer or touch serial {}", .{event.serial}); + if (event.drag.source) |source| source.destroy(); } fn handleStartDrag(listener: *wl.Listener(*wlr.Drag), wlr_drag: *wlr.Drag) void { const self = @fieldParentPtr(Self, "start_drag", listener); - assert(wlr_drag.grab_type == .keyboard_pointer); - - self.pointer_drag = true; - wlr_drag.events.destroy.add(&self.pointer_drag_destroy); + assert(self.drag == .none); + switch (wlr_drag.grab_type) { + .keyboard_pointer => { + self.drag = .pointer; + self.cursor.mode = .passthrough; + }, + .keyboard_touch => self.drag = .touch, + .keyboard => unreachable, + } + wlr_drag.events.destroy.add(&self.drag_destroy); if (wlr_drag.icon) |wlr_drag_icon| { - const node = util.gpa.create(std.SinglyLinkedList(DragIcon).Node) catch { + const drag_icon = util.gpa.create(DragIcon) catch { log.err("out of memory", .{}); wlr_drag.seat_client.client.postNoMemory(); return; }; - node.data.init(self, wlr_drag_icon); - server.root.drag_icons.prepend(node); + drag_icon.init(self, wlr_drag_icon); + self.drag_icon = drag_icon; } - self.cursor.mode = .passthrough; } -fn handlePointerDragDestroy(listener: *wl.Listener(*wlr.Drag), _: *wlr.Drag) void { - const self = @fieldParentPtr(Self, "pointer_drag_destroy", listener); - self.pointer_drag_destroy.link.remove(); +fn handleDragDestroy(listener: *wl.Listener(*wlr.Drag), _: *wlr.Drag) void { + const self = @fieldParentPtr(Self, "drag_destroy", listener); + self.drag_destroy.link.remove(); - self.pointer_drag = false; - self.cursor.checkFocusFollowsCursor(); - self.cursor.updateState(); + switch (self.drag) { + .none => unreachable, + .pointer => { + self.cursor.checkFocusFollowsCursor(); + self.cursor.updateState(); + }, + .touch => {}, + } + self.drag = .none; } fn handleRequestSetPrimarySelection( diff --git a/river/render.zig b/river/render.zig index 690758a..adef939 100644 --- a/river/render.zig +++ b/river/render.zig @@ -260,19 +260,33 @@ fn renderDragIcons(output: *const Output, now: *os.timespec) void { var output_box: wlr.Box = undefined; server.root.output_layout.getBox(output.wlr_output, &output_box); - var it = server.root.drag_icons.first; + var it = server.input_manager.seats.first; while (it) |node| : (it = node.next) { - const drag_icon = &node.data; + const icon = node.data.drag_icon orelse continue; + + var lx: f64 = undefined; + var ly: f64 = undefined; + switch (icon.wlr_drag_icon.drag.grab_type) { + .keyboard_pointer => { + lx = icon.seat.cursor.wlr_cursor.x; + ly = icon.seat.cursor.wlr_cursor.y; + }, + .keyboard_touch => { + const touch_id = icon.wlr_drag_icon.drag.touch_id; + const point = icon.seat.cursor.touch_points.get(touch_id) orelse continue; + lx = point.lx; + ly = point.ly; + }, + .keyboard => unreachable, + } var rdata = SurfaceRenderData{ .output = output, - .output_x = @floatToInt(i32, drag_icon.seat.cursor.wlr_cursor.x) + - drag_icon.wlr_drag_icon.surface.sx - output_box.x, - .output_y = @floatToInt(i32, drag_icon.seat.cursor.wlr_cursor.y) + - drag_icon.wlr_drag_icon.surface.sy - output_box.y, + .output_x = @floatToInt(i32, lx) + icon.sx - output_box.x, + .output_y = @floatToInt(i32, ly) + icon.sy - output_box.y, .when = now, }; - drag_icon.wlr_drag_icon.surface.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata); + icon.wlr_drag_icon.surface.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata); } }