aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--river/Cursor.zig24
-rw-r--r--river/DragIcon.zig48
-rw-r--r--river/Root.zig3
-rw-r--r--river/Seat.zig38
-rw-r--r--river/render.zig31
5 files changed, 122 insertions, 22 deletions
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 <https://www.gnu.org/licenses/>.
+
+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);