2020-05-02 10:21:10 -07:00
|
|
|
// This file is part of river, a dynamic tiling wayland compositor.
|
|
|
|
//
|
|
|
|
// Copyright 2020 Isaac Freund
|
2020-07-07 07:39:08 -07:00
|
|
|
// Copyright 2020 Leon Henrik Plickat
|
2020-05-02 10:21:10 -07:00
|
|
|
//
|
|
|
|
// 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/>.
|
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
const Self = @This();
|
|
|
|
|
2020-05-14 10:01:17 -07:00
|
|
|
const build_options = @import("build_options");
|
2020-03-22 14:42:55 -07:00
|
|
|
const std = @import("std");
|
2020-05-02 07:47:10 -07:00
|
|
|
|
2020-03-29 10:36:15 -07:00
|
|
|
const c = @import("c.zig");
|
2020-06-16 16:25:11 -07:00
|
|
|
const log = @import("log.zig");
|
2020-06-16 11:54:05 -07:00
|
|
|
const util = @import("util.zig");
|
2020-03-22 14:42:55 -07:00
|
|
|
|
2020-07-07 07:39:08 -07:00
|
|
|
const Box = @import("Box.zig");
|
|
|
|
const Config = @import("Config.zig");
|
2020-05-02 14:11:56 -07:00
|
|
|
const LayerSurface = @import("LayerSurface.zig");
|
|
|
|
const Output = @import("Output.zig");
|
|
|
|
const Seat = @import("Seat.zig");
|
|
|
|
const View = @import("View.zig");
|
2020-04-24 05:51:10 -07:00
|
|
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
2020-03-23 08:50:20 -07:00
|
|
|
|
2020-08-07 02:51:53 -07:00
|
|
|
const Mode = union(enum) {
|
|
|
|
passthrough: void,
|
2020-08-07 04:54:58 -07:00
|
|
|
down: *View,
|
2020-08-07 02:51:53 -07:00
|
|
|
move: *View,
|
2020-08-07 12:34:38 -07:00
|
|
|
resize: struct {
|
|
|
|
view: *View,
|
|
|
|
/// Offset from the lower right corner of the view
|
|
|
|
offset_x: i32,
|
|
|
|
offset_y: i32,
|
|
|
|
},
|
2020-08-07 02:51:53 -07:00
|
|
|
|
|
|
|
/// Enter move or resize mode
|
|
|
|
fn enter(self: *Self, mode: @TagType(Mode), event: *c.wlr_event_pointer_button, view: *View) void {
|
|
|
|
log.debug(.cursor, "enter {} mode", .{@tagName(mode)});
|
|
|
|
|
2020-08-12 02:07:29 -07:00
|
|
|
self.seat.focus(view);
|
|
|
|
|
2020-08-07 04:54:58 -07:00
|
|
|
switch (mode) {
|
2020-08-07 02:51:53 -07:00
|
|
|
.passthrough => unreachable,
|
2020-08-12 02:07:29 -07:00
|
|
|
.down => {
|
|
|
|
self.mode = .{ .down = view };
|
|
|
|
view.output.root.startTransaction();
|
|
|
|
},
|
2020-08-07 04:54:58 -07:00
|
|
|
.move, .resize => {
|
|
|
|
const cur_box = &view.current.box;
|
|
|
|
self.mode = switch (mode) {
|
|
|
|
.passthrough, .down => unreachable,
|
|
|
|
.move => .{ .move = view },
|
|
|
|
.resize => .{
|
|
|
|
.resize = .{
|
|
|
|
.view = view,
|
2020-08-07 12:34:38 -07:00
|
|
|
.offset_x = cur_box.x + @intCast(i32, cur_box.width) - @floatToInt(i32, self.wlr_cursor.x),
|
|
|
|
.offset_y = cur_box.y + @intCast(i32, cur_box.height) - @floatToInt(i32, self.wlr_cursor.y),
|
2020-08-07 04:54:58 -07:00
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
// Automatically float all views being moved by the pointer
|
|
|
|
if (!view.current.float) {
|
|
|
|
view.pending.float = true;
|
2020-08-11 10:04:37 -07:00
|
|
|
view.float_box = view.current.box;
|
|
|
|
view.applyPending();
|
2020-08-07 04:54:58 -07:00
|
|
|
}
|
2020-08-07 02:51:53 -07:00
|
|
|
|
2020-08-07 04:54:58 -07:00
|
|
|
// Clear cursor focus, so that the surface does not receive events
|
|
|
|
c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat);
|
2020-08-07 02:51:53 -07:00
|
|
|
|
2020-08-07 04:54:58 -07:00
|
|
|
c.wlr_xcursor_manager_set_cursor_image(
|
|
|
|
self.wlr_xcursor_manager,
|
|
|
|
if (mode == .move) "move" else "se-resize",
|
|
|
|
self.wlr_cursor,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
}
|
2020-08-07 02:51:53 -07:00
|
|
|
}
|
|
|
|
|
2020-08-07 04:54:58 -07:00
|
|
|
/// Return from down/move/resize to passthrough
|
2020-08-07 02:51:53 -07:00
|
|
|
fn leave(self: *Self, event: *c.wlr_event_pointer_button) void {
|
|
|
|
std.debug.assert(self.mode != .passthrough);
|
|
|
|
|
|
|
|
log.debug(.cursor, "leave {} mode", .{@tagName(self.mode)});
|
|
|
|
|
2020-08-07 04:54:58 -07:00
|
|
|
// If we were in down mode, we need pass along the release event
|
|
|
|
if (self.mode == .down)
|
|
|
|
_ = c.wlr_seat_pointer_notify_button(
|
|
|
|
self.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.button,
|
|
|
|
event.state,
|
|
|
|
);
|
|
|
|
|
2020-08-07 02:51:53 -07:00
|
|
|
self.mode = .passthrough;
|
|
|
|
passthrough(self, event.time_msec);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn processMotion(self: *Self, device: *c.wlr_input_device, time: u32, delta_x: f64, delta_y: f64) void {
|
|
|
|
const border_width = self.seat.input_manager.server.config.border_width;
|
|
|
|
|
|
|
|
switch (self.mode) {
|
|
|
|
.passthrough => {
|
|
|
|
c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y);
|
|
|
|
passthrough(self, time);
|
|
|
|
},
|
2020-08-07 04:54:58 -07:00
|
|
|
.down => |view| {
|
|
|
|
c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y);
|
|
|
|
c.wlr_seat_pointer_notify_motion(
|
|
|
|
self.seat.wlr_seat,
|
|
|
|
time,
|
|
|
|
self.wlr_cursor.x - @intToFloat(f64, view.current.box.x),
|
|
|
|
self.wlr_cursor.y - @intToFloat(f64, view.current.box.y),
|
|
|
|
);
|
|
|
|
},
|
2020-08-07 02:51:53 -07:00
|
|
|
.move => |view| {
|
|
|
|
var output_width: c_int = undefined;
|
|
|
|
var output_height: c_int = undefined;
|
|
|
|
c.wlr_output_effective_resolution(view.output.wlr_output, &output_width, &output_height);
|
|
|
|
|
|
|
|
// Set x/y of cursor and view, clamp to output dimensions
|
|
|
|
view.pending.box.x = std.math.clamp(
|
|
|
|
view.pending.box.x + @floatToInt(i32, delta_x),
|
|
|
|
@intCast(i32, border_width),
|
|
|
|
output_width - @intCast(i32, view.pending.box.width + border_width),
|
|
|
|
);
|
|
|
|
view.pending.box.y = std.math.clamp(
|
|
|
|
view.pending.box.y + @floatToInt(i32, delta_y),
|
|
|
|
@intCast(i32, border_width),
|
|
|
|
output_height - @intCast(i32, view.pending.box.height + border_width),
|
|
|
|
);
|
|
|
|
|
|
|
|
c.wlr_cursor_move(
|
|
|
|
self.wlr_cursor,
|
|
|
|
device,
|
|
|
|
@intToFloat(f64, view.pending.box.x - view.current.box.x),
|
|
|
|
@intToFloat(f64, view.pending.box.y - view.current.box.y),
|
|
|
|
);
|
|
|
|
|
2020-08-11 10:04:37 -07:00
|
|
|
view.applyPending();
|
2020-08-07 02:51:53 -07:00
|
|
|
},
|
|
|
|
.resize => |data| {
|
|
|
|
var output_width: c_int = undefined;
|
|
|
|
var output_height: c_int = undefined;
|
|
|
|
c.wlr_output_effective_resolution(data.view.output.wlr_output, &output_width, &output_height);
|
|
|
|
|
|
|
|
// Set width/height of view, clamp to view size constraints and output dimensions
|
|
|
|
const box = &data.view.pending.box;
|
|
|
|
box.width = @intCast(u32, std.math.max(0, @intCast(i32, box.width) + @floatToInt(i32, delta_x)));
|
|
|
|
box.height = @intCast(u32, std.math.max(0, @intCast(i32, box.height) + @floatToInt(i32, delta_y)));
|
|
|
|
|
|
|
|
data.view.applyConstraints();
|
|
|
|
|
|
|
|
box.width = std.math.min(box.width, @intCast(u32, output_width - box.x - @intCast(i32, border_width)));
|
|
|
|
box.height = std.math.min(box.height, @intCast(u32, output_height - box.y - @intCast(i32, border_width)));
|
|
|
|
|
2020-08-11 10:04:37 -07:00
|
|
|
data.view.applyPending();
|
2020-08-07 02:51:53 -07:00
|
|
|
|
|
|
|
// Keep cursor locked to the original offset from the bottom right corner
|
|
|
|
c.wlr_cursor_warp_closest(
|
|
|
|
self.wlr_cursor,
|
|
|
|
device,
|
2020-08-07 12:34:38 -07:00
|
|
|
@intToFloat(f64, box.x + @intCast(i32, box.width) - data.offset_x),
|
|
|
|
@intToFloat(f64, box.y + @intCast(i32, box.height) - data.offset_y),
|
2020-08-07 02:51:53 -07:00
|
|
|
);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Pass an event on to the surface under the cursor, if any.
|
|
|
|
fn passthrough(self: *Self, time: u32) void {
|
|
|
|
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 (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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// There is either no surface under the cursor or input is disallowed
|
|
|
|
// Reset the cursor image to the default and clear focus.
|
|
|
|
c.wlr_xcursor_manager_set_cursor_image(
|
|
|
|
self.wlr_xcursor_manager,
|
|
|
|
"left_ptr",
|
|
|
|
self.wlr_cursor,
|
|
|
|
);
|
|
|
|
c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-07-14 08:34:29 -07:00
|
|
|
const default_size = 24;
|
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
seat: *Seat,
|
|
|
|
wlr_cursor: *c.wlr_cursor,
|
|
|
|
wlr_xcursor_manager: *c.wlr_xcursor_manager,
|
|
|
|
|
2020-07-29 07:36:46 -07:00
|
|
|
/// Number of distinct buttons currently pressed
|
|
|
|
pressed_count: u32,
|
2020-07-07 07:39:08 -07:00
|
|
|
|
2020-07-29 07:36:46 -07:00
|
|
|
/// Current cursor mode as well as any state needed to implement that mode
|
|
|
|
mode: Mode,
|
2020-05-02 07:47:10 -07:00
|
|
|
|
|
|
|
listen_axis: c.wl_listener,
|
|
|
|
listen_button: c.wl_listener,
|
|
|
|
listen_frame: c.wl_listener,
|
|
|
|
listen_motion_absolute: c.wl_listener,
|
|
|
|
listen_motion: c.wl_listener,
|
|
|
|
listen_request_set_cursor: c.wl_listener,
|
|
|
|
|
|
|
|
pub fn init(self: *Self, seat: *Seat) !void {
|
|
|
|
self.seat = seat;
|
|
|
|
|
|
|
|
// Creates a wlroots utility for tracking the cursor image shown on screen.
|
2020-06-25 15:59:31 -07:00
|
|
|
self.wlr_cursor = c.wlr_cursor_create() orelse return error.OutOfMemory;
|
2020-05-02 07:47:10 -07:00
|
|
|
c.wlr_cursor_attach_output_layout(self.wlr_cursor, seat.input_manager.server.root.wlr_output_layout);
|
2020-07-14 08:34:29 -07:00
|
|
|
|
|
|
|
// This is here so that self.wlr_xcursor_manager doesn't need to be an
|
|
|
|
// optional pointer. This isn't optimal as it does a needless allocation,
|
|
|
|
// but this is not a hot path.
|
|
|
|
self.wlr_xcursor_manager = c.wlr_xcursor_manager_create(null, default_size) orelse
|
|
|
|
return error.OutOfMemory;
|
|
|
|
try self.setTheme(null, null);
|
2020-05-02 07:47:10 -07:00
|
|
|
|
2020-07-29 07:36:46 -07:00
|
|
|
self.pressed_count = 0;
|
2020-07-28 07:17:16 -07:00
|
|
|
self.mode = .passthrough;
|
2020-05-02 07:47:10 -07:00
|
|
|
|
|
|
|
// wlr_cursor *only* displays an image on screen. It does not move around
|
|
|
|
// when the pointer moves. However, we can attach input devices to it, and
|
|
|
|
// it will generate aggregate events for all of them. In these events, we
|
|
|
|
// can choose how we want to process them, forwarding them to clients and
|
|
|
|
// moving the cursor around. See following post for more detail:
|
|
|
|
// https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html
|
|
|
|
self.listen_axis.notify = handleAxis;
|
|
|
|
c.wl_signal_add(&self.wlr_cursor.events.axis, &self.listen_axis);
|
|
|
|
|
|
|
|
self.listen_button.notify = handleButton;
|
|
|
|
c.wl_signal_add(&self.wlr_cursor.events.button, &self.listen_button);
|
|
|
|
|
|
|
|
self.listen_frame.notify = handleFrame;
|
|
|
|
c.wl_signal_add(&self.wlr_cursor.events.frame, &self.listen_frame);
|
|
|
|
|
|
|
|
self.listen_motion_absolute.notify = handleMotionAbsolute;
|
|
|
|
c.wl_signal_add(&self.wlr_cursor.events.motion_absolute, &self.listen_motion_absolute);
|
|
|
|
|
|
|
|
self.listen_motion.notify = handleMotion;
|
|
|
|
c.wl_signal_add(&self.wlr_cursor.events.motion, &self.listen_motion);
|
|
|
|
|
|
|
|
self.listen_request_set_cursor.notify = handleRequestSetCursor;
|
|
|
|
c.wl_signal_add(&self.seat.wlr_seat.events.request_set_cursor, &self.listen_request_set_cursor);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn deinit(self: *Self) void {
|
|
|
|
c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager);
|
|
|
|
c.wlr_cursor_destroy(self.wlr_cursor);
|
|
|
|
}
|
|
|
|
|
2020-07-14 08:34:29 -07:00
|
|
|
/// Set the cursor theme for the given seat, as well as the xwayland theme if
|
2020-07-17 11:40:33 -07:00
|
|
|
/// this is the default seat. Either argument may be null, in which case a
|
|
|
|
/// default will be used.
|
2020-07-17 03:56:15 -07:00
|
|
|
pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void {
|
2020-07-14 08:34:29 -07:00
|
|
|
const server = self.seat.input_manager.server;
|
2020-07-17 03:56:15 -07:00
|
|
|
const size = _size orelse default_size;
|
2020-07-14 08:34:29 -07:00
|
|
|
|
|
|
|
c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager);
|
2020-07-17 03:56:15 -07:00
|
|
|
self.wlr_xcursor_manager = c.wlr_xcursor_manager_create(theme, size) orelse
|
2020-07-14 08:34:29 -07:00
|
|
|
return error.OutOfMemory;
|
|
|
|
|
|
|
|
// For each output, ensure a theme of the proper scale is loaded
|
|
|
|
var it = server.root.outputs.first;
|
|
|
|
while (it) |node| : (it = node.next) {
|
|
|
|
const wlr_output = node.data.wlr_output;
|
2020-07-16 12:20:43 -07:00
|
|
|
if (!c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, wlr_output.scale))
|
2020-07-14 08:34:29 -07:00
|
|
|
log.err(.cursor, "failed to load xcursor theme '{}' at scale {}", .{ theme, wlr_output.scale });
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this cursor belongs to the default seat, set the xcursor environment
|
|
|
|
// variables and the xwayland cursor theme.
|
|
|
|
if (self.seat == self.seat.input_manager.default_seat) {
|
|
|
|
const size_str = try std.fmt.allocPrint0(util.gpa, "{}", .{size});
|
|
|
|
defer util.gpa.free(size_str);
|
|
|
|
if (c.setenv("XCURSOR_SIZE", size_str, 1) < 0) return error.OutOfMemory;
|
|
|
|
if (theme) |t| if (c.setenv("XCURSOR_THEME", t, 1) < 0) return error.OutOfMemory;
|
|
|
|
|
|
|
|
if (build_options.xwayland) {
|
2020-07-16 12:20:43 -07:00
|
|
|
if (c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, 1)) {
|
2020-07-14 08:34:29 -07:00
|
|
|
const wlr_xcursor = c.wlr_xcursor_manager_get_xcursor(self.wlr_xcursor_manager, "left_ptr", 1).?;
|
|
|
|
const image: *c.wlr_xcursor_image = wlr_xcursor.*.images[0];
|
|
|
|
c.wlr_xwayland_set_cursor(
|
|
|
|
server.wlr_xwayland,
|
|
|
|
image.buffer,
|
|
|
|
image.width * 4,
|
|
|
|
image.width,
|
|
|
|
image.height,
|
|
|
|
@intCast(i32, image.hotspot_x),
|
|
|
|
@intCast(i32, image.hotspot_y),
|
|
|
|
);
|
|
|
|
} else log.err(.cursor, "failed to load xcursor theme '{}' at scale 1", .{theme});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
fn handleAxis(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
|
|
// This event is forwarded by the cursor when a pointer emits an axis event,
|
|
|
|
// for example when you move the scroll wheel.
|
|
|
|
const cursor = @fieldParentPtr(Self, "listen_axis", listener.?);
|
2020-06-16 11:54:05 -07:00
|
|
|
const event = util.voidCast(c.wlr_event_pointer_axis, data.?);
|
2020-05-02 07:47:10 -07:00
|
|
|
|
|
|
|
// Notify the client with pointer focus of the axis event.
|
|
|
|
c.wlr_seat_pointer_notify_axis(
|
|
|
|
cursor.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.orientation,
|
|
|
|
event.delta,
|
|
|
|
event.delta_discrete,
|
|
|
|
event.source,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
|
|
// This event is forwarded by the cursor when a pointer emits a button
|
|
|
|
// event.
|
|
|
|
const self = @fieldParentPtr(Self, "listen_button", listener.?);
|
2020-06-16 11:54:05 -07:00
|
|
|
const event = util.voidCast(c.wlr_event_pointer_button, data.?);
|
2020-07-29 07:36:46 -07:00
|
|
|
|
|
|
|
if (event.state == .WLR_BUTTON_PRESSED) {
|
|
|
|
self.pressed_count += 1;
|
|
|
|
} else {
|
|
|
|
std.debug.assert(self.pressed_count > 0);
|
|
|
|
self.pressed_count -= 1;
|
|
|
|
if (self.pressed_count == 0 and self.mode != .passthrough) {
|
2020-08-07 02:51:53 -07:00
|
|
|
Mode.leave(self, event);
|
2020-07-29 07:36:46 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
var sx: f64 = undefined;
|
|
|
|
var sy: f64 = undefined;
|
|
|
|
if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |wlr_surface| {
|
|
|
|
// If the found surface is a keyboard inteactive layer surface,
|
|
|
|
// give it keyboard focus.
|
|
|
|
if (c.wlr_surface_is_layer_surface(wlr_surface)) {
|
|
|
|
const wlr_layer_surface = c.wlr_layer_surface_v1_from_wlr_surface(wlr_surface);
|
|
|
|
if (wlr_layer_surface.*.current.keyboard_interactive) {
|
2020-06-16 11:54:05 -07:00
|
|
|
const layer_surface = util.voidCast(LayerSurface, wlr_layer_surface.*.data.?);
|
2020-05-02 07:47:10 -07:00
|
|
|
self.seat.setFocusRaw(.{ .layer = layer_surface });
|
2020-04-24 05:51:10 -07:00
|
|
|
}
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
2020-04-18 03:21:43 -07:00
|
|
|
|
2020-07-31 06:39:59 -07:00
|
|
|
// If the target surface has a view, give that view keyboard focus and
|
|
|
|
// perhaps enter move/resize mode.
|
|
|
|
if (View.fromWlrSurface(wlr_surface)) |view| {
|
|
|
|
if (event.state == .WLR_BUTTON_PRESSED and self.pressed_count == 1) {
|
|
|
|
// If the button is pressed and the pointer modifier is
|
|
|
|
// active, enter cursor mode or close view and return.
|
|
|
|
const fullscreen = view.current.fullscreen or view.pending.fullscreen;
|
|
|
|
if (self.seat.pointer_modifier) {
|
|
|
|
switch (event.button) {
|
2020-08-07 02:51:53 -07:00
|
|
|
c.BTN_LEFT => if (!fullscreen) Mode.enter(self, .move, event, view),
|
2020-07-31 06:39:59 -07:00
|
|
|
c.BTN_MIDDLE => view.close(),
|
2020-08-07 02:51:53 -07:00
|
|
|
c.BTN_RIGHT => if (!fullscreen) Mode.enter(self, .resize, event, view),
|
2020-07-31 06:39:59 -07:00
|
|
|
|
|
|
|
// TODO Some mice have additional buttons. These
|
|
|
|
// could also be bound to some useful action.
|
|
|
|
else => {},
|
2020-07-07 07:39:08 -07:00
|
|
|
}
|
2020-07-31 06:39:59 -07:00
|
|
|
return;
|
2020-08-07 04:54:58 -07:00
|
|
|
} else {
|
|
|
|
Mode.enter(self, .down, event, view);
|
2020-07-07 07:39:08 -07:00
|
|
|
}
|
2020-04-18 03:21:43 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
_ = c.wlr_seat_pointer_notify_button(
|
|
|
|
self.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.button,
|
|
|
|
event.state,
|
2020-04-18 03:21:43 -07:00
|
|
|
);
|
|
|
|
}
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
|
|
// This event is forwarded by the cursor when a pointer emits an frame
|
|
|
|
// event. Frame events are sent after regular pointer events to group
|
|
|
|
// multiple events together. For instance, two axis events may happen at the
|
|
|
|
// same time, in which case a frame event won't be sent in between.
|
|
|
|
const self = @fieldParentPtr(Self, "listen_frame", listener.?);
|
|
|
|
// Notify the client with pointer focus of the frame event.
|
|
|
|
c.wlr_seat_pointer_notify_frame(self.seat.wlr_seat);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handleMotionAbsolute(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
|
|
// This event is forwarded by the cursor when a pointer emits an _absolute_
|
|
|
|
// motion event, from 0..1 on each axis. This happens, for example, when
|
|
|
|
// wlroots is running under a Wayland window rather than KMS+DRM, and you
|
|
|
|
// move the mouse over the window. You could enter the window from any edge,
|
|
|
|
// so we have to warp the mouse there. There is also some hardware which
|
|
|
|
// emits these events.
|
|
|
|
const self = @fieldParentPtr(Self, "listen_motion_absolute", listener.?);
|
2020-06-16 11:54:05 -07:00
|
|
|
const event = util.voidCast(c.wlr_event_pointer_motion_absolute, data.?);
|
2020-08-07 02:51:53 -07:00
|
|
|
|
|
|
|
var lx: f64 = undefined;
|
|
|
|
var ly: f64 = undefined;
|
|
|
|
c.wlr_cursor_absolute_to_layout_coords(self.wlr_cursor, event.device, event.x, event.y, &lx, &ly);
|
|
|
|
|
|
|
|
Mode.processMotion(self, event.device, event.time_msec, lx - self.wlr_cursor.x, ly - self.wlr_cursor.y);
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handleMotion(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
|
|
// This event is forwarded by the cursor when a pointer emits a _relative_
|
|
|
|
// pointer motion event (i.e. a delta)
|
|
|
|
const self = @fieldParentPtr(Self, "listen_motion", listener.?);
|
2020-06-16 11:54:05 -07:00
|
|
|
const event = util.voidCast(c.wlr_event_pointer_motion, data.?);
|
2020-08-07 02:51:53 -07:00
|
|
|
|
|
|
|
Mode.processMotion(self, event.device, event.time_msec, event.delta_x, event.delta_y);
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handleRequestSetCursor(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
|
|
// This event is rasied by the seat when a client provides a cursor image
|
|
|
|
const self = @fieldParentPtr(Self, "listen_request_set_cursor", listener.?);
|
2020-06-16 11:54:05 -07:00
|
|
|
const event = util.voidCast(c.wlr_seat_pointer_request_set_cursor_event, data.?);
|
2020-05-02 07:47:10 -07:00
|
|
|
const focused_client = self.seat.wlr_seat.pointer_state.focused_client;
|
|
|
|
|
|
|
|
// This can be sent by any client, so we check to make sure this one is
|
|
|
|
// actually has pointer focus first.
|
|
|
|
if (focused_client == event.seat_client) {
|
|
|
|
// Once we've vetted the client, we can tell the cursor to use the
|
|
|
|
// provided surface as the cursor image. It will set the hardware cursor
|
|
|
|
// on the output that it's currently on and continue to do so as the
|
|
|
|
// cursor moves between outputs.
|
2020-06-16 16:25:11 -07:00
|
|
|
log.debug(.cursor, "focused client set cursor", .{});
|
2020-05-02 07:47:10 -07:00
|
|
|
c.wlr_cursor_set_surface(
|
|
|
|
self.wlr_cursor,
|
|
|
|
event.surface,
|
|
|
|
event.hotspot_x,
|
|
|
|
event.hotspot_y,
|
2020-04-18 03:21:43 -07:00
|
|
|
);
|
|
|
|
}
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Find the topmost surface under the output layout coordinates lx/ly
|
|
|
|
/// returns the surface if found and sets the sx/sy parametes to the
|
|
|
|
/// surface coordinates.
|
|
|
|
fn surfaceAt(self: Self, lx: f64, ly: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
|
|
|
|
// Find the output to check
|
|
|
|
const root = self.seat.input_manager.server.root;
|
2020-06-27 08:33:09 -07:00
|
|
|
const wlr_output = c.wlr_output_layout_output_at(root.wlr_output_layout, lx, ly) orelse return null;
|
2020-06-16 11:54:05 -07:00
|
|
|
const output = util.voidCast(Output, wlr_output.*.data orelse return null);
|
2020-05-02 07:47:10 -07:00
|
|
|
|
|
|
|
// Get output-local coords from the layout coords
|
|
|
|
var ox = lx;
|
|
|
|
var oy = ly;
|
|
|
|
c.wlr_output_layout_output_coords(root.wlr_output_layout, wlr_output, &ox, &oy);
|
|
|
|
|
|
|
|
// Check layers and views from top to bottom
|
|
|
|
const layer_idxs = [_]usize{
|
|
|
|
c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
|
|
|
|
c.ZWLR_LAYER_SHELL_V1_LAYER_TOP,
|
|
|
|
c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM,
|
|
|
|
c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Check overlay layer incl. popups
|
2020-06-27 08:33:09 -07:00
|
|
|
if (layerSurfaceAt(output.*, output.layers[layer_idxs[0]], ox, oy, sx, sy, false)) |s| return s;
|
2020-04-24 05:51:10 -07:00
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
// Check top-background popups only
|
2020-06-27 08:33:09 -07:00
|
|
|
for (layer_idxs[1..4]) |idx|
|
|
|
|
if (layerSurfaceAt(output.*, output.layers[idx], ox, oy, sx, sy, true)) |s| return s;
|
2020-03-24 12:35:45 -07:00
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
// Check top layer
|
2020-06-27 08:33:09 -07:00
|
|
|
if (layerSurfaceAt(output.*, output.layers[layer_idxs[1]], ox, oy, sx, sy, false)) |s| return s;
|
2020-04-24 05:51:10 -07:00
|
|
|
|
2020-06-27 08:33:09 -07:00
|
|
|
// Check views
|
|
|
|
if (viewSurfaceAt(output.*, ox, oy, sx, sy)) |s| return s;
|
2020-03-22 14:42:55 -07:00
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
// Check the bottom-background layers
|
2020-06-27 08:33:09 -07:00
|
|
|
for (layer_idxs[2..4]) |idx|
|
|
|
|
if (layerSurfaceAt(output.*, output.layers[idx], ox, oy, sx, sy, false)) |s| return s;
|
2020-04-24 05:51:10 -07:00
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Find the topmost surface on the given layer at ox,oy. Will only check
|
|
|
|
/// popups if popups_only is true.
|
|
|
|
fn layerSurfaceAt(
|
|
|
|
output: Output,
|
|
|
|
layer: std.TailQueue(LayerSurface),
|
|
|
|
ox: f64,
|
|
|
|
oy: f64,
|
|
|
|
sx: *f64,
|
|
|
|
sy: *f64,
|
|
|
|
popups_only: bool,
|
|
|
|
) ?*c.wlr_surface {
|
|
|
|
var it = layer.first;
|
|
|
|
while (it) |node| : (it = node.next) {
|
|
|
|
const layer_surface = &node.data;
|
|
|
|
const surface = c.wlr_layer_surface_v1_surface_at(
|
|
|
|
layer_surface.wlr_layer_surface,
|
|
|
|
ox - @intToFloat(f64, layer_surface.box.x),
|
|
|
|
oy - @intToFloat(f64, layer_surface.box.y),
|
|
|
|
sx,
|
|
|
|
sy,
|
|
|
|
);
|
|
|
|
if (surface) |found| {
|
|
|
|
if (!popups_only) {
|
|
|
|
return found;
|
|
|
|
} else if (c.wlr_surface_is_xdg_surface(found)) {
|
|
|
|
const wlr_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(found);
|
|
|
|
if (wlr_xdg_surface.*.role == .WLR_XDG_SURFACE_ROLE_POPUP) {
|
2020-04-24 05:51:10 -07:00
|
|
|
return found;
|
|
|
|
}
|
|
|
|
}
|
2020-03-22 14:42:55 -07:00
|
|
|
}
|
2020-04-24 05:51:10 -07:00
|
|
|
}
|
2020-05-02 07:47:10 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-06-27 08:33:09 -07:00
|
|
|
/// Find the topmost visible view surface (incl. popups) at ox,oy.
|
|
|
|
fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
|
2020-06-27 15:29:44 -07:00
|
|
|
// Focused views are rendered on top, so look for them first.
|
2020-07-02 12:55:21 -07:00
|
|
|
var it = ViewStack(View).iterator(output.views.first, output.current.tags);
|
2020-06-27 15:29:44 -07:00
|
|
|
while (it.next()) |node| {
|
2020-08-03 06:00:04 -07:00
|
|
|
if (node.view.current.focus == 0) continue;
|
2020-06-27 15:29:44 -07:00
|
|
|
if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found;
|
|
|
|
}
|
|
|
|
|
2020-07-02 12:55:21 -07:00
|
|
|
it = ViewStack(View).iterator(output.views.first, output.current.tags);
|
2020-06-27 15:29:44 -07:00
|
|
|
while (it.next()) |node| {
|
|
|
|
if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|