diff --git a/river/Cursor.zig b/river/Cursor.zig index d9d0696..045d08a 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -188,24 +188,18 @@ const Mode = union(enum) { var sx: f64 = undefined; var sy: f64 = undefined; if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |wlr_surface| { - // If input is allowed on the surface, send a pointer enter - // or motion even as needed. + // If input is allowed on the surface, send pointer enter and motion + // events. Note that wlroots won't actually send an enter event if + // the surface has already been entered. if (self.seat.input_manager.inputAllowed(wlr_surface)) { - const wlr_seat = self.seat.wlr_seat; - const focus_change = wlr_seat.pointer_state.focused_surface != wlr_surface; - if (focus_change) { - log.debug(.cursor, "pointer notify enter at ({},{})", .{ sx, sy }); - c.wlr_seat_pointer_notify_enter(wlr_seat, wlr_surface, sx, sy); - } else { - c.wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); - } - return; + c.wlr_seat_pointer_notify_enter(self.seat.wlr_seat, wlr_surface, sx, sy); + c.wlr_seat_pointer_notify_motion(self.seat.wlr_seat, time, sx, sy); } + } else { + // There is either no surface under the cursor or input is disallowed + // Reset the cursor image to the default and clear focus. + self.clearFocus(); } - - // There is either no surface under the cursor or input is disallowed - // Reset the cursor image to the default and clear focus. - self.clearFocus(); } }; diff --git a/river/DragIcon.zig b/river/DragIcon.zig new file mode 100644 index 0000000..ff9854b --- /dev/null +++ b/river/DragIcon.zig @@ -0,0 +1,48 @@ +// This file is part of river, a dynamic tiling wayland compositor. +// +// Copyright 2020 Isaac Freund +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +const Self = @This(); + +const std = @import("std"); + +const c = @import("c.zig"); +const util = @import("util.zig"); + +const Seat = @import("Seat.zig"); + +seat: *Seat, +wlr_drag_icon: *c.wlr_drag_icon, + +listen_destroy: c.wl_listener = undefined, + +pub fn init(self: *Self, seat: *Seat, wlr_drag_icon: *c.wlr_drag_icon) void { + self.* = .{ + .seat = seat, + .wlr_drag_icon = wlr_drag_icon, + }; + + self.listen_destroy.notify = handleDestroy; + c.wl_signal_add(&wlr_drag_icon.events.destroy, &self.listen_destroy); +} + +fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { + const self = @fieldParentPtr(Self, "listen_destroy", listener.?); + const root = &self.seat.input_manager.server.root; + const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self); + root.drag_icons.remove(node); + util.gpa.destroy(node); +} diff --git a/river/Root.zig b/river/Root.zig index 83d79db..a128319 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -29,6 +29,7 @@ const Server = @import("Server.zig"); const View = @import("View.zig"); const ViewStack = @import("view_stack.zig").ViewStack; const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig"); +const DragIcon = @import("DragIcon.zig"); /// Responsible for all windowing operations server: *Server, @@ -40,6 +41,8 @@ outputs: std.TailQueue(Output) = std.TailQueue(Output).init(), /// It is not advertised to clients. noop_output: Output = undefined, +drag_icons: std.SinglyLinkedList(DragIcon) = std.SinglyLinkedList(DragIcon).init(), + /// This list stores all unmanaged Xwayland windows. This needs to be in root /// since X is like the wild west and who knows where these things will go. xwayland_unmanaged_views: if (build_options.xwayland) diff --git a/river/Seat.zig b/river/Seat.zig index 6a2c15e..2527466 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -25,6 +25,7 @@ const command = @import("command.zig"); const log = @import("log.zig"); const util = @import("util.zig"); +const DragIcon = @import("DragIcon.zig"); const Cursor = @import("Cursor.zig"); const InputManager = @import("InputManager.zig"); const Keyboard = @import("Keyboard.zig"); @@ -66,6 +67,8 @@ focus_stack: ViewStack(*View) = ViewStack(*View){}, status_trackers: std.SinglyLinkedList(SeatStatus) = std.SinglyLinkedList(SeatStatus).init(), listen_request_set_selection: c.wl_listener = undefined, +listen_request_start_drag: c.wl_listener = undefined, +listen_start_drag: c.wl_listener = undefined, pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !void { self.* = .{ @@ -80,6 +83,12 @@ pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !voi self.listen_request_set_selection.notify = handleRequestSetSelection; c.wl_signal_add(&self.wlr_seat.events.request_set_selection, &self.listen_request_set_selection); + + self.listen_request_start_drag.notify = handleRequestStartDrag; + c.wl_signal_add(&self.wlr_seat.events.request_start_drag, &self.listen_request_start_drag); + + self.listen_start_drag.notify = handleStartDrag; + c.wl_signal_add(&self.wlr_seat.events.start_drag, &self.listen_start_drag); } pub fn deinit(self: *Self) void { @@ -324,3 +333,32 @@ fn handleRequestSetSelection(listener: ?*c.wl_listener, data: ?*c_void) callconv const event = util.voidCast(c.wlr_seat_request_set_selection_event, data.?); c.wlr_seat_set_selection(self.wlr_seat, event.source, event.serial); } + +fn handleRequestStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { + const self = @fieldParentPtr(Self, "listen_request_start_drag", listener.?); + const event = util.voidCast(c.wlr_seat_request_start_drag_event, data.?); + + if (c.wlr_seat_validate_pointer_grab_serial(self.wlr_seat, event.origin, event.serial)) { + log.debug(.seat, "starting pointer drag", .{}); + c.wlr_seat_start_pointer_drag(self.wlr_seat, event.drag, event.serial); + return; + } + + log.debug(.seat, "ignoring request to start drag, failed to validate serial {}", .{event.serial}); + c.wlr_data_source_destroy(event.drag.*.source); +} + +fn handleStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { + const self = @fieldParentPtr(Self, "listen_start_drag", listener.?); + const wlr_drag = util.voidCast(c.wlr_drag, data.?); + + if (wlr_drag.icon) |wlr_drag_icon| { + const node = util.gpa.create(std.SinglyLinkedList(DragIcon).Node) catch { + log.crit(.seat, "out of memory", .{}); + return; + }; + node.data.init(self, wlr_drag_icon); + self.input_manager.server.root.drag_icons.prepend(node); + } + self.cursor.mode = .passthrough; +} diff --git a/river/render.zig b/river/render.zig index 9e3beaa..65a4ab6 100644 --- a/river/render.zig +++ b/river/render.zig @@ -103,6 +103,8 @@ pub fn renderOutput(output: *Output) void { // The overlay layer is rendered in both fullscreen and normal cases renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now); + renderDragIcons(output.*, &now); + // Hardware cursors are rendered by the GPU on a separate plane, and can be // moved around without re-rendering what's beneath them - which is more // efficient. However, not all hardware supports hardware cursors. For this @@ -175,13 +177,28 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void { } } +fn renderDragIcons(output: Output, now: *c.timespec) void { + const output_box = c.wlr_output_layout_get_box(output.root.wlr_output_layout, output.wlr_output); + + var it = output.root.drag_icons.first; + while (it) |node| : (it = node.next) { + const drag_icon = &node.data; + + 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, + .when = now, + }; + c.wlr_surface_for_each_surface(drag_icon.wlr_drag_icon.surface, renderSurfaceIterator, &rdata); + } +} + /// Render all xwayland unmanaged windows that appear on the output fn renderXwaylandUnmanaged(output: Output, now: *c.timespec) void { - const root = output.root; - const output_box: *c.wlr_box = c.wlr_output_layout_get_box( - root.wlr_output_layout, - output.wlr_output, - ); + const output_box = c.wlr_output_layout_get_box(output.root.wlr_output_layout, output.wlr_output); var it = output.root.xwayland_unmanaged_views.first; while (it) |node| : (it = node.next) { @@ -189,8 +206,8 @@ fn renderXwaylandUnmanaged(output: Output, now: *c.timespec) void { var rdata = SurfaceRenderData{ .output = &output, - .output_x = wlr_xwayland_surface.x - output_box.x, - .output_y = wlr_xwayland_surface.y - output_box.y, + .output_x = wlr_xwayland_surface.x - output_box.*.x, + .output_y = wlr_xwayland_surface.y - output_box.*.y, .when = now, }; c.wlr_surface_for_each_surface(wlr_xwayland_surface.surface, renderSurfaceIterator, &rdata);