2020-03-22 14:42:55 -07:00
|
|
|
const std = @import("std");
|
|
|
|
const c = @import("c.zig").c;
|
|
|
|
|
2020-03-23 08:50:20 -07:00
|
|
|
const Seat = @import("seat.zig").Seat;
|
2020-03-23 13:51:46 -07:00
|
|
|
const Server = @import("server.zig").Server;
|
2020-03-23 08:50:20 -07:00
|
|
|
const View = @import("view.zig").View;
|
|
|
|
|
2020-03-22 14:42:55 -07:00
|
|
|
const CursorMode = enum {
|
|
|
|
Passthrough,
|
|
|
|
Move,
|
|
|
|
Resize,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub const Cursor = struct {
|
|
|
|
seat: *Seat,
|
|
|
|
wlr_cursor: *c.wlr_cursor,
|
|
|
|
wlr_xcursor_manager: *c.wlr_xcursor_manager,
|
|
|
|
|
|
|
|
listen_motion: c.wl_listener,
|
|
|
|
listen_motion_absolute: c.wl_listener,
|
|
|
|
listen_button: c.wl_listener,
|
|
|
|
listen_axis: c.wl_listener,
|
|
|
|
listen_frame: c.wl_listener,
|
|
|
|
|
2020-03-23 08:50:20 -07:00
|
|
|
listen_request_set_cursor: c.wl_listener,
|
2020-03-22 14:42:55 -07:00
|
|
|
|
2020-03-23 08:50:20 -07:00
|
|
|
mode: CursorMode,
|
2020-03-22 14:42:55 -07:00
|
|
|
grabbed_view: ?*View,
|
|
|
|
grab_x: f64,
|
|
|
|
grab_y: f64,
|
|
|
|
grab_width: c_int,
|
|
|
|
grab_height: c_int,
|
|
|
|
resize_edges: u32,
|
|
|
|
|
2020-03-23 13:51:46 -07:00
|
|
|
pub fn create(seat: *Seat) !@This() {
|
2020-03-24 12:35:45 -07:00
|
|
|
const cursor = @This(){
|
2020-03-22 14:42:55 -07:00
|
|
|
.seat = seat,
|
|
|
|
|
|
|
|
// Creates a wlroots utility for tracking the cursor image shown on screen.
|
2020-03-23 13:51:46 -07:00
|
|
|
//
|
|
|
|
// TODO: free this, it allocates!
|
2020-03-22 14:42:55 -07:00
|
|
|
.wlr_cursor = c.wlr_cursor_create() orelse
|
|
|
|
return error.CantCreateWlrCursor,
|
|
|
|
|
|
|
|
// Creates an xcursor manager, another wlroots utility which loads up
|
|
|
|
// Xcursor themes to source cursor images from and makes sure that cursor
|
|
|
|
// images are available at all scale factors on the screen (necessary for
|
|
|
|
// HiDPI support). We add a cursor theme at scale factor 1 to begin with.
|
2020-03-23 13:51:46 -07:00
|
|
|
//
|
|
|
|
// TODO: free this, it allocates!
|
2020-03-22 14:42:55 -07:00
|
|
|
.wlr_xcursor_manager = c.wlr_xcursor_manager_create(null, 24) orelse
|
|
|
|
return error.CantCreateWlrXCursorManager,
|
|
|
|
|
|
|
|
.listen_motion = c.wl_listener{
|
|
|
|
.link = undefined,
|
|
|
|
.notify = @This().handle_motion,
|
|
|
|
},
|
|
|
|
.listen_motion_absolute = c.wl_listener{
|
|
|
|
.link = undefined,
|
|
|
|
.notify = @This().handle_motion_absolute,
|
|
|
|
},
|
|
|
|
.listen_button = c.wl_listener{
|
|
|
|
.link = undefined,
|
|
|
|
.notify = @This().handle_button,
|
|
|
|
},
|
|
|
|
.listen_axis = c.wl_listener{
|
|
|
|
.link = undefined,
|
|
|
|
.notify = @This().handle_axis,
|
|
|
|
},
|
|
|
|
.listen_frame = c.wl_listener{
|
|
|
|
.link = undefined,
|
|
|
|
.notify = @This().handle_frame,
|
|
|
|
},
|
|
|
|
|
|
|
|
.listen_request_set_cursor = c.wl_listener{
|
|
|
|
.link = undefined,
|
|
|
|
.notify = @This().handle_request_set_cursor,
|
|
|
|
},
|
|
|
|
|
|
|
|
.mode = CursorMode.Passthrough,
|
|
|
|
|
|
|
|
.grabbed_view = null,
|
|
|
|
.grab_x = 0.0,
|
|
|
|
.grab_y = 0.0,
|
|
|
|
.grab_width = 0,
|
|
|
|
.grab_height = 0,
|
|
|
|
.resize_edges = 0,
|
|
|
|
};
|
|
|
|
|
2020-03-23 08:50:20 -07:00
|
|
|
c.wlr_cursor_attach_output_layout(cursor.wlr_cursor, seat.server.wlr_output_layout);
|
|
|
|
_ = c.wlr_xcursor_manager_load(cursor.wlr_xcursor_manager, 1);
|
2020-03-22 14:42:55 -07:00
|
|
|
|
2020-03-23 13:51:46 -07:00
|
|
|
return cursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn init(self: *@This()) void {
|
2020-03-22 14:42:55 -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
|
2020-03-24 12:03:48 -07:00
|
|
|
c.wl_signal_add(&self.wlr_cursor.events.motion, &self.listen_motion);
|
|
|
|
c.wl_signal_add(&self.wlr_cursor.events.motion_absolute, &self.listen_motion_absolute);
|
|
|
|
c.wl_signal_add(&self.wlr_cursor.events.button, &self.listen_button);
|
|
|
|
c.wl_signal_add(&self.wlr_cursor.events.axis, &self.listen_axis);
|
|
|
|
c.wl_signal_add(&self.wlr_cursor.events.frame, &self.listen_frame);
|
2020-03-22 14:42:55 -07:00
|
|
|
|
|
|
|
// This listens for clients requesting a specific cursor image
|
2020-03-23 13:51:46 -07:00
|
|
|
c.wl_signal_add(&self.seat.wlr_seat.events.request_set_cursor, &self.listen_request_set_cursor);
|
2020-03-22 14:42:55 -07:00
|
|
|
}
|
|
|
|
|
2020-03-23 08:50:20 -07:00
|
|
|
fn process_move(self: *@This(), time: u32) void {
|
2020-03-22 14:42:55 -07:00
|
|
|
// Move the grabbed view to the new position.
|
2020-03-24 12:39:02 -07:00
|
|
|
// TODO: log on null
|
|
|
|
if (self.grabbed_view) |view| {
|
|
|
|
view.x = @floatToInt(c_int, self.wlr_cursor.x - self.grab_x);
|
|
|
|
view.y = @floatToInt(c_int, self.wlr_cursor.y - self.grab_y);
|
|
|
|
}
|
2020-03-22 14:42:55 -07:00
|
|
|
}
|
|
|
|
|
2020-03-23 08:50:20 -07:00
|
|
|
fn process_resize(self: *@This(), time: u32) void {
|
2020-03-22 14:42:55 -07:00
|
|
|
// Resizing the grabbed view can be a little bit complicated, because we
|
|
|
|
// could be resizing from any corner or edge. This not only resizes the view
|
|
|
|
// on one or two axes, but can also move the view if you resize from the top
|
|
|
|
// or left edges (or top-left corner).
|
|
|
|
//
|
|
|
|
// Note that I took some shortcuts here. In a more fleshed-out compositor,
|
|
|
|
// you'd wait for the client to prepare a buffer at the new size, then
|
|
|
|
// commit any movement that was prepared.
|
|
|
|
|
2020-03-23 08:50:20 -07:00
|
|
|
// TODO: Handle null view
|
2020-03-24 12:39:02 -07:00
|
|
|
const view = self.grabbed_view.?;
|
2020-03-24 12:35:45 -07:00
|
|
|
|
|
|
|
const dx: f64 = self.wlr_cursor.x - self.grab_x;
|
|
|
|
const dy: f64 = self.wlr_cursor.y - self.grab_y;
|
2020-03-23 08:50:20 -07:00
|
|
|
|
2020-03-24 12:39:02 -07:00
|
|
|
var x: f64 = @intToFloat(f64, view.x);
|
|
|
|
var y: f64 = @intToFloat(f64, view.y);
|
2020-03-22 14:42:55 -07:00
|
|
|
|
2020-03-23 08:50:20 -07:00
|
|
|
var width = @intToFloat(f64, self.grab_width);
|
|
|
|
var height = @intToFloat(f64, self.grab_height);
|
2020-03-24 12:35:45 -07:00
|
|
|
|
2020-03-23 08:50:20 -07:00
|
|
|
if (self.resize_edges & @intCast(u32, c.WLR_EDGE_TOP) != 0) {
|
|
|
|
y = self.grab_y + dy;
|
2020-03-22 14:42:55 -07:00
|
|
|
height -= dy;
|
|
|
|
if (height < 1) {
|
|
|
|
y += height;
|
|
|
|
}
|
2020-03-23 08:50:20 -07:00
|
|
|
} else if (self.resize_edges & @intCast(u32, c.WLR_EDGE_BOTTOM) != 0) {
|
2020-03-22 14:42:55 -07:00
|
|
|
height += dy;
|
|
|
|
}
|
2020-03-23 08:50:20 -07:00
|
|
|
if (self.resize_edges & @intCast(u32, c.WLR_EDGE_LEFT) != 0) {
|
|
|
|
x = self.grab_x + dx;
|
2020-03-22 14:42:55 -07:00
|
|
|
width -= dx;
|
|
|
|
if (width < 1) {
|
|
|
|
x += width;
|
|
|
|
}
|
2020-03-23 08:50:20 -07:00
|
|
|
} else if (self.resize_edges & @intCast(u32, c.WLR_EDGE_RIGHT) != 0) {
|
2020-03-22 14:42:55 -07:00
|
|
|
width += dx;
|
|
|
|
}
|
2020-03-24 12:39:02 -07:00
|
|
|
view.x = @floatToInt(c_int, x);
|
|
|
|
view.y = @floatToInt(c_int, y);
|
2020-03-22 14:42:55 -07:00
|
|
|
_ = c.wlr_xdg_toplevel_set_size(
|
2020-03-24 12:39:02 -07:00
|
|
|
view.wlr_xdg_surface,
|
2020-03-22 14:42:55 -07:00
|
|
|
@floatToInt(u32, width),
|
|
|
|
@floatToInt(u32, height),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-03-23 08:50:20 -07:00
|
|
|
fn process_motion(self: *@This(), time: u32) void {
|
2020-03-22 14:42:55 -07:00
|
|
|
// If the mode is non-passthrough, delegate to those functions.
|
2020-03-23 08:50:20 -07:00
|
|
|
if (self.mode == CursorMode.Move) {
|
|
|
|
self.process_move(time);
|
2020-03-22 14:42:55 -07:00
|
|
|
return;
|
2020-03-23 08:50:20 -07:00
|
|
|
} else if (self.mode == CursorMode.Resize) {
|
|
|
|
self.process_resize(time);
|
2020-03-22 14:42:55 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, find the view under the pointer and send the event along.
|
|
|
|
var sx: f64 = undefined;
|
|
|
|
var sy: f64 = undefined;
|
|
|
|
var opt_surface: ?*c.wlr_surface = null;
|
2020-03-24 12:35:45 -07:00
|
|
|
const view = self.seat.server.desktop_view_at(
|
2020-03-23 08:50:20 -07:00
|
|
|
self.wlr_cursor.x,
|
|
|
|
self.wlr_cursor.y,
|
2020-03-22 14:42:55 -07:00
|
|
|
&opt_surface,
|
|
|
|
&sx,
|
|
|
|
&sy,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (view == null) {
|
|
|
|
// If there's no view under the cursor, set the cursor image to a
|
|
|
|
// default. This is what makes the cursor image appear when you move it
|
|
|
|
// around the screen, not over any views.
|
|
|
|
c.wlr_xcursor_manager_set_cursor_image(
|
2020-03-23 08:50:20 -07:00
|
|
|
self.wlr_xcursor_manager,
|
2020-03-22 14:42:55 -07:00
|
|
|
"left_ptr",
|
2020-03-23 08:50:20 -07:00
|
|
|
self.wlr_cursor,
|
2020-03-22 14:42:55 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-03-24 12:35:45 -07:00
|
|
|
const wlr_seat = self.seat.wlr_seat;
|
2020-03-22 14:42:55 -07:00
|
|
|
if (opt_surface) |surface| {
|
2020-03-23 08:50:20 -07:00
|
|
|
const focus_changed = wlr_seat.pointer_state.focused_surface != surface;
|
2020-03-22 14:42:55 -07:00
|
|
|
// "Enter" the surface if necessary. This lets the client know that the
|
|
|
|
// cursor has entered one of its surfaces.
|
|
|
|
//
|
|
|
|
// Note that this gives the surface "pointer focus", which is distinct
|
|
|
|
// from keyboard focus. You get pointer focus by moving the pointer over
|
|
|
|
// a window.
|
2020-03-23 08:50:20 -07:00
|
|
|
c.wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy);
|
2020-03-22 14:42:55 -07:00
|
|
|
if (!focus_changed) {
|
|
|
|
// The enter event contains coordinates, so we only need to notify
|
|
|
|
// on motion if the focus did not change.
|
2020-03-23 08:50:20 -07:00
|
|
|
c.wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy);
|
2020-03-22 14:42:55 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Clear pointer focus so future button events and such are not sent to
|
|
|
|
// the last client to have the cursor over it.
|
2020-03-23 08:50:20 -07:00
|
|
|
c.wlr_seat_pointer_clear_focus(wlr_seat);
|
2020-03-22 14:42:55 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-24 11:40:47 -07:00
|
|
|
fn handle_motion(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
2020-03-22 14:42:55 -07:00
|
|
|
// This event is forwarded by the cursor when a pointer emits a _relative_
|
|
|
|
// pointer motion event (i.e. a delta)
|
2020-03-24 12:35:45 -07:00
|
|
|
const cursor = @fieldParentPtr(Cursor, "listen_motion", listener.?);
|
|
|
|
const event = @ptrCast(
|
2020-03-22 14:42:55 -07:00
|
|
|
*c.wlr_event_pointer_motion,
|
|
|
|
@alignCast(@alignOf(*c.wlr_event_pointer_motion), data),
|
|
|
|
);
|
|
|
|
// The cursor doesn't move unless we tell it to. The cursor automatically
|
|
|
|
// handles constraining the motion to the output layout, as well as any
|
|
|
|
// special configuration applied for the specific input device which
|
|
|
|
// generated the event. You can pass NULL for the device if you want to move
|
|
|
|
// the cursor around without any input.
|
2020-03-23 08:50:20 -07:00
|
|
|
c.wlr_cursor_move(cursor.wlr_cursor, event.device, event.delta_x, event.delta_y);
|
|
|
|
cursor.process_motion(event.time_msec);
|
2020-03-22 14:42:55 -07:00
|
|
|
}
|
|
|
|
|
2020-03-24 11:40:47 -07:00
|
|
|
fn handle_motion_absolute(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
2020-03-22 14:42:55 -07:00
|
|
|
// 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.
|
2020-03-24 12:35:45 -07:00
|
|
|
const cursor = @fieldParentPtr(Cursor, "listen_motion_absolute", listener.?);
|
|
|
|
const event = @ptrCast(
|
2020-03-22 14:42:55 -07:00
|
|
|
*c.wlr_event_pointer_motion_absolute,
|
|
|
|
@alignCast(@alignOf(*c.wlr_event_pointer_motion_absolute), data),
|
|
|
|
);
|
2020-03-23 08:50:20 -07:00
|
|
|
c.wlr_cursor_warp_absolute(cursor.wlr_cursor, event.device, event.x, event.y);
|
|
|
|
cursor.process_motion(event.time_msec);
|
2020-03-22 14:42:55 -07:00
|
|
|
}
|
|
|
|
|
2020-03-24 11:40:47 -07:00
|
|
|
fn handle_button(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
2020-03-22 14:42:55 -07:00
|
|
|
// This event is forwarded by the cursor when a pointer emits a button
|
|
|
|
// event.
|
2020-03-24 12:35:45 -07:00
|
|
|
const cursor = @fieldParentPtr(Cursor, "listen_button", listener.?);
|
|
|
|
const event = @ptrCast(
|
2020-03-22 14:42:55 -07:00
|
|
|
*c.wlr_event_pointer_button,
|
|
|
|
@alignCast(@alignOf(*c.wlr_event_pointer_button), data),
|
|
|
|
);
|
|
|
|
// Notify the client with pointer focus that a button press has occurred
|
|
|
|
_ = c.wlr_seat_pointer_notify_button(
|
2020-03-23 08:50:20 -07:00
|
|
|
cursor.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.button,
|
|
|
|
event.state,
|
2020-03-22 14:42:55 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
var sx: f64 = undefined;
|
|
|
|
var sy: f64 = undefined;
|
|
|
|
|
|
|
|
var surface: ?*c.wlr_surface = null;
|
2020-03-24 12:35:45 -07:00
|
|
|
const view = cursor.seat.server.desktop_view_at(
|
2020-03-23 08:50:20 -07:00
|
|
|
cursor.wlr_cursor.x,
|
|
|
|
cursor.wlr_cursor.y,
|
2020-03-22 14:42:55 -07:00
|
|
|
&surface,
|
|
|
|
&sx,
|
|
|
|
&sy,
|
|
|
|
);
|
|
|
|
|
2020-03-24 12:03:48 -07:00
|
|
|
if (event.state == c.enum_wlr_button_state.WLR_BUTTON_RELEASED) {
|
2020-03-22 14:42:55 -07:00
|
|
|
// If you released any buttons, we exit interactive move/resize mode.
|
2020-03-23 08:50:20 -07:00
|
|
|
cursor.mode = CursorMode.Passthrough;
|
2020-03-22 14:42:55 -07:00
|
|
|
} else {
|
|
|
|
// Focus that client if the button was _pressed_
|
|
|
|
if (view) |v| {
|
2020-03-23 08:50:20 -07:00
|
|
|
v.focus(surface.?);
|
2020-03-22 14:42:55 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-24 11:40:47 -07:00
|
|
|
fn handle_axis(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
2020-03-22 14:42:55 -07:00
|
|
|
// This event is forwarded by the cursor when a pointer emits an axis event,
|
|
|
|
// for example when you move the scroll wheel.
|
2020-03-24 12:35:45 -07:00
|
|
|
const cursor = @fieldParentPtr(Cursor, "listen_axis", listener.?);
|
|
|
|
const event = @ptrCast(
|
2020-03-22 14:42:55 -07:00
|
|
|
*c.wlr_event_pointer_axis,
|
|
|
|
@alignCast(@alignOf(*c.wlr_event_pointer_axis), data),
|
|
|
|
);
|
2020-03-23 08:50:20 -07:00
|
|
|
|
2020-03-22 14:42:55 -07:00
|
|
|
// Notify the client with pointer focus of the axis event.
|
|
|
|
c.wlr_seat_pointer_notify_axis(
|
2020-03-23 08:50:20 -07:00
|
|
|
cursor.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.orientation,
|
|
|
|
event.delta,
|
|
|
|
event.delta_discrete,
|
|
|
|
event.source,
|
2020-03-22 14:42:55 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-03-24 11:40:47 -07:00
|
|
|
fn handle_frame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
2020-03-22 14:42:55 -07:00
|
|
|
// 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.
|
2020-03-24 12:35:45 -07:00
|
|
|
const cursor = @fieldParentPtr(Cursor, "listen_frame", listener.?);
|
2020-03-22 14:42:55 -07:00
|
|
|
// Notify the client with pointer focus of the frame event.
|
2020-03-23 08:50:20 -07:00
|
|
|
c.wlr_seat_pointer_notify_frame(cursor.seat.wlr_seat);
|
2020-03-22 14:42:55 -07:00
|
|
|
}
|
|
|
|
|
2020-03-24 11:40:47 -07:00
|
|
|
fn handle_request_set_cursor(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
2020-03-22 14:42:55 -07:00
|
|
|
// This event is rasied by the seat when a client provides a cursor image
|
2020-03-24 12:35:45 -07:00
|
|
|
const cursor = @fieldParentPtr(Cursor, "listen_request_set_cursor", listener.?);
|
|
|
|
const event = @ptrCast(
|
2020-03-22 14:42:55 -07:00
|
|
|
*c.wlr_seat_pointer_request_set_cursor_event,
|
|
|
|
@alignCast(@alignOf(*c.wlr_seat_pointer_request_set_cursor_event), data),
|
|
|
|
);
|
2020-03-23 08:50:20 -07:00
|
|
|
const focused_client = cursor.seat.wlr_seat.pointer_state.focused_client;
|
2020-03-22 14:42:55 -07:00
|
|
|
|
|
|
|
// This can be sent by any client, so we check to make sure this one is
|
|
|
|
// actually has pointer focus first.
|
2020-03-24 12:03:48 -07:00
|
|
|
if (focused_client == event.seat_client) {
|
2020-03-22 14:42:55 -07:00
|
|
|
// 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.
|
|
|
|
c.wlr_cursor_set_surface(
|
2020-03-23 08:50:20 -07:00
|
|
|
cursor.wlr_cursor,
|
|
|
|
event.surface,
|
|
|
|
event.hotspot_x,
|
|
|
|
event.hotspot_y,
|
2020-03-22 14:42:55 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|