initial commit
This commit is contained in:
+461
@@ -0,0 +1,461 @@
|
||||
//! A seat: keyboard focus, key and pointer bindings, and interactive
|
||||
//! move/resize operations.
|
||||
//!
|
||||
//! river delivers binding and pointer events immediately but requires the
|
||||
//! matching protocol requests to be made inside a manage sequence. Every
|
||||
//! handler here therefore records intent in a field and calls `needsManage`;
|
||||
//! `applyManage` is the only place that talks back to the compositor.
|
||||
|
||||
const Seat = @This();
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.client.wl;
|
||||
const river = wayland.client.river;
|
||||
|
||||
const config = @import("config");
|
||||
const action = @import("action");
|
||||
|
||||
const Wm = @import("Wm.zig");
|
||||
const Window = @import("Window.zig");
|
||||
const Output = @import("Output.zig");
|
||||
const layout = @import("layout.zig");
|
||||
const Box = layout.Box;
|
||||
|
||||
pub const KeyBinding = struct {
|
||||
seat: *Seat,
|
||||
/// Index into config.keys.
|
||||
index: usize,
|
||||
object: *river.XkbBindingV1,
|
||||
};
|
||||
|
||||
pub const PointerBinding = struct {
|
||||
seat: *Seat,
|
||||
/// Index into config.buttons.
|
||||
index: usize,
|
||||
object: *river.PointerBindingV1,
|
||||
};
|
||||
|
||||
pub const Op = struct {
|
||||
kind: enum { move, resize },
|
||||
window: *Window,
|
||||
/// The window's cell when the operation started.
|
||||
start: Box,
|
||||
/// For resize: which corner is being dragged.
|
||||
edges: river.WindowV1.Edges = .{},
|
||||
};
|
||||
|
||||
wm: *Wm,
|
||||
seat: *river.SeatV1,
|
||||
layer_seat: ?*river.LayerShellSeatV1 = null,
|
||||
|
||||
wl_seat_name: u32 = 0,
|
||||
wl_seat: ?*wl.Seat = null,
|
||||
pointer: ?*wl.Pointer = null,
|
||||
|
||||
keys: std.ArrayList(*KeyBinding) = .empty,
|
||||
buttons: std.ArrayList(*PointerBinding) = .empty,
|
||||
/// Set once the bindings have been enabled in a manage sequence.
|
||||
bindings_enabled: bool = false,
|
||||
|
||||
focused: ?*Window = null,
|
||||
/// Focus we want river to apply in the next manage sequence.
|
||||
pending_focus: ?*Window = null,
|
||||
/// True when focus should be cleared rather than moved.
|
||||
pending_clear_focus: bool = false,
|
||||
|
||||
/// A layer surface (a bar, a launcher) holds focus; our focus requests are
|
||||
/// either ignored or would steal it.
|
||||
layer_focus: enum { none, exclusive, non_exclusive } = .none,
|
||||
|
||||
/// Pointer position in the compositor's logical coordinate space.
|
||||
pointer_x: i32 = 0,
|
||||
pointer_y: i32 = 0,
|
||||
|
||||
/// The window the pointer is currently inside.
|
||||
hovered: ?*Window = null,
|
||||
|
||||
op: ?Op = null,
|
||||
/// An operation to start in the next manage sequence.
|
||||
pending_op: ?Op = null,
|
||||
/// The running operation should be ended in the next manage sequence.
|
||||
pending_op_end: bool = false,
|
||||
|
||||
/// One of our own surfaces has pointer focus (the tab bar).
|
||||
pointer_surface: ?*wl.Surface = null,
|
||||
pointer_local_x: f64 = 0,
|
||||
pointer_local_y: f64 = 0,
|
||||
|
||||
removed: bool = false,
|
||||
|
||||
pub fn create(wm: *Wm, seat: *river.SeatV1) !*Seat {
|
||||
const self = try wm.gpa.create(Seat);
|
||||
self.* = .{ .wm = wm, .seat = seat };
|
||||
seat.setListener(*Seat, onEvent, self);
|
||||
|
||||
if (wm.layer_shell) |ls| {
|
||||
self.layer_seat = ls.getSeat(seat) catch null;
|
||||
if (self.layer_seat) |lseat| lseat.setListener(*Seat, onLayerEvent, self);
|
||||
}
|
||||
|
||||
try self.createBindings();
|
||||
|
||||
if (config.cursor_theme) |theme| {
|
||||
var buf: [256]u8 = undefined;
|
||||
const z = std.fmt.bufPrintZ(&buf, "{s}", .{theme}) catch null;
|
||||
if (z) |name| seat.setXcursorTheme(name, config.cursor_size);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(self: *Seat) void {
|
||||
const gpa = self.wm.gpa;
|
||||
for (self.keys.items) |binding| {
|
||||
binding.object.destroy();
|
||||
gpa.destroy(binding);
|
||||
}
|
||||
for (self.buttons.items) |binding| {
|
||||
binding.object.destroy();
|
||||
gpa.destroy(binding);
|
||||
}
|
||||
self.keys.deinit(gpa);
|
||||
self.buttons.deinit(gpa);
|
||||
if (self.pointer) |p| p.release();
|
||||
if (self.wl_seat) |s| s.release();
|
||||
if (self.layer_seat) |l| l.destroy();
|
||||
self.seat.destroy();
|
||||
gpa.destroy(self);
|
||||
}
|
||||
|
||||
fn createBindings(self: *Seat) !void {
|
||||
const gpa = self.wm.gpa;
|
||||
|
||||
if (self.wm.xkb_bindings) |xkb_bindings| {
|
||||
try self.keys.ensureTotalCapacity(gpa, config.keys.len);
|
||||
for (config.keys, 0..) |key, i| {
|
||||
const mods: river.SeatV1.Modifiers = @bitCast(key.mods);
|
||||
const object = xkb_bindings.getXkbBinding(self.seat, @intFromEnum(key.keysym), mods) catch |err| {
|
||||
std.log.err("failed to bind key {t}: {s}", .{ key.keysym, @errorName(err) });
|
||||
continue;
|
||||
};
|
||||
const binding = try gpa.create(KeyBinding);
|
||||
binding.* = .{ .seat = self, .index = i, .object = object };
|
||||
object.setListener(*KeyBinding, onKeyEvent, binding);
|
||||
self.keys.appendAssumeCapacity(binding);
|
||||
}
|
||||
}
|
||||
|
||||
std.log.info("registered {d}/{d} key bindings", .{ self.keys.items.len, config.keys.len });
|
||||
|
||||
try self.buttons.ensureTotalCapacity(gpa, config.buttons.len);
|
||||
for (config.buttons, 0..) |button, i| {
|
||||
const mods: river.SeatV1.Modifiers = @bitCast(button.mods);
|
||||
const object = self.seat.getPointerBinding(button.button, mods) catch |err| {
|
||||
std.log.err("failed to bind button {d}: {s}", .{ button.button, @errorName(err) });
|
||||
continue;
|
||||
};
|
||||
const binding = try gpa.create(PointerBinding);
|
||||
binding.* = .{ .seat = self, .index = i, .object = object };
|
||||
object.setListener(*PointerBinding, onButtonEvent, binding);
|
||||
self.buttons.appendAssumeCapacity(binding);
|
||||
}
|
||||
|
||||
std.log.info("registered {d}/{d} pointer bindings", .{ self.buttons.items.len, config.buttons.len });
|
||||
}
|
||||
|
||||
/// The output this seat is working on: the one holding the focused window,
|
||||
/// else the one under the pointer.
|
||||
pub fn currentOutput(self: *Seat) ?*Output {
|
||||
if (self.focused) |win| {
|
||||
if (win.output) |out| return out;
|
||||
}
|
||||
return self.wm.outputAt(self.pointer_x, self.pointer_y) orelse self.wm.firstOutput();
|
||||
}
|
||||
|
||||
pub fn focus(self: *Seat, window: ?*Window) void {
|
||||
if (window) |win| {
|
||||
self.pending_focus = win;
|
||||
self.pending_clear_focus = false;
|
||||
} else {
|
||||
self.pending_focus = null;
|
||||
self.pending_clear_focus = true;
|
||||
}
|
||||
self.wm.needsManage();
|
||||
}
|
||||
|
||||
pub fn startMove(self: *Seat, window: *Window) void {
|
||||
if (window.fullscreen) return;
|
||||
self.pending_op = .{ .kind = .move, .window = window, .start = window.cell };
|
||||
self.wm.needsManage();
|
||||
}
|
||||
|
||||
pub fn startResize(self: *Seat, window: *Window, edges: river.WindowV1.Edges) void {
|
||||
if (window.fullscreen) return;
|
||||
self.pending_op = .{
|
||||
.kind = .resize,
|
||||
.window = window,
|
||||
.start = window.cell,
|
||||
.edges = edges,
|
||||
};
|
||||
self.wm.needsManage();
|
||||
}
|
||||
|
||||
/// Issue the requests recorded by the event handlers. Manage sequence only.
|
||||
pub fn applyManage(self: *Seat) void {
|
||||
if (!self.bindings_enabled) {
|
||||
for (self.keys.items) |binding| binding.object.enable();
|
||||
for (self.buttons.items) |binding| binding.object.enable();
|
||||
self.bindings_enabled = true;
|
||||
}
|
||||
|
||||
if (self.pending_op) |op| {
|
||||
// Dragging a tiled window pops it out into floating, as in dwm.
|
||||
if (!op.window.floating) {
|
||||
op.window.floating = true;
|
||||
op.window.floating_forced = true;
|
||||
op.window.float_box = op.window.cell;
|
||||
}
|
||||
self.seat.opStartPointer();
|
||||
self.op = op;
|
||||
self.op.?.start = op.window.cell;
|
||||
self.pending_op = null;
|
||||
op.window.window.informResizeStart();
|
||||
}
|
||||
|
||||
if (self.pending_op_end) {
|
||||
if (self.op) |op| {
|
||||
self.seat.opEnd();
|
||||
if (!op.window.closed) op.window.window.informResizeEnd();
|
||||
}
|
||||
self.op = null;
|
||||
self.pending_op_end = false;
|
||||
}
|
||||
|
||||
// A layer surface with exclusive focus outranks us entirely.
|
||||
if (self.layer_focus == .exclusive) {
|
||||
self.pending_focus = null;
|
||||
self.pending_clear_focus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.pending_focus) |win| {
|
||||
if (!win.closed and win.mapped) {
|
||||
self.seat.focusWindow(win.window);
|
||||
self.focused = win;
|
||||
self.wm.focus_serial += 1;
|
||||
win.focus_serial = self.wm.focus_serial;
|
||||
self.warpTo(win);
|
||||
self.wm.ipcDirty();
|
||||
}
|
||||
self.pending_focus = null;
|
||||
} else if (self.pending_clear_focus) {
|
||||
self.seat.clearFocus();
|
||||
self.focused = null;
|
||||
self.pending_clear_focus = false;
|
||||
self.wm.ipcDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// Pull the pointer to the middle of a newly focused window.
|
||||
///
|
||||
/// Skipped when the pointer is already inside it, so keyboard focus following
|
||||
/// the mouse does not yank the cursor out from under the user. Manage sequence
|
||||
/// only.
|
||||
fn warpTo(self: *Seat, win: *Window) void {
|
||||
if (!config.warp_cursor) return;
|
||||
if (self.op != null) return;
|
||||
if (win.cell.contains(self.pointer_x, self.pointer_y)) return;
|
||||
|
||||
self.seat.pointerWarp(
|
||||
win.cell.x + @divTrunc(win.cell.width, 2),
|
||||
win.cell.y + @divTrunc(win.cell.height, 2),
|
||||
);
|
||||
}
|
||||
|
||||
fn onEvent(_: *river.SeatV1, event: river.SeatV1.Event, self: *Seat) void {
|
||||
switch (event) {
|
||||
.removed => {
|
||||
self.removed = true;
|
||||
self.wm.needsManage();
|
||||
},
|
||||
|
||||
.wl_seat => |ev| {
|
||||
self.wl_seat_name = ev.name;
|
||||
self.wm.attachWlSeat(self);
|
||||
},
|
||||
|
||||
.pointer_position => |ev| {
|
||||
self.pointer_x = ev.x;
|
||||
self.pointer_y = ev.y;
|
||||
},
|
||||
|
||||
.pointer_enter => |ev| {
|
||||
const win = Wm.windowFromProxy(ev.window) orelse return;
|
||||
self.hovered = win;
|
||||
if (config.focus_follows_mouse and self.op == null) {
|
||||
if (win.mapped and win.visible) self.focus(win);
|
||||
}
|
||||
},
|
||||
|
||||
.pointer_leave => {
|
||||
self.hovered = null;
|
||||
},
|
||||
|
||||
.window_interaction => |ev| {
|
||||
const win = Wm.windowFromProxy(ev.window) orelse return;
|
||||
// Clicking a window focuses it and, if floating, raises it.
|
||||
self.focus(win);
|
||||
if (win.output) |out| self.wm.focusOutput(out);
|
||||
},
|
||||
|
||||
.shell_surface_interaction => {
|
||||
// Our own tab bar; handled through wl_pointer where we know the
|
||||
// coordinates.
|
||||
},
|
||||
|
||||
.op_delta => |ev| {
|
||||
const op = self.op orelse return;
|
||||
const win = op.window;
|
||||
if (win.closed) return;
|
||||
|
||||
switch (op.kind) {
|
||||
.move => {
|
||||
win.cell.x = op.start.x + ev.dx;
|
||||
win.cell.y = op.start.y + ev.dy;
|
||||
win.float_box = win.cell;
|
||||
},
|
||||
.resize => {
|
||||
var box = op.start;
|
||||
if (op.edges.left) {
|
||||
box.x = op.start.x + ev.dx;
|
||||
box.width = op.start.width - ev.dx;
|
||||
} else {
|
||||
box.width = op.start.width + ev.dx;
|
||||
}
|
||||
if (op.edges.top) {
|
||||
box.y = op.start.y + ev.dy;
|
||||
box.height = op.start.height - ev.dy;
|
||||
} else {
|
||||
box.height = op.start.height + ev.dy;
|
||||
}
|
||||
const min = 2 * config.border_width + 1;
|
||||
box.width = @max(min, box.width);
|
||||
box.height = @max(min, box.height);
|
||||
win.cell = box;
|
||||
win.float_box = box;
|
||||
},
|
||||
}
|
||||
self.wm.needsManage();
|
||||
},
|
||||
|
||||
.op_release => {
|
||||
self.pending_op_end = true;
|
||||
self.wm.needsManage();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn onLayerEvent(_: *river.LayerShellSeatV1, event: river.LayerShellSeatV1.Event, self: *Seat) void {
|
||||
switch (event) {
|
||||
.focus_exclusive => self.layer_focus = .exclusive,
|
||||
.focus_non_exclusive => self.layer_focus = .non_exclusive,
|
||||
.focus_none => {
|
||||
self.layer_focus = .none;
|
||||
// Hand focus back to whatever the user was using.
|
||||
if (self.focused) |win| {
|
||||
if (!win.closed and win.visible) self.focus(win);
|
||||
}
|
||||
},
|
||||
}
|
||||
self.wm.needsManage();
|
||||
}
|
||||
|
||||
fn onKeyEvent(_: *river.XkbBindingV1, event: river.XkbBindingV1.Event, binding: *KeyBinding) void {
|
||||
const self = binding.seat;
|
||||
const key = config.keys[binding.index];
|
||||
switch (event) {
|
||||
.pressed => {
|
||||
self.wm.perform(self, key.action);
|
||||
if (key.shouldRepeat()) self.wm.startRepeat(self, binding.index);
|
||||
},
|
||||
.released, .stop_repeat => {
|
||||
self.wm.stopRepeat(binding.index);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn onButtonEvent(_: *river.PointerBindingV1, event: river.PointerBindingV1.Event, binding: *PointerBinding) void {
|
||||
const self = binding.seat;
|
||||
const button = config.buttons[binding.index];
|
||||
switch (event) {
|
||||
.pressed => {
|
||||
const win = self.hovered orelse self.wm.windowAt(self.pointer_x, self.pointer_y) orelse return;
|
||||
self.focus(win);
|
||||
switch (button.action) {
|
||||
.move => self.startMove(win),
|
||||
.resize => {
|
||||
// Resize from whichever corner the pointer is nearest, so
|
||||
// the drag pulls the expected edge.
|
||||
const mid_x = win.cell.x + @divTrunc(win.cell.width, 2);
|
||||
const mid_y = win.cell.y + @divTrunc(win.cell.height, 2);
|
||||
self.startResize(win, .{
|
||||
.left = self.pointer_x < mid_x,
|
||||
.right = self.pointer_x >= mid_x,
|
||||
.top = self.pointer_y < mid_y,
|
||||
.bottom = self.pointer_y >= mid_y,
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
.released => {
|
||||
self.pending_op_end = true;
|
||||
self.wm.needsManage();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ─── wl_pointer, used only to click the tab bar ──────────────────────────────
|
||||
|
||||
pub fn onWlSeatEvent(_: *wl.Seat, event: wl.Seat.Event, self: *Seat) void {
|
||||
switch (event) {
|
||||
.capabilities => |ev| {
|
||||
if (ev.capabilities.pointer and self.pointer == null) {
|
||||
self.pointer = self.wl_seat.?.getPointer() catch null;
|
||||
if (self.pointer) |p| p.setListener(*Seat, onPointerEvent, self);
|
||||
}
|
||||
},
|
||||
.name => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn onPointerEvent(_: *wl.Pointer, event: wl.Pointer.Event, self: *Seat) void {
|
||||
switch (event) {
|
||||
.enter => |ev| {
|
||||
self.pointer_surface = ev.surface;
|
||||
self.pointer_local_x = ev.surface_x.toDouble();
|
||||
self.pointer_local_y = ev.surface_y.toDouble();
|
||||
},
|
||||
.leave => {
|
||||
self.pointer_surface = null;
|
||||
},
|
||||
.motion => |ev| {
|
||||
self.pointer_local_x = ev.surface_x.toDouble();
|
||||
self.pointer_local_y = ev.surface_y.toDouble();
|
||||
},
|
||||
.button => |ev| {
|
||||
if (ev.state != .pressed) return;
|
||||
const surface = self.pointer_surface orelse return;
|
||||
const out = self.wm.outputForTabBarSurface(surface) orelse return;
|
||||
|
||||
const gx = out.tabbar.box.x + @as(i32, @intFromFloat(self.pointer_local_x));
|
||||
const gy = out.tabbar.box.y + @as(i32, @intFromFloat(self.pointer_local_y));
|
||||
if (out.tabbar.windowAt(gx, gy)) |win| {
|
||||
self.wm.focusOutput(out);
|
||||
self.focus(win);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user