diff --git a/.builds/alpine.yml b/.builds/alpine.yml index 30356d9..ae040fc 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -28,7 +28,7 @@ sources: tasks: - install_deps: | cd wlroots - git checkout 0.19.0 + git checkout 0.20.0 meson setup build --auto-features=enabled -Drenderers=gles2 \ -Dcolor-management=disabled -Dlibliftoff=disabled \ -Dexamples=false -Dwerror=false -Db_ndebug=false \ diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index bf7db0a..be19c56 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -25,7 +25,7 @@ sources: tasks: - install_deps: | cd wlroots - git checkout 0.19.0 + git checkout 0.20.0 meson setup build --auto-features=enabled -Drenderers=gles2 \ -Dcolor-management=disabled -Dlibliftoff=disabled \ -Dexamples=false -Dwerror=false -Db_ndebug=false \ diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 8450545..3191571 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -31,7 +31,7 @@ sources: tasks: - install_deps: | cd wlroots - git checkout 0.19.0 + git checkout 0.20.0 meson setup build --auto-features=enabled -Drenderers=gles2 \ -Dallocators=gbm \ -Dcolor-management=disabled -Dlibliftoff=disabled \ diff --git a/README.md b/README.md index 9231a79..0f84f01 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,12 @@ layout and shifted around as windows are opened/closed. Rather than having the tiled layout logic built into the compositor process, river-classic uses a [custom Wayland -protocol](https://codeberg.org/river/river-classic/src/branch/master/protocol/river-layout-v3.xml) +protocol](https://codeberg.org/river/river-classic/src/branch/main/protocol/river-layout-v3.xml) and separate "layout generator" process. A basic layout generator, `rivertile`, is provided but users are encouraged to use community-developed [layout -generators](https://codeberg.org/river/wiki/src/branch/master/pages/Community-Layouts.md) +generators](https://codeberg.org/river/wiki-classic/src/branch/main/pages/Community-Layouts.md) or write their own. Examples in C and Python may be found -[here](https://codeberg.org/river/river-classic/src/branch/master/contrib). +[here](https://codeberg.org/river/river-classic/src/branch/main/contrib). Tags are used to organize windows rather than workspaces. A window may be assigned to one or more tags. Likewise, one or more tags may be displayed on a @@ -53,7 +53,7 @@ distribution. - [zig](https://ziglang.org/download/) 0.15 - wayland - wayland-protocols -- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.19 +- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.20 - xkbcommon - libevdev - pixman diff --git a/build.zig b/build.zig index bdbd49c..81128dc 100644 --- a/build.zig +++ b/build.zig @@ -86,8 +86,11 @@ pub fn build(b: *Build) !void { scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); scanner.addSystemProtocol("stable/tablet/tablet-v2.xml"); + scanner.addSystemProtocol("staging/color-management/color-management-v1.xml"); + scanner.addSystemProtocol("staging/color-representation/color-representation-v1.xml"); scanner.addSystemProtocol("staging/cursor-shape/cursor-shape-v1.xml"); scanner.addSystemProtocol("staging/ext-session-lock/ext-session-lock-v1.xml"); + scanner.addSystemProtocol("staging/ext-image-copy-capture/ext-image-copy-capture-v1.xml"); scanner.addSystemProtocol("staging/tearing-control/tearing-control-v1.xml"); scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"); scanner.addSystemProtocol("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml"); @@ -98,6 +101,7 @@ pub fn build(b: *Build) !void { scanner.addCustomProtocol(b.path("protocol/river-layout-v3.xml")); scanner.addCustomProtocol(b.path("protocol/wlr-layer-shell-unstable-v1.xml")); scanner.addCustomProtocol(b.path("protocol/wlr-output-power-management-unstable-v1.xml")); + scanner.addCustomProtocol(b.path("protocol/virtual-keyboard-unstable-v1.xml")); // Some of these versions may be out of date with what wlroots implements. // This is not a problem in practice though as long as river successfully compiles. @@ -117,8 +121,11 @@ pub fn build(b: *Build) !void { scanner.generate("zwp_tablet_manager_v2", 1); scanner.generate("zxdg_decoration_manager_v1", 1); scanner.generate("ext_session_lock_manager_v1", 1); + scanner.generate("ext_image_copy_capture_manager_v1", 1); scanner.generate("wp_cursor_shape_manager_v1", 1); scanner.generate("wp_tearing_control_manager_v1", 1); + scanner.generate("wp_color_manager_v1", 2); + scanner.generate("wp_color_representation_manager_v1", 1); scanner.generate("zriver_control_v1", 1); scanner.generate("zriver_status_manager_v1", 4); @@ -126,6 +133,7 @@ pub fn build(b: *Build) !void { scanner.generate("zwlr_layer_shell_v1", 4); scanner.generate("zwlr_output_power_manager_v1", 1); + scanner.generate("zwp_virtual_keyboard_manager_v1", 1); const wayland = b.createModule(.{ .root_source_file = scanner.result }); @@ -141,7 +149,7 @@ pub fn build(b: *Build) !void { // exposed to the wlroots module for @cImport() to work. This seems to be // the best way to do so with the current std.Build API. wlroots.resolved_target = target; - wlroots.linkSystemLibrary("wlroots-0.19", .{}); + wlroots.linkSystemLibrary("wlroots-0.20", .{}); const flags = b.createModule(.{ .root_source_file = b.path("common/flags.zig") }); const globber = b.createModule(.{ .root_source_file = b.path("common/globber.zig") }); @@ -162,7 +170,7 @@ pub fn build(b: *Build) !void { river.linkSystemLibrary("libevdev"); river.linkSystemLibrary("libinput"); river.linkSystemLibrary("wayland-server"); - river.linkSystemLibrary("wlroots-0.19"); + river.linkSystemLibrary("wlroots-0.20"); river.linkSystemLibrary("xkbcommon"); river.linkSystemLibrary("pixman-1"); diff --git a/build.zig.zon b/build.zig.zon index da4fdfa..ba347f5 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,7 @@ // When a release is tagged, the "-dev" suffix should be removed for the // commit that gets tagged. Directly after the tagged commit, the version // should be bumped and the "-dev" suffix added. - .version = "0.3.15-dev", + .version = "0.3.16-dev", .paths = .{""}, .dependencies = .{ .pixman = .{ @@ -17,8 +17,8 @@ .hash = "wayland-0.4.0-lQa1khbMAQAsLS2eBR7M5lofyEGPIbu2iFDmoz8lPC27", }, .wlroots = .{ - .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.19.3.tar.gz", - .hash = "wlroots-0.19.3-jmOlcuL_AwBHhLCwpFsXbTizE3q9BugFmGX-XIxqcPMc", + .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.20.0.tar.gz", + .hash = "wlroots-0.20.0-jmOlcmtCBADS6eoJ6mkeiSNZkibrhD-c5Qwn-LiM86r1", }, .xkbcommon = .{ .url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.3.0.tar.gz", diff --git a/protocol/virtual-keyboard-unstable-v1.xml b/protocol/virtual-keyboard-unstable-v1.xml new file mode 100644 index 0000000..5095c91 --- /dev/null +++ b/protocol/virtual-keyboard-unstable-v1.xml @@ -0,0 +1,113 @@ + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2013 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2018 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The virtual keyboard provides an application with requests which emulate + the behaviour of a physical keyboard. + + This interface can be used by clients on its own to provide raw input + events, or it can accompany the input method protocol. + + + + + Provide a file descriptor to the compositor which can be + memory-mapped to provide a keyboard mapping description. + + Format carries a value from the keymap_format enumeration. + + + + + + + + + + + + + A key was pressed or released. + The time argument is a timestamp with millisecond granularity, with an + undefined base. All requests regarding a single object must share the + same clock. + + Keymap must be set before issuing this request. + + State carries a value from the key_state enumeration. + + + + + + + + + Notifies the compositor that the modifier and/or group state has + changed, and it should update state. + + The client should use wl_keyboard.modifiers event to synchronize its + internal state with seat state. + + Keymap must be set before issuing this request. + + + + + + + + + + + + + + + A virtual keyboard manager allows an application to provide keyboard + input events as if they came from a physical keyboard. + + + + + + + + + Creates a new virtual keyboard associated to a seat. + + If the compositor enables a keyboard to perform arbitrary actions, it + should present an error when an untrusted client requests a new + keyboard. + + + + + + diff --git a/river/Cursor.zig b/river/Cursor.zig index a92749b..fdeeb05 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -307,10 +307,7 @@ pub fn setTheme(cursor: *Cursor, theme: ?[*:0]const u8, _size: ?u32) !void { const wlr_xcursor = xcursor_manager.getXcursor("default", 1).?; const image = wlr_xcursor.images[0]; xwayland.setCursor( - image.buffer, - image.width * 4, - image.width, - image.height, + image.getBuffer(), @intCast(image.hotspot_x), @intCast(image.hotspot_y), ); diff --git a/river/InputManager.zig b/river/InputManager.zig index 0a744d4..96df752 100644 --- a/river/InputManager.zig +++ b/river/InputManager.zig @@ -104,8 +104,8 @@ pub fn init(input_manager: *InputManager) !void { input_manager.virtual_pointer_manager.events.new_virtual_pointer.add(&input_manager.new_virtual_pointer); input_manager.virtual_keyboard_manager.events.new_virtual_keyboard.add(&input_manager.new_virtual_keyboard); input_manager.pointer_constraints.events.new_constraint.add(&input_manager.new_constraint); - input_manager.input_method_manager.events.input_method.add(&input_manager.new_input_method); - input_manager.text_input_manager.events.text_input.add(&input_manager.new_text_input); + input_manager.input_method_manager.events.new_input_method.add(&input_manager.new_input_method); + input_manager.text_input_manager.events.new_text_input.add(&input_manager.new_text_input); } pub fn deinit(input_manager: *InputManager) void { diff --git a/river/InputRelay.zig b/river/InputRelay.zig index 69d655d..8e1a8e5 100644 --- a/river/InputRelay.zig +++ b/river/InputRelay.zig @@ -46,17 +46,12 @@ input_popups: wl.list.Head(InputPopup, .link), /// Always null if there is no input method. text_input: ?*TextInput = null, -input_method_commit: wl.Listener(*wlr.InputMethodV2) = - wl.Listener(*wlr.InputMethodV2).init(handleInputMethodCommit), -grab_keyboard: wl.Listener(*wlr.InputMethodV2.KeyboardGrab) = - wl.Listener(*wlr.InputMethodV2.KeyboardGrab).init(handleInputMethodGrabKeyboard), -input_method_destroy: wl.Listener(*wlr.InputMethodV2) = - wl.Listener(*wlr.InputMethodV2).init(handleInputMethodDestroy), -input_method_new_popup: wl.Listener(*wlr.InputPopupSurfaceV2) = - wl.Listener(*wlr.InputPopupSurfaceV2).init(handleInputMethodNewPopup), +input_method_commit: wl.Listener(void) = .init(handleInputMethodCommit), +grab_keyboard: wl.Listener(*wlr.InputMethodV2.KeyboardGrab) = .init(handleInputMethodGrabKeyboard), +input_method_destroy: wl.Listener(void) = .init(handleInputMethodDestroy), +input_method_new_popup: wl.Listener(*wlr.InputPopupSurfaceV2) = .init(handleInputMethodNewPopup), -grab_keyboard_destroy: wl.Listener(*wlr.InputMethodV2.KeyboardGrab) = - wl.Listener(*wlr.InputMethodV2.KeyboardGrab).init(handleInputMethodGrabKeyboardDestroy), +grab_keyboard_destroy: wl.Listener(void) = .init(handleInputMethodGrabKeyboardDestroy), pub fn init(relay: *InputRelay) void { relay.* = .{ .text_inputs = undefined, .input_popups = undefined }; @@ -89,12 +84,9 @@ pub fn newInputMethod(relay: *InputRelay, input_method: *wlr.InputMethodV2) void } } -fn handleInputMethodCommit( - listener: *wl.Listener(*wlr.InputMethodV2), - input_method: *wlr.InputMethodV2, -) void { +fn handleInputMethodCommit(listener: *wl.Listener(void)) void { const relay: *InputRelay = @fieldParentPtr("input_method_commit", listener); - assert(input_method == relay.input_method); + const input_method = relay.input_method.?; if (!input_method.client_active) return; const text_input = relay.text_input orelse return; @@ -123,12 +115,8 @@ fn handleInputMethodCommit( text_input.wlr_text_input.sendDone(); } -fn handleInputMethodDestroy( - listener: *wl.Listener(*wlr.InputMethodV2), - input_method: *wlr.InputMethodV2, -) void { +fn handleInputMethodDestroy(listener: *wl.Listener(void)) void { const relay: *InputRelay = @fieldParentPtr("input_method_destroy", listener); - assert(input_method == relay.input_method); relay.input_method_commit.link.remove(); relay.grab_keyboard.link.remove(); @@ -166,15 +154,15 @@ fn handleInputMethodNewPopup( }; } -fn handleInputMethodGrabKeyboardDestroy( - listener: *wl.Listener(*wlr.InputMethodV2.KeyboardGrab), - keyboard_grab: *wlr.InputMethodV2.KeyboardGrab, -) void { +fn handleInputMethodGrabKeyboardDestroy(listener: *wl.Listener(void)) void { const relay: *InputRelay = @fieldParentPtr("grab_keyboard_destroy", listener); + const input_method = relay.input_method.?; + const keyboard_grab = input_method.keyboard_grab.?; + relay.grab_keyboard_destroy.link.remove(); if (keyboard_grab.keyboard) |keyboard| { - keyboard_grab.input_method.seat.keyboardNotifyModifiers(&keyboard.modifiers); + input_method.seat.keyboardNotifyModifiers(&keyboard.modifiers); } } diff --git a/river/LayerSurface.zig b/river/LayerSurface.zig index 92b62cc..01654ac 100644 --- a/river/LayerSurface.zig +++ b/river/LayerSurface.zig @@ -230,6 +230,7 @@ fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.Xdg wlr_xdg_popup, layer_surface.popup_tree, layer_surface.popup_tree, + null, ) catch { wlr_xdg_popup.resource.postNoMemory(); return; diff --git a/river/Root.zig b/river/Root.zig index 71b9a57..e7eb8bd 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -124,6 +124,8 @@ pub fn init(root: *Root) !void { const gamma_control_manager = try wlr.GammaControlManagerV1.create(server.wl_server); scene.setGammaControlManagerV1(gamma_control_manager); + if (server.color_manager) |color_manager| scene.setColorManagerV1(color_manager); + const interactive_content = try scene.tree.createSceneTree(); const drag_icons = try scene.tree.createSceneTree(); const hidden_tree = try scene.tree.createSceneTree(); @@ -311,7 +313,7 @@ pub fn deactivateOutput(root: *Root, output: *Output) void { }; if (fallback_output) |fallback| { var it = output.pending.focus_stack.safeIterator(.reverse); - while (it.next()) |view| view.setPendingOutput(fallback); + while (it.next()) |view| view.setPendingOutput(fallback, fallback.attachMode()); } else { var it = output.pending.focus_stack.iterator(.forward); while (it.next()) |view| view.pending.output = null; @@ -390,7 +392,7 @@ pub fn activateOutput(root: *Root, output: *Output) void { output.pending.tags = root.fallback_pending.tags; { var it = root.fallback_pending.wm_stack.safeIterator(.reverse); - while (it.next()) |view| view.setPendingOutput(output); + while (it.next()) |view| view.setPendingOutput(output, .top); } { // Focus the new output with all seats @@ -407,7 +409,7 @@ pub fn activateOutput(root: *Root, output: *Output) void { const name = view.output_before_evac orelse continue; if (mem.eql(u8, name, mem.span(output.wlr_output.name))) { if (view.pending.output != output) { - view.setPendingOutput(output); + view.setPendingOutput(output, output.attachMode()); } util.gpa.free(name); view.output_before_evac = null; diff --git a/river/Server.zig b/river/Server.zig index f5f1efd..b444a74 100644 --- a/river/Server.zig +++ b/river/Server.zig @@ -22,7 +22,9 @@ const assert = std.debug.assert; const mem = std.mem; const posix = std.posix; const wlr = @import("wlroots"); -const wl = @import("wayland").server.wl; +const wayland = @import("wayland"); +const wl = wayland.server.wl; +const wp = wayland.server.wp; const c = @import("c.zig").c; const util = @import("util.zig"); @@ -44,6 +46,7 @@ const XdgDecoration = @import("XdgDecoration.zig"); const XdgToplevel = @import("XdgToplevel.zig"); const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig"); const XwaylandView = @import("XwaylandView.zig"); +const View = @import("View.zig"); const log = std.log.scoped(.server); @@ -52,6 +55,8 @@ wl_server: *wl.Server, sigint_source: *wl.EventSource, sigterm_source: *wl.EventSource, +fixes: *wlr.Fixes, + backend: *wlr.Backend, session: ?*wlr.Session, @@ -66,6 +71,9 @@ linux_dmabuf: ?*wlr.LinuxDmabufV1 = null, linux_drm_syncobj_manager: ?*wlr.LinuxDrmSyncobjManagerV1 = null, single_pixel_buffer_manager: *wlr.SinglePixelBufferManagerV1, +color_manager: ?*wlr.ColorManagerV1 = null, +color_representation_manager: *wlr.ColorRepresentationManagerV1, + viewporter: *wlr.Viewporter, fractional_scale_manager: *wlr.FractionalScaleManagerV1, compositor: *wlr.Compositor, @@ -84,6 +92,10 @@ data_control_manager: *wlr.DataControlManagerV1, export_dmabuf_manager: *wlr.ExportDmabufManagerV1, screencopy_manager: *wlr.ScreencopyManagerV1, +image_copy_capture_manager: *wlr.ExtImageCopyCaptureManagerV1, +output_image_capture_source_manager: *wlr.ExtOutputImageCaptureSourceManagerV1, +foreign_toplevel_image_capture_source_manager: *wlr.ExtForeignToplevelImageCaptureSourceManagerV1, + foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1, foreign_toplevel_list: *wlr.ExtForeignToplevelListV1, @@ -117,6 +129,8 @@ request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) = wl.Listener(*wlr.XdgActivationV1.event.RequestActivate).init(handleRequestActivate), request_set_cursor_shape: wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape) = wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape).init(handleRequestSetCursorShape), +new_foreign_toplevel_capture_request: wl.Listener(*wlr.ExtForeignToplevelImageCaptureSourceManagerV1.Request) = + wl.Listener(*wlr.ExtForeignToplevelImageCaptureSourceManagerV1.Request).init(handleNewForeignToplevelCaptureRequest), pub fn init(server: *Server, runtime_xwayland: bool) !void { // We intentionally don't try to prevent memory leaks on error in this function @@ -137,6 +151,8 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void { .sigint_source = try loop.addSignal(*wl.Server, posix.SIG.INT, terminate, wl_server), .sigterm_source = try loop.addSignal(*wl.Server, posix.SIG.TERM, terminate, wl_server), + .fixes = try wlr.Fixes.create(wl_server, 1), + .backend = backend, .session = session, .renderer = renderer, @@ -147,11 +163,13 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void { .shm = try wlr.Shm.createWithRenderer(wl_server, 2, renderer), .single_pixel_buffer_manager = try wlr.SinglePixelBufferManagerV1.create(wl_server), + .color_representation_manager = try wlr.ColorRepresentationManagerV1.createWithRenderer(wl_server, 1, renderer), + .viewporter = try wlr.Viewporter.create(wl_server), .fractional_scale_manager = try wlr.FractionalScaleManagerV1.create(wl_server, 1), .compositor = compositor, .subcompositor = try wlr.Subcompositor.create(wl_server), - .cursor_shape_manager = try wlr.CursorShapeManagerV1.create(server.wl_server, 1), + .cursor_shape_manager = try wlr.CursorShapeManagerV1.create(server.wl_server, 2), .xdg_shell = try wlr.XdgShell.create(wl_server, 5), .xdg_decoration_manager = try wlr.XdgDecorationManagerV1.create(wl_server), @@ -165,6 +183,10 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void { .export_dmabuf_manager = try wlr.ExportDmabufManagerV1.create(wl_server), .screencopy_manager = try wlr.ScreencopyManagerV1.create(wl_server), + .image_copy_capture_manager = try wlr.ExtImageCopyCaptureManagerV1.create(wl_server, 1), + .output_image_capture_source_manager = try wlr.ExtOutputImageCaptureSourceManagerV1.create(wl_server, 1), + .foreign_toplevel_image_capture_source_manager = try wlr.ExtForeignToplevelImageCaptureSourceManagerV1.create(wl_server, 1), + .foreign_toplevel_manager = try wlr.ForeignToplevelManagerV1.create(wl_server), .foreign_toplevel_list = try wlr.ExtForeignToplevelListV1.create(wl_server, 1), @@ -194,6 +216,23 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void { } } + if (renderer.features.input_color_transform) { + const render_intents: []const wp.ColorManagerV1.RenderIntent = &.{.perceptual}; + const transfer_functions = renderer.transferFunctionList(); + defer std.c.free(transfer_functions.ptr); + const primaries = renderer.primariesList(); + defer std.c.free(primaries.ptr); + server.color_manager = try wlr.ColorManagerV1.create(wl_server, 2, .{ + .features = .{ + .parametric = true, + .set_mastering_display_primaries = true, + }, + .render_intents = render_intents, + .transfer_functions = transfer_functions, + .primaries = primaries, + }); + } + if (build_options.xwayland and runtime_xwayland) { server.xwayland = try wlr.Xwayland.create(wl_server, compositor, false); server.xwayland.?.events.new_surface.add(&server.new_xwayland_surface); @@ -213,6 +252,7 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void { server.layer_shell.events.new_surface.add(&server.new_layer_surface); server.xdg_activation.events.request_activate.add(&server.request_activate); server.cursor_shape_manager.events.request_set_shape.add(&server.request_set_cursor_shape); + server.foreign_toplevel_image_capture_source_manager.events.new_request.add(&server.new_foreign_toplevel_capture_request); wl_server.setGlobalFilter(*Server, globalFilter, server); } @@ -228,6 +268,7 @@ pub fn deinit(server: *Server) void { server.new_layer_surface.link.remove(); server.request_activate.link.remove(); server.request_set_cursor_shape.link.remove(); + server.new_foreign_toplevel_capture_request.link.remove(); if (build_options.xwayland) { if (server.xwayland) |xwayland| { @@ -306,6 +347,9 @@ fn allowlist(server: *Server, global: *const wl.Global) bool { if (server.linux_drm_syncobj_manager) |linux_drm_syncobj_manager| { if (global == linux_drm_syncobj_manager.global) return true; } + if (server.color_manager) |color_manager| { + if (global == color_manager.global) return true; + } // We must use the getInterface() approach for dynamically created globals // such as wl_output and wl_seat since the wl_global_create() function will @@ -320,8 +364,10 @@ fn allowlist(server: *Server, global: *const wl.Global) bool { // For other globals I like the current pointer comparison approach as it // should catch river accidentally exposing multiple copies of e.g. wl_shm // with an assertion failure. - return global == server.shm.global or + return global == server.fixes.global or + global == server.shm.global or global == server.single_pixel_buffer_manager.global or + global == server.color_representation_manager.global or global == server.viewporter.global or global == server.fractional_scale_manager.global or global == server.compositor.global or @@ -351,6 +397,9 @@ fn blocklist(server: *Server, global: *const wl.Global) bool { global == server.foreign_toplevel_manager.global or global == server.foreign_toplevel_list.global or global == server.screencopy_manager.global or + global == server.image_copy_capture_manager.global or + global == server.output_image_capture_source_manager.global or + global == server.foreign_toplevel_image_capture_source_manager.global or global == server.export_dmabuf_manager.global or global == server.data_control_manager.global or global == server.layout_manager.global or @@ -556,3 +605,24 @@ fn handleRequestSetCursorShape( } } } + +fn handleNewForeignToplevelCaptureRequest( + listener: *wl.Listener(*wlr.ExtForeignToplevelImageCaptureSourceManagerV1.Request), + request: *wlr.ExtForeignToplevelImageCaptureSourceManagerV1.Request, +) void { + const server: *Server = @fieldParentPtr("new_foreign_toplevel_capture_request", listener); + if (request.toplevel_handle.data) |opaque_view| { + const view: *View = @ptrCast(@alignCast(opaque_view)); + const capture_source = view.image_capture_source orelse wlr.ExtImageCaptureSourceV1.createWithSceneNode( + &view.image_capture_scene.tree.node, + server.wl_server.getEventLoop(), + server.allocator, + server.renderer, + ) catch { + log.err("failed to create ext image capture source", .{}); + return; + }; + + _ = request.accept(capture_source); + } +} diff --git a/river/TextInput.zig b/river/TextInput.zig index e296cb4..f9d601f 100644 --- a/river/TextInput.zig +++ b/river/TextInput.zig @@ -33,14 +33,10 @@ link: wl.list.Link, wlr_text_input: *wlr.TextInputV3, -enable: wl.Listener(*wlr.TextInputV3) = - wl.Listener(*wlr.TextInputV3).init(handleEnable), -commit: wl.Listener(*wlr.TextInputV3) = - wl.Listener(*wlr.TextInputV3).init(handleCommit), -disable: wl.Listener(*wlr.TextInputV3) = - wl.Listener(*wlr.TextInputV3).init(handleDisable), -destroy: wl.Listener(*wlr.TextInputV3) = - wl.Listener(*wlr.TextInputV3).init(handleDestroy), +enable: wl.Listener(void) = .init(handleEnable), +commit: wl.Listener(void) = .init(handleCommit), +disable: wl.Listener(void) = .init(handleDisable), +destroy: wl.Listener(void) = .init(handleDestroy), pub fn create(wlr_text_input: *wlr.TextInputV3) !void { const seat: *Seat = @ptrCast(@alignCast(wlr_text_input.seat.data)); @@ -62,7 +58,7 @@ pub fn create(wlr_text_input: *wlr.TextInputV3) !void { wlr_text_input.events.destroy.add(&text_input.destroy); } -fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { +fn handleEnable(listener: *wl.Listener(void)) void { const text_input: *TextInput = @fieldParentPtr("enable", listener); const seat: *Seat = @ptrCast(@alignCast(text_input.wlr_text_input.seat.data)); @@ -89,7 +85,7 @@ fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) v } } -fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { +fn handleCommit(listener: *wl.Listener(void)) void { const text_input: *TextInput = @fieldParentPtr("commit", listener); const seat: *Seat = @ptrCast(@alignCast(text_input.wlr_text_input.seat.data)); @@ -103,7 +99,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) v } } -fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { +fn handleDisable(listener: *wl.Listener(void)) void { const text_input: *TextInput = @fieldParentPtr("disable", listener); const seat: *Seat = @ptrCast(@alignCast(text_input.wlr_text_input.seat.data)); @@ -112,7 +108,7 @@ fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) } } -fn handleDestroy(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { +fn handleDestroy(listener: *wl.Listener(void)) void { const text_input: *TextInput = @fieldParentPtr("destroy", listener); const seat: *Seat = @ptrCast(@alignCast(text_input.wlr_text_input.seat.data)); diff --git a/river/View.zig b/river/View.zig index f93026e..021e82f 100644 --- a/river/View.zig +++ b/river/View.zig @@ -28,6 +28,7 @@ const wp = @import("wayland").server.wp; const server = &@import("main.zig").server; const util = @import("util.zig"); +const Config = @import("Config.zig"); const ForeignToplevelHandle = @import("ForeignToplevelHandle.zig"); const Output = @import("Output.zig"); const SceneNodeData = @import("SceneNodeData.zig"); @@ -139,6 +140,9 @@ saved_surface_tree: *wlr.SceneTree, borders: [4]*wlr.SceneRect, popup_tree: *wlr.SceneTree, +image_capture_scene: *wlr.Scene, +image_capture_source: ?*wlr.ExtImageCaptureSourceV1, + /// Bounds on the width/height of the view, set by the toplevel/xwayland_view implementation. constraints: Constraints = .{}, @@ -214,6 +218,9 @@ pub fn create(impl: Impl) error{OutOfMemory}!*View { }, .popup_tree = popup_tree, + .image_capture_scene = try wlr.Scene.create(), + .image_capture_source = null, + .pending_wm_stack_link = undefined, .pending_focus_stack_link = undefined, .inflight_wm_stack_link = undefined, @@ -229,6 +236,7 @@ pub fn create(impl: Impl) error{OutOfMemory}!*View { view.tree.node.setEnabled(false); view.popup_tree.node.setEnabled(false); view.saved_surface_tree.node.setEnabled(false); + view.image_capture_scene.restack_xwayland_surfaces = false; try SceneNodeData.attach(&view.tree.node, .{ .view = view }); try SceneNodeData.attach(&view.popup_tree.node, .{ .view = view }); @@ -249,6 +257,7 @@ pub fn destroy(view: *View, when: enum { lazy, assert }) void { // around until the current transaction completes. This function will be // called again in Root.commitTransaction() if (!view.saved_surface_tree.node.enabled) { + view.image_capture_scene.tree.node.destroy(); view.tree.node.destroy(); view.popup_tree.node.destroy(); @@ -529,12 +538,12 @@ fn saveSurfaceTreeIter( saved.setTransform(buffer.transform); } -pub fn setPendingOutput(view: *View, output: *Output) void { +pub fn setPendingOutput(view: *View, output: *Output, attach_mode: Config.AttachMode) void { view.pending.output = output; view.pending_wm_stack_link.remove(); view.pending_focus_stack_link.remove(); - switch (output.attachMode()) { + switch (attach_mode) { .top => output.pending.wm_stack.prepend(view), .bottom => output.pending.wm_stack.append(view), .after => |n| view.attachAfter(&output.pending, n), @@ -664,6 +673,7 @@ pub fn map(view: *View) !void { .app_id = view.getAppId(), })) |handle| { view.ext_foreign_toplevel_handle = handle; + handle.data = view; } else |_| { log.err("failed to create ext foreign toplevel handle", .{}); } @@ -719,7 +729,7 @@ pub fn map(view: *View) !void { }; if (output) |o| { - view.setPendingOutput(o); + view.setPendingOutput(o, o.attachMode()); var it = server.input_manager.seats.iterator(.forward); while (it.next()) |seat| seat.focus(view); diff --git a/river/XdgPopup.zig b/river/XdgPopup.zig index 3467210..44982e6 100644 --- a/river/XdgPopup.zig +++ b/river/XdgPopup.zig @@ -34,6 +34,8 @@ root: *wlr.SceneTree, tree: *wlr.SceneTree, +image_capture_tree: ?*wlr.SceneTree, + destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy), commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup), @@ -44,14 +46,21 @@ pub fn create( wlr_xdg_popup: *wlr.XdgPopup, root: *wlr.SceneTree, parent: *wlr.SceneTree, + image_capture_parent: ?*wlr.SceneTree, ) error{OutOfMemory}!void { const xdg_popup = try util.gpa.create(XdgPopup); errdefer util.gpa.destroy(xdg_popup); + const image_capture_tree = if (image_capture_parent) |p| + try p.createSceneXdgSurface(wlr_xdg_popup.base) + else + null; + xdg_popup.* = .{ .wlr_xdg_popup = wlr_xdg_popup, .root = root, .tree = try parent.createSceneXdgSurface(wlr_xdg_popup.base), + .image_capture_tree = image_capture_tree, }; wlr_xdg_popup.events.destroy.add(&xdg_popup.destroy); @@ -86,6 +95,7 @@ fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.Xdg wlr_xdg_popup, xdg_popup.root, xdg_popup.tree, + xdg_popup.image_capture_tree, ) catch { wlr_xdg_popup.resource.postNoMemory(); return; diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index d7ef3cf..8041a34 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -97,6 +97,7 @@ pub fn create(wlr_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void { errdefer toplevel.unmap.link.remove(); _ = try view.surface_tree.createSceneXdgSurface(wlr_toplevel.base); + _ = try view.image_capture_scene.tree.createSceneXdgSurface(wlr_toplevel.base); toplevel.view = view; @@ -282,7 +283,12 @@ fn handleUnmap(listener: *wl.Listener(void)) void { fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void { const toplevel: *XdgToplevel = @fieldParentPtr("new_popup", listener); - XdgPopup.create(wlr_xdg_popup, toplevel.view.popup_tree, toplevel.view.popup_tree) catch { + XdgPopup.create( + wlr_xdg_popup, + toplevel.view.popup_tree, + toplevel.view.popup_tree, + &toplevel.view.image_capture_scene.tree, + ) catch { wlr_xdg_popup.resource.postNoMemory(); return; }; diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index 732cbb3..d61eed8 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -179,6 +179,12 @@ pub fn handleMap(listener: *wl.Listener(void)) void { return; }; + _ = view.image_capture_scene.tree.createSceneSurface(surface) catch { + log.err("out of memory", .{}); + surface.resource.getClient().postNoMemory(); + return; + }; + view.pending.box = .{ .x = 0, .y = 0, diff --git a/river/command/output.zig b/river/command/output.zig index 98d1726..6dccf80 100644 --- a/river/command/output.zig +++ b/river/command/output.zig @@ -78,7 +78,7 @@ pub fn sendToOutput( seat.focused.view.pending.tags = destination_output.pending.tags; } - seat.focused.view.setPendingOutput(destination_output); + seat.focused.view.setPendingOutput(destination_output, destination_output.attachMode()); // When explicitly sending a view to an output, the user likely // does not expect a previously evacuated view moved back to a