seat: implement drag and drop
This commit is contained in:
parent
976a3ce73d
commit
f597e7da63
|
@ -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
48
river/DragIcon.zig
Normal 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);
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue