aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--river/Cursor.zig308
-rw-r--r--river/Root.zig52
-rw-r--r--river/SceneNodeData.zig54
-rw-r--r--river/View.zig14
-rw-r--r--river/XdgToplevel.zig20
-rw-r--r--river/XwaylandView.zig11
6 files changed, 152 insertions, 307 deletions
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 <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);
+}
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;