seat: implement drag and drop

This commit is contained in:
Isaac Freund 2020-09-10 01:13:47 +02:00
parent 976a3ce73d
commit f597e7da63
No known key found for this signature in database
GPG key ID: 86DED400DDFD7A11
5 changed files with 122 additions and 22 deletions

View file

@ -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();
}
};

48
river/DragIcon.zig Normal file
View file

@ -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);
}

View file

@ -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)

View file

@ -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;
}

View file

@ -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);