Cursor: remove surfaceAt()

We now use the wlr_scene API to find out what is at the cursor location.
This commit is contained in:
Isaac Freund 2023-01-29 12:03:41 +01:00
parent 4f0ce8fceb
commit 683ed0f04e
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
6 changed files with 152 additions and 307 deletions

View File

@ -34,6 +34,7 @@ const Config = @import("Config.zig");
const LayerSurface = @import("LayerSurface.zig");
const LockSurface = @import("LockSurface.zig");
const Output = @import("Output.zig");
const Root = @import("Root.zig");
const Seat = @import("Seat.zig");
const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
@ -320,8 +321,8 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
return;
}
if (self.surfaceAt()) |result| {
if (result.parent == .view and self.handlePointerMapping(event, result.parent.view)) {
if (server.root.at(self.wlr_cursor.x, self.wlr_cursor.y)) |result| {
if (result.node == .view and self.handlePointerMapping(event, result.node.view)) {
// If a mapping is triggered don't send events to clients.
return;
}
@ -330,14 +331,16 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
_ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
self.mode = .{
.down = .{
.lx = self.wlr_cursor.x,
.ly = self.wlr_cursor.y,
.sx = result.sx,
.sy = result.sy,
},
};
if (result.surface != null) {
self.mode = .{
.down = .{
.lx = self.wlr_cursor.x,
.ly = self.wlr_cursor.y,
.sx = result.sx,
.sy = result.sy,
},
};
}
} else {
self.updateOutputFocus(self.wlr_cursor.x, self.wlr_cursor.y);
}
@ -345,8 +348,8 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
server.root.startTransaction();
}
fn updateKeyboardFocus(self: Self, result: SurfaceAtResult) void {
switch (result.parent) {
fn updateKeyboardFocus(self: Self, result: Root.AtResult) void {
switch (result.node) {
.view => |view| {
self.seat.focus(view);
},
@ -485,16 +488,18 @@ fn handleTouchDown(
log.err("out of memory", .{});
};
if (surfaceAtCoords(lx, ly)) |result| {
if (server.root.at(lx, ly)) |result| {
self.updateKeyboardFocus(result);
_ = self.seat.wlr_seat.touchNotifyDown(
result.surface,
event.time_msec,
event.touch_id,
result.sx,
result.sy,
);
if (result.surface) |surface| {
_ = self.seat.wlr_seat.touchNotifyDown(
surface,
event.time_msec,
event.touch_id,
result.sx,
result.sy,
);
}
} else {
self.updateOutputFocus(lx, ly);
}
@ -518,7 +523,7 @@ fn handleTouchMotion(
log.err("out of memory", .{});
};
if (surfaceAtCoords(lx, ly)) |result| {
if (server.root.at(lx, ly)) |result| {
self.seat.wlr_seat.touchNotifyMotion(event.time_msec, event.touch_id, result.sx, result.sy);
}
}
@ -649,245 +654,6 @@ fn handleHideCursorTimeout(self: *Self) c_int {
return 0;
}
const SurfaceAtResult = struct {
surface: *wlr.Surface,
sx: f64,
sy: f64,
parent: union(enum) {
view: *View,
layer_surface: *LayerSurface,
lock_surface: *LockSurface,
xwayland_override_redirect: if (build_options.xwayland) *XwaylandOverrideRedirect else noreturn,
},
};
/// Find the surface under the cursor if any, and return information about that
/// surface and the cursor's position in surface local coords.
pub fn surfaceAt(self: Self) ?SurfaceAtResult {
return surfaceAtCoords(self.wlr_cursor.x, self.wlr_cursor.y);
}
/// Find the surface at the given layout coords if any, and return information about that
/// surface and the surface local coords.
/// This function must be kept in sync with the rendering order in render.zig.
fn surfaceAtCoords(lx: f64, ly: f64) ?SurfaceAtResult {
const wlr_output = server.root.output_layout.outputAt(lx, ly) orelse return null;
const output = @intToPtr(*Output, wlr_output.data);
// Get output-local coords from the layout coords
var ox = lx;
var oy = ly;
server.root.output_layout.outputCoords(wlr_output, &ox, &oy);
if (server.lock_manager.state != .unlocked) {
if (output.lock_surface) |lock_surface| {
var sx: f64 = undefined;
var sy: f64 = undefined;
if (lock_surface.wlr_lock_surface.surface.surfaceAt(ox, oy, &sx, &sy)) |found| {
return SurfaceAtResult{
.surface = found,
.sx = sx,
.sy = sy,
.parent = .{ .lock_surface = lock_surface },
};
}
}
return null;
}
// Find the first visible fullscreen view in the stack if there is one
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
const fullscreen_view = while (it.next()) |view| {
if (view.current.fullscreen) break view;
} else null;
// Check surfaces in the reverse order they are rendered in:
//
// fullscreen:
// 1. overlay layer toplevels and popups
// 2. xwayland override redirect windows
// 3. fullscreen view toplevels and popups
//
// non-fullscreen:
// 1. overlay layer toplevels and popups
// 2. top, bottom, background layer popups
// 3. top layer toplevels
// 4. xwayland override redirect windows
// 5. view toplevels and popups
// 6. bottom, background layer toplevels
if (layerSurfaceAt(output.getLayer(.overlay).*, ox, oy)) |s| return s;
if (fullscreen_view) |view| {
if (build_options.xwayland) if (xwaylandOverrideRedirectSurfaceAt(lx, ly)) |s| return s;
var sx: f64 = undefined;
var sy: f64 = undefined;
if (view.surfaceAt(ox, oy, &sx, &sy)) |found| {
return SurfaceAtResult{
.surface = found,
.sx = sx,
.sy = sy,
.parent = .{ .view = view },
};
}
} else {
for ([_]zwlr.LayerShellV1.Layer{ .top, .bottom, .background }) |layer| {
if (layerPopupSurfaceAt(output.getLayer(layer).*, ox, oy)) |s| return s;
}
if (layerSurfaceAt(output.getLayer(.top).*, ox, oy)) |s| return s;
if (build_options.xwayland) if (xwaylandOverrideRedirectSurfaceAt(lx, ly)) |s| return s;
if (viewSurfaceAt(output, ox, oy)) |s| return s;
for ([_]zwlr.LayerShellV1.Layer{ .bottom, .background }) |layer| {
if (layerSurfaceAt(output.getLayer(layer).*, ox, oy)) |s| return s;
}
}
return null;
}
/// Find the topmost popup surface on the given layer at ox,oy.
fn layerPopupSurfaceAt(layer: std.TailQueue(LayerSurface), ox: f64, oy: f64) ?SurfaceAtResult {
var it = layer.first;
while (it) |node| : (it = node.next) {
const layer_surface = &node.data;
var sx: f64 = undefined;
var sy: f64 = undefined;
if (layer_surface.wlr_layer_surface.popupSurfaceAt(
ox - @intToFloat(f64, layer_surface.box.x),
oy - @intToFloat(f64, layer_surface.box.y),
&sx,
&sy,
)) |found| {
return SurfaceAtResult{
.surface = found,
.sx = sx,
.sy = sy,
.parent = .{ .layer_surface = layer_surface },
};
}
}
return null;
}
/// Find the topmost surface (or popup surface) on the given layer at ox,oy.
fn layerSurfaceAt(layer: std.TailQueue(LayerSurface), ox: f64, oy: f64) ?SurfaceAtResult {
var it = layer.first;
while (it) |node| : (it = node.next) {
const layer_surface = &node.data;
var sx: f64 = undefined;
var sy: f64 = undefined;
if (layer_surface.wlr_layer_surface.surfaceAt(
ox - @intToFloat(f64, layer_surface.box.x),
oy - @intToFloat(f64, layer_surface.box.y),
&sx,
&sy,
)) |found| {
return SurfaceAtResult{
.surface = found,
.sx = sx,
.sy = sy,
.parent = .{ .layer_surface = layer_surface },
};
}
}
return null;
}
/// Find the topmost visible view surface (incl. popups) at ox,oy.
fn viewSurfaceAt(output: *const Output, ox: f64, oy: f64) ?SurfaceAtResult {
var sx: f64 = undefined;
var sy: f64 = undefined;
// focused, floating views
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
while (it.next()) |view| {
if (view.current.focus == 0 or !view.current.float) continue;
if (view.surfaceAt(ox, oy, &sx, &sy)) |found| {
return SurfaceAtResult{
.surface = found,
.sx = sx,
.sy = sy,
.parent = .{ .view = view },
};
}
}
// non-focused, floating views
it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
while (it.next()) |view| {
if (view.current.focus != 0 or !view.current.float) continue;
if (view.surfaceAt(ox, oy, &sx, &sy)) |found| {
return SurfaceAtResult{
.surface = found,
.sx = sx,
.sy = sy,
.parent = .{ .view = view },
};
}
}
// focused, non-floating views
it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
while (it.next()) |view| {
if (view.current.focus == 0 or view.current.float) continue;
if (view.surfaceAt(ox, oy, &sx, &sy)) |found| {
return SurfaceAtResult{
.surface = found,
.sx = sx,
.sy = sy,
.parent = .{ .view = view },
};
}
}
// non-focused, non-floating views
it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
while (it.next()) |view| {
if (view.current.focus != 0 or view.current.float) continue;
if (view.surfaceAt(ox, oy, &sx, &sy)) |found| {
return SurfaceAtResult{
.surface = found,
.sx = sx,
.sy = sy,
.parent = .{ .view = view },
};
}
}
return null;
}
fn xwaylandOverrideRedirectSurfaceAt(lx: f64, ly: f64) ?SurfaceAtResult {
var it = server.root.xwayland_override_redirect_views.first;
while (it) |node| : (it = node.next) {
const xwayland_surface = node.data.xwayland_surface;
var sx: f64 = undefined;
var sy: f64 = undefined;
if (xwayland_surface.surface.?.surfaceAt(
lx - @intToFloat(f64, xwayland_surface.x),
ly - @intToFloat(f64, xwayland_surface.y),
&sx,
&sy,
)) |found| {
return SurfaceAtResult{
.surface = found,
.sx = sx,
.sy = sy,
.parent = .{ .xwayland_override_redirect = &node.data },
};
}
}
return null;
}
fn surfaceAtFilter(view: *View, filter_tags: u32) bool {
return view.tree.node.enabled and view.current.tags & filter_tags != 0;
}
pub fn enterMode(self: *Self, mode: enum { move, resize }, view: *View) void {
log.debug("enter {s} cursor mode", .{@tagName(mode)});
@ -1024,8 +790,8 @@ pub fn checkFocusFollowsCursor(self: *Self) void {
// change can't occur.
if (self.seat.drag == .pointer) return;
if (server.config.focus_follows_cursor == .disabled) return;
if (self.surfaceAt()) |result| {
switch (result.parent) {
if (server.root.at(self.wlr_cursor.x, self.wlr_cursor.y)) |result| {
switch (result.node) {
.view => |view| {
// Don't re-focus the last focused view when the mode is .normal
if (server.config.focus_follows_cursor == .normal and
@ -1105,15 +871,17 @@ fn shouldPassthrough(self: Self) bool {
fn passthrough(self: *Self, time: u32) void {
assert(self.mode == .passthrough);
if (self.surfaceAt()) |result| {
assert((result.parent == .lock_surface) == (server.lock_manager.state != .unlocked));
self.seat.wlr_seat.pointerNotifyEnter(result.surface, result.sx, result.sy);
self.seat.wlr_seat.pointerNotifyMotion(time, result.sx, result.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();
if (server.root.at(self.wlr_cursor.x, self.wlr_cursor.y)) |result| {
// TODO audit session lock assertions after wlr_scene upgrade
assert((result.node == .lock_surface) == (server.lock_manager.state != .unlocked));
if (result.surface) |surface| {
self.seat.wlr_seat.pointerNotifyEnter(surface, result.sx, result.sy);
self.seat.wlr_seat.pointerNotifyMotion(time, result.sx, result.sy);
return;
}
}
self.clearFocus();
}
fn warp(self: *Self) void {

View File

@ -26,11 +26,14 @@ const wl = @import("wayland").server.wl;
const server = &@import("main.zig").server;
const util = @import("util.zig");
const DragIcon = @import("DragIcon.zig");
const LayerSurface = @import("LayerSurface.zig");
const LockSurface = @import("LockSurface.zig");
const Output = @import("Output.zig");
const SceneNodeData = @import("SceneNodeData.zig");
const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
const DragIcon = @import("DragIcon.zig");
scene: *wlr.Scene,
@ -118,6 +121,53 @@ pub fn deinit(self: *Self) void {
self.transaction_timer.remove();
}
pub const AtResult = struct {
surface: ?*wlr.Surface,
sx: f64,
sy: f64,
node: union(enum) {
view: *View,
layer_surface: *LayerSurface,
lock_surface: *LockSurface,
xwayland_override_redirect: if (build_options.xwayland) *XwaylandOverrideRedirect else noreturn,
},
};
/// Return information about what is currently rendered at the given layout coordinates.
pub fn at(self: Self, lx: f64, ly: f64) ?AtResult {
var sx: f64 = undefined;
var sy: f64 = undefined;
const node_at = self.scene.tree.node.at(lx, ly, &sx, &sy) orelse return null;
const surface: ?*wlr.Surface = blk: {
if (node_at.type == .buffer) {
const scene_buffer = wlr.SceneBuffer.fromNode(node_at);
if (wlr.SceneSurface.fromBuffer(scene_buffer)) |scene_surface| {
break :blk scene_surface.surface;
}
}
break :blk null;
};
{
var it: ?*wlr.SceneNode = node_at;
while (it) |node| : (it = node.parent) {
if (@intToPtr(?*SceneNodeData, node.data)) |scene_node_data| {
switch (scene_node_data.data) {
.view => |view| return .{
.surface = surface,
.sx = sx,
.sy = sy,
.node = .{ .view = view },
},
}
}
}
}
return null;
}
fn handleNewOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
const self = @fieldParentPtr(Self, "new_output", listener);
std.log.scoped(.output_manager).debug("new output {s}", .{wlr_output.name});

54
river/SceneNodeData.zig Normal file
View File

@ -0,0 +1,54 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2023 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, version 3.
//
// 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 SceneNodeData = @This();
const build_options = @import("build_options");
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const util = @import("util.zig");
const View = @import("View.zig");
const Data = union(enum) {
view: *View,
};
node: *wlr.SceneNode,
data: Data,
destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy),
pub fn attach(node: *wlr.SceneNode, data: Data) error{OutOfMemory}!void {
const scene_node_data = try util.gpa.create(SceneNodeData);
scene_node_data.* = .{
.node = node,
.data = data,
};
node.data = @ptrToInt(scene_node_data);
node.events.destroy.add(&scene_node_data.destroy);
}
fn handleDestroy(listener: *wl.Listener(void)) void {
const scene_node_data = @fieldParentPtr(SceneNodeData, "destroy", listener);
scene_node_data.destroy.link.remove();
scene_node_data.node.data = 0;
util.gpa.destroy(scene_node_data);
}

View File

@ -28,6 +28,7 @@ const server = &@import("main.zig").server;
const util = @import("util.zig");
const Output = @import("Output.zig");
const SceneNodeData = @import("SceneNodeData.zig");
const Seat = @import("Seat.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
const XdgToplevel = @import("XdgToplevel.zig");
@ -113,12 +114,14 @@ draw_borders: bool = true,
request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) =
wl.Listener(*wlr.XdgActivationV1.event.RequestActivate).init(handleRequestActivate),
pub fn init(self: *Self, output: *Output, tree: *wlr.SceneTree, impl: Impl) void {
pub fn init(self: *Self, output: *Output, tree: *wlr.SceneTree, impl: Impl) error{OutOfMemory}!void {
const initial_tags = blk: {
const tags = output.current.tags & server.config.spawn_tagmask;
break :blk if (tags != 0) tags else output.current.tags;
};
try SceneNodeData.attach(&tree.node, .{ .view = self });
self.* = .{
.impl = impl,
.output = output,
@ -371,15 +374,6 @@ pub inline fn forEachSurface(
}
}
/// Return the surface at output coordinates ox, oy and set sx, sy to the
/// corresponding surface-relative coordinates, if there is a surface.
pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface {
return switch (self.impl) {
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.surfaceAt(ox, oy, sx, sy),
.xwayland_view => |xwayland_view| xwayland_view.surfaceAt(ox, oy, sx, sy),
};
}
/// Return the current title of the view if any.
pub fn getTitle(self: Self) ?[*:0]const u8 {
return switch (self.impl) {

View File

@ -60,19 +60,21 @@ set_app_id: wl.Listener(void) = wl.Listener(void).init(handleSetAppId),
/// The View will add itself to the output's view stack on map
pub fn create(output: *Output, xdg_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void {
const node = try util.gpa.create(ViewStack(View).Node);
errdefer util.gpa.destroy(node);
const view = &node.view;
const tree = try output.tree.createSceneXdgSurface(xdg_toplevel.base);
errdefer tree.node.destroy();
view.init(output, tree, .{ .xdg_toplevel = .{
try view.init(output, tree, .{ .xdg_toplevel = .{
.view = view,
.xdg_toplevel = xdg_toplevel,
} });
const self = &node.view.impl.xdg_toplevel;
xdg_toplevel.base.data = @ptrToInt(self);
xdg_toplevel.base.data = @ptrToInt(view);
// Add listeners that are active over the view's entire lifetime
const self = &view.impl.xdg_toplevel;
xdg_toplevel.base.events.destroy.add(&self.destroy);
xdg_toplevel.base.events.map.add(&self.map);
xdg_toplevel.base.events.unmap.add(&self.unmap);
@ -122,18 +124,6 @@ pub fn setResizing(self: Self, resizing: bool) void {
_ = self.xdg_toplevel.setResizing(resizing);
}
/// Return the surface at output coordinates ox, oy and set sx, sy to the
/// corresponding surface-relative coordinates, if there is a surface.
pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface {
const view = self.view;
return self.xdg_toplevel.base.surfaceAt(
ox - @intToFloat(f64, view.current.box.x - view.surface_box.x),
oy - @intToFloat(f64, view.current.box.y - view.surface_box.y),
sx,
sy,
);
}
/// Return the current title of the toplevel if any.
pub fn getTitle(self: Self) ?[*:0]const u8 {
return self.xdg_toplevel.title;

View File

@ -142,17 +142,6 @@ pub fn setFullscreen(self: *Self, fullscreen: bool) void {
self.xwayland_surface.setFullscreen(fullscreen);
}
/// Return the surface at output coordinates ox, oy and set sx, sy to the
/// corresponding surface-relative coordinates, if there is a surface.
pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface {
return self.xwayland_surface.surface.?.surfaceAt(
ox - @intToFloat(f64, self.view.current.box.x),
oy - @intToFloat(f64, self.view.current.box.y),
sx,
sy,
);
}
/// Get the current title of the xwayland surface if any.
pub fn getTitle(self: Self) ?[*:0]const u8 {
return self.xwayland_surface.title;