river: pointer-constraints and relative-pointer

This commit is contained in:
Bonicgamer 2021-02-15 15:07:29 -05:00 committed by Isaac Freund
parent 4beb39920a
commit 26b0acddb7
7 changed files with 207 additions and 7 deletions

View file

@ -3,3 +3,4 @@ The following developers have contributed code to river:
Leon Henrik Plickat
Marten Ringwelski
Rishabh Das
Bonicgamer

View file

@ -42,6 +42,7 @@ pub fn build(b: *zbs.Builder) !void {
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
scanner.addSystemProtocol("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml");
scanner.addSystemProtocol("unstable/xdg-output/xdg-output-unstable-v1.xml");
scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-control-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-options-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");

2
deps/zig-wlroots vendored

@ -1 +1 @@
Subproject commit b38d3d5d2d9a5e4c748b8c01ed0d3861241661a6
Subproject commit 1bdbb7a15a4038ff8bf7c9272c6a4d6eeb64ffa2

View file

@ -60,6 +60,8 @@ wlr_cursor: *wlr.Cursor,
pointer_gestures: *wlr.PointerGesturesV1,
xcursor_manager: *wlr.XcursorManager,
constraint: ?*wlr.PointerConstraintV1 = null,
/// Number of distinct buttons currently pressed
pressed_count: u32 = 0,
@ -382,7 +384,9 @@ fn handleMotionAbsolute(
var ly: f64 = undefined;
self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly);
self.processMotion(event.device, event.time_msec, lx - self.wlr_cursor.x, ly - self.wlr_cursor.y);
const dx = lx - self.wlr_cursor.x;
const dy = ly - self.wlr_cursor.y;
self.processMotion(event.device, event.time_msec, dx, dy, dx, dy);
}
/// This event is forwarded by the cursor when a pointer emits a _relative_
@ -395,7 +399,7 @@ fn handleMotion(
self.seat.handleActivity();
self.processMotion(event.device, event.time_msec, event.delta_x, event.delta_y);
self.processMotion(event.device, event.time_msec, event.delta_x, event.delta_y, event.unaccel_dx, event.unaccel_dy);
}
fn handleRequestSetCursor(
@ -561,14 +565,44 @@ fn leaveMode(self: *Self, event: *wlr.Pointer.event.Button) void {
self.passthrough(event.time_msec);
}
fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, delta_y: f64) void {
fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, delta_y: f64, unaccel_dx: f64, unaccel_dy: f64) void {
self.seat.input_manager.relative_pointer_manager.sendRelativeMotion(
self.seat.wlr_seat,
@as(u64, time) * 1000,
delta_x,
delta_y,
unaccel_dx,
unaccel_dy,
);
var dx: f64 = delta_x;
var dy: f64 = delta_y;
if (self.constraint) |constraint| {
if (self.mode == .passthrough or self.mode == .down) {
if (constraint.type == .locked) return;
var sx: f64 = undefined;
var sy: f64 = undefined;
const surface = self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy);
if (surface != constraint.surface) return;
var sx_con: f64 = undefined;
var sy_con: f64 = undefined;
if (!wlr.region.confine(&constraint.region, sx, sy, sx + dx, sy + dy, &sx_con, &sy_con)) {
return;
}
dx = sx_con - sx;
dy = sy_con - sy;
}
}
switch (self.mode) {
.passthrough => {
self.wlr_cursor.move(device, delta_x, delta_y);
self.wlr_cursor.move(device, dx, dy);
self.passthrough(time);
},
.down => |view| {
self.wlr_cursor.move(device, delta_x, delta_y);
self.wlr_cursor.move(device, dx, dy);
// This takes surface-local coordinates
const output_box = view.output.root.output_layout.getBox(view.output.wlr_output).?;
self.seat.wlr_seat.pointerNotifyMotion(

View file

@ -27,6 +27,7 @@ const util = @import("util.zig");
const Seat = @import("Seat.zig");
const Server = @import("Server.zig");
const View = @import("View.zig");
const PointerConstraint = @import("PointerConstraint.zig");
const default_seat_name = "default";
@ -37,6 +38,8 @@ new_input: wl.Listener(*wlr.InputDevice) = wl.Listener(*wlr.InputDevice).init(ha
idle: *wlr.Idle,
input_inhibit_manager: *wlr.InputInhibitManager,
pointer_constraints: *wlr.PointerConstraintsV1,
relative_pointer_manager: *wlr.RelativePointerManagerV1,
virtual_pointer_manager: *wlr.VirtualPointerManagerV1,
virtual_keyboard_manager: *wlr.VirtualKeyboardManagerV1,
@ -49,6 +52,8 @@ inhibit_activate: wl.Listener(*wlr.InputInhibitManager) =
wl.Listener(*wlr.InputInhibitManager).init(handleInhibitActivate),
inhibit_deactivate: wl.Listener(*wlr.InputInhibitManager) =
wl.Listener(*wlr.InputInhibitManager).init(handleInhibitDeactivate),
new_pointer_constraint: wl.Listener(*wlr.PointerConstraintV1) =
wl.Listener(*wlr.PointerConstraintV1).init(handleNewPointerConstraint),
new_virtual_pointer: wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer) =
wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer).init(handleNewVirtualPointer),
new_virtual_keyboard: wl.Listener(*wlr.VirtualKeyboardV1) =
@ -64,6 +69,8 @@ pub fn init(self: *Self, server: *Server) !void {
// These are automatically freed when the display is destroyed
.idle = try wlr.Idle.create(server.wl_server),
.input_inhibit_manager = try wlr.InputInhibitManager.create(server.wl_server),
.pointer_constraints = try wlr.PointerConstraintsV1.create(server.wl_server),
.relative_pointer_manager = try wlr.RelativePointerManagerV1.create(server.wl_server),
.virtual_pointer_manager = try wlr.VirtualPointerManagerV1.create(server.wl_server),
.virtual_keyboard_manager = try wlr.VirtualKeyboardManagerV1.create(server.wl_server),
};
@ -76,6 +83,7 @@ pub fn init(self: *Self, server: *Server) !void {
server.backend.events.new_input.add(&self.new_input);
self.input_inhibit_manager.events.activate.add(&self.inhibit_activate);
self.input_inhibit_manager.events.deactivate.add(&self.inhibit_deactivate);
self.pointer_constraints.events.new_constraint.add(&self.new_pointer_constraint);
self.virtual_pointer_manager.events.new_virtual_pointer.add(&self.new_virtual_pointer);
self.virtual_keyboard_manager.events.new_virtual_keyboard.add(&self.new_virtual_keyboard);
}
@ -171,6 +179,15 @@ fn handleNewInput(listener: *wl.Listener(*wlr.InputDevice), device: *wlr.InputDe
self.defaultSeat().addDevice(device);
}
fn handleNewPointerConstraint(listener: *wl.Listener(*wlr.PointerConstraintV1), constraint: *wlr.PointerConstraintV1) void {
const pointer_constraint = util.gpa.create(PointerConstraint) catch {
log.crit("out of memory", .{});
return;
};
pointer_constraint.init(constraint);
}
fn handleNewVirtualPointer(
listener: *wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer),
event: *wlr.VirtualPointerManagerV1.event.NewPointer,

132
river/PointerConstraint.zig Normal file
View file

@ -0,0 +1,132 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2021 The River Developers
//
// 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 build_options = @import("build_options");
const std = @import("std");
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const pixman = @import("pixman");
const log = @import("log.zig");
const util = @import("util.zig");
const Cursor = @import("Cursor.zig");
const Seat = @import("Seat.zig");
const View = @import("View.zig");
constraint: *wlr.PointerConstraintV1,
cursor: *Cursor,
// zig fmt: off
destroy: wl.Listener(*wlr.PointerConstraintV1) =
wl.Listener(*wlr.PointerConstraintV1).init(handleDestroy),
set_region: wl.Listener(void) =
wl.Listener(void).init(handleSetRegion),
// zig fmt: on
pub fn init(self: *Self, constraint: *wlr.PointerConstraintV1) void {
const seat = @intToPtr(*Seat, constraint.seat.data);
self.* = .{
.constraint = constraint,
.cursor = &seat.cursor,
};
self.constraint.data = @ptrToInt(self);
self.constraint.events.destroy.add(&self.destroy);
self.constraint.events.set_region.add(&self.set_region);
if (seat.focused == .view and seat.focused.view.surface == self.constraint.surface) {
self.setAsActive();
}
}
pub fn setAsActive(self: *Self) void {
if (self.cursor.constraint == self.constraint) return;
if (self.cursor.constraint) |constraint| {
constraint.sendDeactivated();
}
self.cursor.constraint = self.constraint;
if (self.constraint.current.region.notEmpty() != 0) {
_ = self.constraint.region.intersect(&self.constraint.surface.input_region, &self.constraint.current.region);
} else {
_ = self.constraint.region.copy(&self.constraint.surface.input_region);
}
self.constrainToRegion();
self.constraint.sendActivated();
}
fn constrainToRegion(self: *Self) void {
if (self.cursor.constraint != self.constraint) return;
if (View.fromWlrSurface(self.constraint.surface)) |view| {
const cx = @floatToInt(c_int, self.cursor.wlr_cursor.x) - @intCast(c_int, view.current.box.x);
const cy = @floatToInt(c_int, self.cursor.wlr_cursor.y) - @intCast(c_int, view.current.box.y);
var box: pixman.Box32 = undefined;
if (self.constraint.region.containsPoint(cx, cy, &box) == 0) {
var nRects: c_int = undefined;
const rects = self.constraint.region.rectangles(&nRects);
if (nRects > 0) {
const new_cx = @intToFloat(f64, view.current.box.x + rects.x1 + @divFloor(rects.x2, 2));
const new_cy = @intToFloat(f64, view.current.box.y + rects.y1 + @divFloor(rects.y2, 2));
self.cursor.wlr_cursor.warpClosest(null, new_cx, new_cy);
}
}
}
}
fn handleDestroy(listener: *wl.Listener(*wlr.PointerConstraintV1), constraint: *wlr.PointerConstraintV1) void {
const self = @fieldParentPtr(Self, "destroy", listener);
self.destroy.link.remove();
self.set_region.link.remove();
if (self.cursor.constraint == self.constraint) {
warpToHint(self.cursor);
self.cursor.constraint = null;
}
util.gpa.destroy(self);
}
fn handleSetRegion(listener: *wl.Listener(void)) void {
const self = @fieldParentPtr(Self, "set_region", listener);
self.constrainToRegion();
}
pub fn warpToHint(cursor: *Cursor) void {
if (cursor.constraint) |constraint| {
if (constraint.current.committed.cursor_hint) {
if (View.fromWlrSurface(constraint.surface)) |view| {
const cx = constraint.current.cursor_hint.x + @intToFloat(f64, view.current.box.x);
const cy = constraint.current.cursor_hint.y + @intToFloat(f64, view.current.box.y);
_ = cursor.wlr_cursor.warp(null, cx, cy);
}
}
}
}

View file

@ -37,6 +37,7 @@ const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
const log = std.log.scoped(.seat);
const PointerConstraint = @import("PointerConstraint.zig");
const FocusTarget = union(enum) {
view: *View,
@ -223,7 +224,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
}
self.focused = new_focus;
// Send surface enter/leave events
// Send keyboard enter/leave events and handle pointer constraints
if (target_surface) |wlr_surface| {
if (self.wlr_seat.getKeyboard()) |keyboard| {
self.wlr_seat.keyboardNotifyEnter(
@ -235,8 +236,22 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
} else {
self.wlr_seat.keyboardNotifyEnter(wlr_surface, null, 0, null);
}
if (self.input_manager.pointer_constraints.constraintForSurface(wlr_surface, self.wlr_seat)) |constraint| {
@intToPtr(*PointerConstraint, constraint.data).setAsActive();
} else if (self.cursor.constraint) |constraint| {
PointerConstraint.warpToHint(&self.cursor);
constraint.sendDeactivated();
self.cursor.constraint = null;
}
} else {
self.wlr_seat.keyboardClearFocus();
if (self.cursor.constraint) |constraint| {
PointerConstraint.warpToHint(&self.cursor);
constraint.sendDeactivated();
self.cursor.constraint = null;
}
}
}