From 683ed0f04e89099e734afd5d1a8c9b76077e0f82 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sun, 29 Jan 2023 12:03:41 +0100 Subject: [PATCH] Cursor: remove surfaceAt() We now use the wlr_scene API to find out what is at the cursor location. --- river/Cursor.zig | 308 +++++----------------------------------- river/Root.zig | 52 ++++++- river/SceneNodeData.zig | 54 +++++++ river/View.zig | 14 +- river/XdgToplevel.zig | 20 +-- river/XwaylandView.zig | 11 -- 6 files changed, 152 insertions(+), 307 deletions(-) create mode 100644 river/SceneNodeData.zig diff --git a/river/Cursor.zig b/river/Cursor.zig index feca44f..f696a4e 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -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 { diff --git a/river/Root.zig b/river/Root.zig index 2543f13..429bc74 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -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}); diff --git a/river/SceneNodeData.zig b/river/SceneNodeData.zig new file mode 100644 index 0000000..36a815d --- /dev/null +++ b/river/SceneNodeData.zig @@ -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 . + +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); +} diff --git a/river/View.zig b/river/View.zig index 5dc4ce2..9d932f0 100644 --- a/river/View.zig +++ b/river/View.zig @@ -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) { diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index 9a5e077..a9509ac 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -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; diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index 3b6d807..0f77a26 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -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;