commit 120ceaf05e0feb30b65124801b12ece290bb9338 from: mtmn date: Wed Sep 23 18:55:36 2026 UTC migrate tree to river client architecture commit - 7b9ed7a39e3d9a6b73b9bca5dde1780ad6b43137 commit + 120ceaf05e0feb30b65124801b12ece290bb9338 blob - 3b72907378186aea4b7cef3e8272441751a3001f blob + 98217fe532330103bd4e4bfcf8adbed591b5537e --- .builds/alpine.yml +++ .builds/alpine.yml @@ -50,6 +50,6 @@ tasks: - fmt: | cd river-classic zig fmt --check river/ + zig fmt --check ponton/ zig fmt --check riverctl/ - zig fmt --check rivertile/ zig fmt --check build.zig blob - 28bc349e3041befb38f09e19f10bb5999d0366d0 blob + d1d74a050aa7ff36900d8179660a9011156c70fb --- .builds/archlinux.yml +++ .builds/archlinux.yml @@ -49,6 +49,6 @@ tasks: - fmt: | cd river-classic zig fmt --check river/ + zig fmt --check ponton/ zig fmt --check riverctl/ - zig fmt --check rivertile/ zig fmt --check build.zig blob - 220978579418c077b13a7e3f74f1adc6b528d53f blob + fb925e13e54dfe2e037b89e9619aeeb631dc9964 --- .builds/freebsd.yml +++ .builds/freebsd.yml @@ -54,6 +54,6 @@ tasks: - fmt: | cd river-classic zig fmt --check river/ + zig fmt --check ponton/ zig fmt --check riverctl/ - zig fmt --check rivertile/ zig fmt --check build.zig blob - 03cb27d540eacf456abb8d4e94196d3456ea6ea2 blob + f8456d19d4d421c81d40d8891fa32494c4fe889e --- .gitignore +++ .gitignore @@ -1,3 +1,4 @@ .zig-cache/ zig-out/ zig-pkg/ +river-new blob - /dev/null blob + 11d5b10534b3ceb16e234404a33d153b09c3ccd7 (mode 644) --- /dev/null +++ ARCHITECTURE.md @@ -0,0 +1,45 @@ + + +# Architecture + +This is a high level overview of the structure of river's code base. + +See also the [doc/internal](doc/internal) directory for in-depth documentation +of complex corners of the code base. If you are struggling to understand some +part of river's code and feel like it should have documentation in that +directory, feel free to open an issue. + +## Window Management State Machine + +At the heart of river lies the window management state machine specified in the +[river-window-management-v1] protocol and described in an [introductory blog +post] and [FOSDEM talk]. If you have not yet familiarized yourself with the +state machine, that should be your first step. + +In river's code base the state machine is implemented in `WindowManager`. The +three core objects in the state machine are `Window`, `Output`, and `Seat`. +Each of these files contain `manageStart()`, `manageFinish()`, `renderStart()`, +and `renderFinish()` functions which progress the state machine, with the top +level WindowManager.zig calling into the subordinate objects. + +## Input + +The top level code handling input (e.g. from keyboard or pointer hardware) is +located in `InputManager`. `InputDevice` represents a single hardware or virtual +input source. Each `InputDevice` is assigned to exactly one `Seat`, which is +exposed to Wayland clients and the window management state machine. + +## Output + +The top level code handling output (i.e. compositing and displaying buffers on +your monitor) is located in `OutputManager`. Individual output devices are +represented by the `Output` type. For frame perfection, all changes to rendered +state are double-buffered and synchronized with the window management state +machine. + +[river-window-management-v1]: https://isaacfreund.com/docs/wayland/river-window-management-v1/ +[introductory blog post]: https://isaacfreund.com/blog/river-window-management/ +[FOSDEM talk]: https://fosdem.org/2026/schedule/event/GR8BFE-separating_the_wayland_compositor_and_window_manager/ blob - 1c5d6d8e596e92ef35e39711de6b5162d803350d blob + cbabc5794c8ce2851d00273dc781edbbc6fd1f8e --- CONTRIBUTING.md +++ CONTRIBUTING.md @@ -1,66 +1,22 @@ -## Contributing to river +## Contributing to river-classic -Contributing is as simple as opening a pull request on -[codeberg](https://codeberg.org/river/river). -You'll likely have more success with your contribution if you visit -[#river](https://web.libera.chat/?channels=#river) on irc.libera.chat to discuss -your plans first. +Open a pull request on Codeberg after discussing substantial changes with the +river developers. ## Commit messages -Please take the time to write a good commit message, having a clean git -history makes maintaining and contributing to river easier. Commit messages -should start with a prefix indicating what part of river is affected by the -change, followed by a brief summary. - +Commit messages should start with the affected area followed by a short summary. For example: +```text +protocol: add window management support +classic: handle window dimensions ``` -build: scan river-status protocol -``` -or - -``` -river-status: send view_tags on view map/unmap -``` - -In addition to the summary, feel free to add any other details you want preceded -by a blank line. A good rule of thumb is that anything you would write in a pull -request description on codeberg has a place in the commit message as well. - -For further details regarding commit style and git history see -[weston's contributing guidelines](https://gitlab.freedesktop.org/wayland/weston/-/blob/master/CONTRIBUTING.md#formatting-and-separating-commits). - ## Coding style -Please follow the -[Zig Style Guide](https://ziglang.org/documentation/master/#Style-Guide) -and run `zig fmt` before every commit. With regards to line length, keep it -under 100 columns in general but prioritize readability over adhering to a -strict limit. Note that inserting a trailing comma after the last parameter in -function calls, struct declarations, etc. will cause `zig fmt` to wrap those -lines. I highly recommend configuring your editor to run `zig fmt` on write. +Follow Zig's style guide. Run `zig fmt` before every commit. Keep lines below +100 columns where practical. Use braces for multi-line conditionals. -The single additional style rule is to avoid writing `if` statements and -similar across multiple lines without braces: - -```zig -test { - // avoid this - if (foo) - bar(); - - // prefer this - if (foo) bar(); - - // or this - if (foo) { - bar(); - } -} -``` - -On a higher level, prioritize simplicity of code over nearly everything else. -Performance is only a valid reason for code complexity if there are profiling -results to back it up which demonstrate a significant benefit. +Keep compositor code policy-free. Window manager policy belongs in `classic/`. +Do not add old control, status or layout protocols back to the compositor. blob - 90e06fcb683f84198268e36cc2bfa5dd21612749 blob + f6ae5ef4a5f71ec964c67490c96382c21e45445a --- README.md +++ README.md @@ -1,105 +1,61 @@ -# river-classic +# ponton -river-classic is a dynamic tiling Wayland compositor with flexible runtime -configuration. +`ponton` is a dynamic tiling window manager for the non-monolithic `river` +compositor, rewritten from river-classic onto the stable river protocols. +`river` owns Wayland clients, rendering and frame synchronisation. `ponton` +owns tags, focus, keybindings, layout policy and window management decisions. -It is a fork of [river](https://codeberg.org/river/river) 0.3 intended for users -that are happy with how river 0.3 works and do not wish to deal with the majorly -breaking changes planned for the river 0.4.0 release. +This tree builds `ponton` and `riverctl` only. The `river` compositor comes +from upstream river and is installed separately. -Join us at [#river](https://web.libera.chat/?channels=#river) on irc.libera.chat — -Read our man pages, [wiki](https://codeberg.org/river/wiki-classic), and -[Code of Conduct](CODE_OF_CONDUCT.md) +The compositor and window manager communicate through the stable +`river-window-management-v1` protocol. Input configuration uses stable river +input protocols. Old compositor-side control, status and layout-generator +protocols are not part of this tree. Commands without a protocol surface +are documented under NOT SUPPORTED in `riverctl`(1): external layout +generators, compositor background and cursor hiding, lid-switch bindings +and keyboard groups. -The main repository is on [codeberg](https://codeberg.org/river/river-classic), -which is where the issue tracker may be found and where contributions are accepted. - -Read-only mirrors exist on [sourcehut](https://git.sr.ht/~ifreund/river-classic) -and [github](https://github.com/riverwm/river-classic). - -## Features - -river-classic's window management style is quite similar to -[dwm](http://dwm.suckless.org), [xmonad](https://xmonad.org), and other classic -dynamic tiling X11 window managers. Windows are automatically arranged in a tiled -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/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-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/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 -monitor at a time. - -river-classic is configured at runtime using the `riverctl` tool. It can define -keybindings, set the active layout generator, configure input devices, and more. -On startup, river-classic runs a user-defined init script which usually runs -`riverctl` commands to set up the user's configuration. - ## Building -Note: If you are packaging river-classic for distribution, see [PACKAGING.md](PACKAGING.md). +Dependencies: -To compile river-classic first ensure that you have the following dependencies -installed. The "development" versions are required if applicable to your -distribution. - -- [zig](https://ziglang.org/download/) 0.16 +- Zig 0.16 - wayland - wayland-protocols -- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.20 -- xkbcommon +- wlroots 0.20 +- xkbcommon 1.12 or newer - libevdev +- libinput - pixman - pkg-config -- scdoc (optional, but required for man page generation) +- scdoc for man pages -Then run, for example: -``` +Build and install: + +```text zig build -Doptimize=ReleaseSafe --prefix ~/.local install ``` -To enable Xwayland support pass the `-Dxwayland` option as well. -Run `zig build -h` to see a list of all options. -## Usage +Pass `-Dxwayland` to enable Xwayland support. -River can either be run nested in an X11/Wayland session or directly -from a tty using KMS/DRM. Simply run the `river` command. +## Running -On startup river-classic will run an executable file at `$XDG_CONFIG_HOME/river/init` -if such an executable exists. If `$XDG_CONFIG_HOME` is not set, -`~/.config/river/init` will be used instead. +Start upstream `river` from your session startup command. Start `ponton` +from `$XDG_CONFIG_HOME/river/init` or `$HOME/.config/river/init`. -Usually this executable is a shell script invoking *riverctl*(1) to create -mappings, start programs such as a layout generator or status bar, and -perform other configuration. +`ponton` creates a private control socket at: -An example init script with sane defaults is provided [here](example/init) -in the example directory. +```text +$XDG_RUNTIME_DIR/ponton.sock +``` -For complete documentation see the `river(1)`, `riverctl(1)`, and -`rivertile(1)` man pages. +`riverctl` sends commands to this socket. It does not bind an old river +Wayland control protocol. -## Donate - -If my work on river-classic adds value to your life and you'd like to support me -financially you can find donation information [here](https://isaacfreund.com/donate/). - -## Licensing - -river-classic is released under the GNU General Public License v3.0 only. - -The protocols in the `protocol` directory are released under various licenses by -various parties. You should refer to the copyright block of each protocol for -the licensing information. The protocols prefixed with `river` and developed by -this project are released under the ISC license (as stated in their copyright -blocks). - -The river logo is licensed under the CC BY-SA 4.0 license, see the -[license](logo/LICENSE) in the logo directory. +`ponton` supports protocol lifecycle, tiling layout with adjustable main +area, tag visibility, focus cycling, keyboard and pointer bindings with +modes, interactive move and resize, float and fullscreen state, window +rules, input device configuration, keyboard layouts, layer-shell awareness, +floating window ops, multi-output focus and routing, cursor policy, borders, +`close` and `exit`. blob - 37f8bdd083cec5f14b99f83c6a40e4d250fab896 blob + 51f4475b24b32070c140b62dab001e2437ac86f1 --- build.zig +++ build.zig @@ -1,14 +1,10 @@ +// SPDX-FileCopyrightText: © 2020 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + const std = @import("std"); -const assert = std.debug.assert; const Build = std.Build; -const fs = std.fs; -const mem = std.mem; -const manifest = @import("build.zig.zon"); -const version = manifest.version; - const Scanner = @import("wayland").Scanner; -const Translator = @import("translate_c").Translator; pub fn build(b: *Build) !void { const target = b.standardTargetOptions(.{}); @@ -34,84 +30,37 @@ pub fn build(b: *Build) !void { break :scdoc_found true; }; - const bash_completion = b.option( - bool, - "bash-completion", - "Set to true to install bash completion for riverctl. Defaults to true.", - ) orelse true; - - const zsh_completion = b.option( - bool, - "zsh-completion", - "Set to true to install zsh completion for riverctl. Defaults to true.", - ) orelse true; - - const fish_completion = b.option( - bool, - "fish-completion", - "Set to true to install fish completion for riverctl. Defaults to true.", - ) orelse true; - - const xwayland = b.option( - bool, - "xwayland", - "Set to true to enable xwayland support", - ) orelse false; - - const full_version = blk: { - if (mem.endsWith(u8, version, "-dev")) { - var ret: u8 = undefined; - - const git_describe_long = b.runAllowFail( - &.{ "git", "-C", b.build_root.path orelse ".", "describe", "--long" }, - &ret, - .inherit, - ) catch break :blk version; - - var it = mem.splitScalar(u8, mem.trim(u8, git_describe_long, &std.ascii.whitespace), '-'); - _ = it.next().?; // previous tag - const commit_count = it.next().?; - const commit_hash = it.next().?; - assert(it.next() == null); - assert(commit_hash[0] == 'g'); - - // Follow semantic versioning, e.g. 0.2.0-dev.42+d1cf95b - break :blk b.fmt(version ++ ".{s}+{s}", .{ commit_count, commit_hash[1..] }); - } else { - break :blk version; - } - }; - - const options = b.addOptions(); - options.addOption(bool, "xwayland", xwayland); - options.addOption([]const u8, "version", full_version); - const scanner = Scanner.create(b, .{}); - scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); scanner.addSystemProtocol("stable/tablet/tablet-v2.xml"); + scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.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"); scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"); + scanner.addSystemProtocol("unstable/xdg-foreign/xdg-foreign-unstable-v2.xml"); + scanner.addSystemProtocol("staging/ext-transient-seat/ext-transient-seat-v1.xml"); - scanner.addCustomProtocol(b.path("protocol/river-control-unstable-v1.xml")); - scanner.addCustomProtocol(b.path("protocol/river-status-unstable-v1.xml")); - 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")); + scanner.addCustomProtocol(b.path("protocol/river-window-management-v1.xml")); + scanner.addCustomProtocol(b.path("protocol/river-xkb-bindings-v1.xml")); + scanner.addCustomProtocol(b.path("protocol/river-layer-shell-v1.xml")); + scanner.addCustomProtocol(b.path("protocol/river-touch-gestures-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. - // These versions control Zig code generation and have no effect on anything internal - // to wlroots. Therefore, the only thnig that can happen due to a version being too - // old is that river fails to compile. + scanner.addCustomProtocol(b.path("protocol/river-input-management-v1.xml")); + scanner.addCustomProtocol(b.path("protocol/river-libinput-config-v1.xml")); + scanner.addCustomProtocol(b.path("protocol/river-xkb-config-v1.xml")); + + scanner.addCustomProtocol(b.path("protocol/upstream/wlr-layer-shell-unstable-v1.xml")); + scanner.addCustomProtocol(b.path("protocol/upstream/wlr-output-power-management-unstable-v1.xml")); + scanner.addCustomProtocol(b.path("protocol/upstream/virtual-keyboard-unstable-v1.xml")); + + // Some of these versions may be out of date with what upstream implements. + // This is not a problem in practice though as long as ponton successfully + // compiles. These versions control Zig code generation only. scanner.generate("wl_compositor", 4); scanner.generate("wl_subcompositor", 1); scanner.generate("wl_shm", 1); @@ -124,54 +73,37 @@ pub fn build(b: *Build) !void { scanner.generate("zwp_pointer_constraints_v1", 1); scanner.generate("zwp_tablet_manager_v2", 1); scanner.generate("zxdg_decoration_manager_v1", 1); + scanner.generate("zxdg_importer_v2", 1); + scanner.generate("zxdg_exporter_v2", 1); scanner.generate("ext_session_lock_manager_v1", 1); - scanner.generate("ext_image_copy_capture_manager_v1", 1); + scanner.generate("ext_transient_seat_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); - scanner.generate("river_layout_manager_v3", 2); + scanner.generate("river_window_manager_v1", 6); + scanner.generate("river_xkb_bindings_v1", 3); + scanner.generate("river_layer_shell_v1", 1); + scanner.generate("river_touch_gestures_v1", 1); - scanner.generate("zwlr_layer_shell_v1", 4); + scanner.generate("river_input_manager_v1", 2); + scanner.generate("river_libinput_config_v1", 2); + scanner.generate("river_xkb_config_v1", 3); + scanner.generate("zwlr_output_power_manager_v1", 1); + scanner.generate("zwlr_layer_shell_v1", 4); scanner.generate("zwp_virtual_keyboard_manager_v1", 1); const wayland = b.createModule(.{ .root_source_file = scanner.result }); const xkbcommon = b.dependency("xkbcommon", .{}).module("xkbcommon"); - const pixman = b.dependency("pixman", .{}).module("pixman"); - const wlroots = b.dependency("wlroots", .{}).module("wlroots"); - wlroots.addImport("wayland", wayland); - wlroots.addImport("xkbcommon", xkbcommon); - wlroots.addImport("pixman", pixman); - - // We need to ensure the wlroots include path obtained from pkg-config is - // 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.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") }); - - const translate_c: Translator = .init(b.dependency("translate_c", .{}), .{ - .name = "c", - .c_source_file = b.path("river/c.h"), - .target = target, - .optimize = optimize, - }); - translate_c.linkSystemLibrary("libevdev", .{}); - translate_c.linkSystemLibrary("libinput", .{}); - { - const river = b.addExecutable(.{ - .name = "river", + const ponton = b.addExecutable(.{ + .name = "ponton", .root_module = b.createModule(.{ - .root_source_file = b.path("river/main.zig"), + .root_source_file = b.path("ponton/main.zig"), .target = target, .optimize = optimize, .strip = strip, @@ -180,32 +112,13 @@ pub fn build(b: *Build) !void { .use_llvm = use_llvm, .use_lld = use_llvm, }); - river.root_module.addOptions("build_options", options); - - river.root_module.linkSystemLibrary("libevdev", .{}); - river.root_module.linkSystemLibrary("libinput", .{}); - river.root_module.linkSystemLibrary("wayland-server", .{}); - river.root_module.linkSystemLibrary("wlroots-0.20", .{}); - river.root_module.linkSystemLibrary("xkbcommon", .{}); - river.root_module.linkSystemLibrary("pixman-1", .{}); - - river.root_module.addImport("wayland", wayland); - river.root_module.addImport("xkbcommon", xkbcommon); - river.root_module.addImport("pixman", pixman); - river.root_module.addImport("wlroots", wlroots); - river.root_module.addImport("flags", flags); - river.root_module.addImport("globber", globber); - river.root_module.addImport("c", translate_c.mod); - - river.root_module.addCSourceFile(.{ - .file = b.path("river/wlroots_log_wrapper.c"), - .flags = &.{ "-std=c99", "-O2" }, - }); - - river.pie = pie; - river.root_module.omit_frame_pointer = omit_frame_pointer; - - b.installArtifact(river); + ponton.root_module.addImport("wayland", wayland); + ponton.root_module.addImport("xkbcommon", xkbcommon); + ponton.root_module.linkSystemLibrary("wayland-client", .{}); + ponton.root_module.linkSystemLibrary("xkbcommon", .{}); + ponton.pie = pie; + ponton.root_module.omit_frame_pointer = omit_frame_pointer; + b.installArtifact(ponton); } { @@ -221,62 +134,11 @@ pub fn build(b: *Build) !void { .use_llvm = use_llvm, .use_lld = use_llvm, }); - riverctl.root_module.addOptions("build_options", options); - - riverctl.root_module.addImport("flags", flags); - riverctl.root_module.addImport("wayland", wayland); - riverctl.root_module.linkSystemLibrary("wayland-client", .{}); - - riverctl.pie = pie; - riverctl.root_module.omit_frame_pointer = omit_frame_pointer; - b.installArtifact(riverctl); } - { - const rivertile = b.addExecutable(.{ - .name = "rivertile", - .root_module = b.createModule(.{ - .root_source_file = b.path("rivertile/main.zig"), - .target = target, - .optimize = optimize, - .strip = strip, - .link_libc = true, - }), - .use_llvm = use_llvm, - .use_lld = use_llvm, - }); - rivertile.root_module.addOptions("build_options", options); - - rivertile.root_module.addImport("flags", flags); - rivertile.root_module.addImport("wayland", wayland); - rivertile.root_module.linkSystemLibrary("wayland-client", .{}); - - rivertile.pie = pie; - rivertile.root_module.omit_frame_pointer = omit_frame_pointer; - - b.installArtifact(rivertile); - } - - { - const wf = Build.Step.WriteFile.create(b); - const pc_file = wf.add("river-protocols.pc", b.fmt( - \\prefix={s} - \\datadir=${{prefix}}/share - \\pkgdatadir=${{datadir}}/river-protocols - \\ - \\Name: river-protocols - \\URL: https://codeberg.org/river/river - \\Description: protocol files for the river wayland compositor - \\Version: {s} - , .{ b.install_prefix, full_version })); - - b.installFile("protocol/river-layout-v3.xml", "share/river-protocols/river-layout-v3.xml"); - b.getInstallStep().dependOn(&b.addInstallFile(pc_file, "share/pkgconfig/river-protocols.pc").step); - } - if (man_pages) { - inline for (.{ "river", "riverctl", "rivertile" }) |page| { + inline for (.{ "ponton", "riverctl" }) |page| { // Workaround for https://github.com/ziglang/zig/issues/16369 // Even passing a buffer to std.Build.Step.Run appears to be racy and occasionally deadlocks. const scdoc = b.addSystemCommand(&.{ "/bin/sh", "-c", "scdoc < doc/" ++ page ++ ".1.scd" }); @@ -288,29 +150,70 @@ pub fn build(b: *Build) !void { } } - if (bash_completion) { - b.installFile("completions/bash/riverctl", "share/bash-completion/completions/riverctl"); - } - - if (zsh_completion) { - b.installFile("completions/zsh/_riverctl", "share/zsh/site-functions/_riverctl"); - } - - if (fish_completion) { - b.installFile("completions/fish/riverctl.fish", "share/fish/vendor_completions.d/riverctl.fish"); - } - { - const globber_test = b.addTest(.{ + const slotmap_test = b.addTest(.{ .root_module = b.createModule(.{ - .root_source_file = b.path("common/globber.zig"), + .root_source_file = b.path("common/slotmap.zig"), .target = target, .optimize = optimize, }), + .use_llvm = use_llvm, + .use_lld = use_llvm, }); - const run_globber_test = b.addRunArtifact(globber_test); + const run_slotmap_test = b.addRunArtifact(slotmap_test); + const layout_test = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = b.path("ponton/Layout.zig"), + .target = target, + .optimize = optimize, + }), + .use_llvm = use_llvm, + .use_lld = use_llvm, + }); + const run_layout_test = b.addRunArtifact(layout_test); + + const rule_test = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = b.path("ponton/Rule.zig"), + .target = target, + .optimize = optimize, + }), + .use_llvm = use_llvm, + .use_lld = use_llvm, + }); + const run_rule_test = b.addRunArtifact(rule_test); + + const manager_test_module = b.createModule(.{ + .root_source_file = b.path("ponton/WindowManager.zig"), + .target = target, + .optimize = optimize, + }); + manager_test_module.addImport("wayland", wayland); + manager_test_module.addImport("xkbcommon", xkbcommon); + const manager_test = b.addTest(.{ + .root_module = manager_test_module, + .use_llvm = use_llvm, + .use_lld = use_llvm, + }); + const run_manager_test = b.addRunArtifact(manager_test); + + const command_test = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = b.path("ponton/Command.zig"), + .target = target, + .optimize = optimize, + }), + .use_llvm = use_llvm, + .use_lld = use_llvm, + }); + const run_command_test = b.addRunArtifact(command_test); + const test_step = b.step("test", "Run the tests"); - test_step.dependOn(&run_globber_test.step); + test_step.dependOn(&run_slotmap_test.step); + test_step.dependOn(&run_layout_test.step); + test_step.dependOn(&run_rule_test.step); + test_step.dependOn(&run_manager_test.step); + test_step.dependOn(&run_command_test.step); } } blob - cb16f22d8c7de3da87912827a05d53cc3f4f9830 blob + 11e407cebf47a371d87cadd1f3494e648f19a2c0 --- build.zig.zon +++ build.zig.zon @@ -1,11 +1,14 @@ +// SPDX-FileCopyrightText: © 2024 The River Developers +// SPDX-License-Identifier: GPL-3.0-only .{ - .name = .river_classic, + .name = .river, // While a river release is in development, this string should contain // the version in development with the "-dev" suffix. // 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.18-dev", + // commit that gets tagged. + // Directly after the tagged commit, the version should be bumped and the + // "-dev" suffix added. + .version = "0.5.0-dev", .paths = .{""}, .dependencies = .{ .pixman = .{ @@ -17,17 +20,17 @@ .hash = "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX", }, .wlroots = .{ - .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.20.1.tar.gz", - .hash = "wlroots-0.20.1-jmOlcqNVBAB3uB5oqBTzpRlwu-FmMyyZMVAWCe5kmcSt", + .url = "git+https://codeberg.org/ifreund/zig-wlroots#c183af18ae35dd5fb3dde709697c8f84bb1dc7f3", + .hash = "wlroots-0.20.2-dev-jmOlcrJuBABMWPGDKK86BO7zrfmxm17nimqFb0gffZTn", }, .xkbcommon = .{ - .url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.3.0.tar.gz", - .hash = "xkbcommon-0.3.0-VDqIe3K9AQB2fG5ZeRcMC9i7kfrp5m2rWgLrmdNn9azr", + .url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.4.0.tar.gz", + .hash = "xkbcommon-0.4.0-VDqIe0i2AgDRsok2GpMFYJ8SVhQS10_PI2M_CnHXsJJZ", }, .translate_c = .{ - .url = "git+https://codeberg.org/ziglang/translate-c/#7a1a9fdc4ab00835748a6657ecbb835e3d5d45f7", - .hash = "translate_c-0.0.0-Q_BUWvP1BgCjAk6PWv5286tOlvzD9-X-NkuTzh0KxY0Q", + .url = "git+https://codeberg.org/ifreund/translate-c?ref=0.16.x#64570104eeed461dc221e77574cd3f1c5b7eb974", + .hash = "translate_c-0.0.0-Q_BUWlL1BgD7PmL27vTsccyNCOvKb-B65O5Ki1ZKm-ED", }, }, - .fingerprint = 0x3dae7aba2ea52a3b, + .fingerprint = 0xf5e3672b8e8d6efc, } blob - 72f197fba7b43369df6355d671948c9b071d40d9 (mode 644) blob + /dev/null --- common/flags.zig +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-FileCopyrightText: © 2023 Isaac Freund -// SPDX-License-Identifier: 0BSD - -const std = @import("std"); -const mem = std.mem; - -pub const Flag = struct { - name: []const u8, - kind: enum { boolean, arg }, -}; - -pub fn parser(comptime flags: []const Flag) type { - return struct { - pub const Result = struct { - /// Remaining args after the recognized flags - args: []const [:0]const u8, - /// Data obtained from parsed flags - flags: Flags, - - pub const Flags = flags_type: { - const Attributes = std.builtin.Type.StructField.Attributes; - var names: [flags.len][]const u8 = undefined; - var types: [flags.len]type = undefined; - var attrs: [flags.len]Attributes = undefined; - for (flags, &names, &types, &attrs) |flag, *name, *ty, *attr| { - name.* = flag.name; - switch (flag.kind) { - .boolean => { - ty.* = bool; - attr.* = .{ .default_value_ptr = &false }; - }, - .arg => { - ty.* = ?[:0]const u8; - attr.* = .{ .default_value_ptr = &@as(ty.*, null) }; - }, - } - } - break :flags_type @Struct(.auto, null, &names, &types, &attrs); - }; - }; - - pub fn parse(args: []const [:0]const u8) error{MissingFlagArgument}!Result { - var result_flags: Result.Flags = .{}; - - var i: usize = 0; - outer: while (i < args.len) : (i += 1) { - inline for (flags) |flag| { - if (mem.eql(u8, "-" ++ flag.name, args[i])) { - switch (flag.kind) { - .boolean => @field(result_flags, flag.name) = true, - .arg => { - i += 1; - if (i == args.len) { - std.log.err("option '-" ++ flag.name ++ - "' requires an argument but none was provided!", .{}); - return error.MissingFlagArgument; - } - @field(result_flags, flag.name) = args[i]; - }, - } - continue :outer; - } - } - break; - } - - return Result{ - .args = args[i..], - .flags = result_flags, - }; - } - }; -} blob - /dev/null blob + 9494101a766231bcb341b7f765dc9e168f6bbec3 (mode 644) --- /dev/null +++ common/slotmap.zig @@ -0,0 +1,271 @@ +// SPDX-FileCopyrightText: © 2025 Isaac Freund +// SPDX-License-Identifier: 0BSD + +const std = @import("std"); +const assert = std.debug.assert; +const mem = std.mem; + +pub fn SlotMap(comptime T: type) type { + return struct { + const Map = @This(); + + /// This is packed just to make == work. + pub const Key = packed struct { + generation: u32, + index: u32, + }; + + const Slot = struct { + generation: u32, + data: union(enum) { + value: T, + // Index of the next free slot or slots.items.len + 1 if + // this is the last free slot. + next_free: u32, + }, + }; + + slots: std.ArrayListUnmanaged(Slot), + /// Number of values stored in the map. + count: u32, + /// Index of the first free slot or slots.items.len + 1 if there + /// is no free slot. + first_free: u32, + + pub const empty: Map = .{ + .slots = .empty, + .count = 0, + .first_free = 1, + }; + + pub fn deinit(map: *Map, gpa: mem.Allocator) void { + map.slots.deinit(gpa); + } + + pub fn put(map: *Map, gpa: mem.Allocator, value: T) error{OutOfMemory}!Key { + if (map.first_free < map.slots.items.len) { + const index = map.first_free; + const slot = &map.slots.items[index]; + map.first_free = slot.data.next_free; + slot.data = .{ .value = value }; + map.count += 1; + return .{ + .generation = slot.generation, + .index = index, + }; + } + try map.slots.append(gpa, .{ + .generation = 0, + .data = .{ .value = value }, + }); + map.count += 1; + map.first_free += 1; + return .{ + .generation = 0, + .index = @intCast(map.slots.items.len - 1), + }; + } + + pub fn get(map: *Map, key: Key) ?T { + if (map.getSlot(key)) |slot| { + return slot.data.value; + } + return null; + } + + pub fn remove(map: *Map, key: Key) void { + if (map.getSlot(key)) |slot| { + assert(slot.data == .value); + slot.* = .{ + .generation = slot.generation +% 1, + .data = .{ .next_free = map.first_free }, + }; + map.count -= 1; + map.first_free = key.index; + } + } + + fn getSlot(map: *Map, key: Key) ?*Slot { + if (key.index < map.slots.items.len) { + if (key.generation == map.slots.items[key.index].generation) { + return &map.slots.items[key.index]; + } + } + return null; + } + + pub const Iterator = struct { + map: *Map, + index: u32, + + pub fn next(it: *Iterator) ?T { + while (it.index < it.map.slots.items.len) { + defer it.index += 1; + switch (it.map.slots.items[it.index].data) { + .value => |value| return value, + .next_free => {}, + } + } + return null; + } + }; + + /// Removing values from the map during iteration is safe. + /// Adding values to the map during iteration is safe but there is + /// no guarantee whether or not values added during iteration will + /// be seen by the iterator. + pub fn iterator(map: *Map) Iterator { + return .{ .map = map, .index = 0 }; + } + }; +} + +// TODO fuzz test? +test "basic" { + const testing = std.testing; + + var map: SlotMap(u32) = .empty; + defer map.deinit(testing.allocator); + + const five = try map.put(testing.allocator, 5); + try testing.expectEqual(5, map.get(five)); + try testing.expectEqual(5, map.get(five)); + + map.remove(five); + try testing.expectEqual(null, map.get(five)); + + map.remove(five); + try testing.expectEqual(null, map.get(five)); + + const six = try map.put(testing.allocator, 6); + try testing.expectEqual(6, map.get(six)); + try testing.expectEqual(null, map.get(five)); + try testing.expectEqual(6, map.get(six)); + + map.remove(five); + try testing.expectEqual(null, map.get(five)); + try testing.expectEqual(6, map.get(six)); + + const seven = try map.put(testing.allocator, 7); + const eight = try map.put(testing.allocator, 8); + const nine = try map.put(testing.allocator, 9); + try testing.expectEqual(null, map.get(five)); + try testing.expectEqual(6, map.get(six)); + try testing.expectEqual(7, map.get(seven)); + try testing.expectEqual(8, map.get(eight)); + try testing.expectEqual(9, map.get(nine)); + + map.remove(five); + map.remove(eight); + try testing.expectEqual(null, map.get(five)); + try testing.expectEqual(6, map.get(six)); + try testing.expectEqual(7, map.get(seven)); + try testing.expectEqual(null, map.get(eight)); + try testing.expectEqual(9, map.get(nine)); + + try testing.expectEqual(null, map.get(five)); + try testing.expectEqual(6, map.get(six)); + try testing.expectEqual(7, map.get(seven)); + try testing.expectEqual(null, map.get(eight)); + try testing.expectEqual(9, map.get(nine)); +} + +test "iteration" { + const testing = std.testing; + + var map: SlotMap(u64) = .empty; + defer map.deinit(testing.allocator); + + { + var it = map.iterator(); + try testing.expectEqual(null, it.next()); + } + + const five = try map.put(testing.allocator, 5); + const six = try map.put(testing.allocator, 6); + const seven = try map.put(testing.allocator, 7); + const eight = try map.put(testing.allocator, 8); + const nine = try map.put(testing.allocator, 9); + + try testing.expectEqual(5, map.get(five)); + try testing.expectEqual(6, map.get(six)); + try testing.expectEqual(7, map.get(seven)); + try testing.expectEqual(8, map.get(eight)); + try testing.expectEqual(9, map.get(nine)); + try expectIterate(&.{ 5, 6, 7, 8, 9 }, &map); + + map.remove(seven); + try testing.expectEqual(5, map.get(five)); + try testing.expectEqual(6, map.get(six)); + try testing.expectEqual(null, map.get(seven)); + try testing.expectEqual(8, map.get(eight)); + try testing.expectEqual(9, map.get(nine)); + try expectIterate(&.{ 5, 6, 8, 9 }, &map); + + const ten = try map.put(testing.allocator, 10); + try testing.expectEqual(5, map.get(five)); + try testing.expectEqual(6, map.get(six)); + try testing.expectEqual(null, map.get(seven)); + try testing.expectEqual(8, map.get(eight)); + try testing.expectEqual(9, map.get(nine)); + try testing.expectEqual(10, map.get(ten)); + try expectIterate(&.{ 5, 6, 10, 8, 9 }, &map); + + map.remove(five); + map.remove(nine); + map.remove(six); + try testing.expectEqual(null, map.get(five)); + try testing.expectEqual(null, map.get(six)); + try testing.expectEqual(null, map.get(seven)); + try testing.expectEqual(8, map.get(eight)); + try testing.expectEqual(null, map.get(nine)); + try testing.expectEqual(10, map.get(ten)); + try expectIterate(&.{ 10, 8 }, &map); +} + +fn expectIterate(expected: []const u64, map: *SlotMap(u64)) !void { + var it = map.iterator(); + var i: u32 = 0; + while (it.next()) |value| : (i += 1) { + try std.testing.expect(i < expected.len); + try std.testing.expectEqual(expected[i], value); + } + try std.testing.expectEqual(i, map.count); + try std.testing.expectEqual(i, expected.len); +} + +test "remove during iteration" { + const testing = std.testing; + + var map: SlotMap(u64) = .empty; + defer map.deinit(testing.allocator); + + const five = try map.put(testing.allocator, 5); + const six = try map.put(testing.allocator, 6); + const seven = try map.put(testing.allocator, 7); + const eight = try map.put(testing.allocator, 8); + const nine = try map.put(testing.allocator, 9); + + try testing.expectEqual(5, map.get(five)); + try testing.expectEqual(6, map.get(six)); + try testing.expectEqual(7, map.get(seven)); + try testing.expectEqual(8, map.get(eight)); + try testing.expectEqual(9, map.get(nine)); + try expectIterate(&.{ 5, 6, 7, 8, 9 }, &map); + + var it = map.iterator(); + map.remove(five); + + try testing.expectEqual(6, it.next()); + try testing.expectEqual(null, map.get(five)); + try testing.expectEqual(6, map.get(six)); + try testing.expectEqual(7, map.get(seven)); + try testing.expectEqual(8, map.get(eight)); + try testing.expectEqual(9, map.get(nine)); + + try testing.expectEqual(7, it.next()); + map.remove(seven); + map.remove(nine); + map.remove(eight); + try testing.expectEqual(null, it.next()); +} blob - c92e99cc47bfac850f3c049bb89d0447ecd24690 (mode 644) blob + /dev/null --- common/globber.zig +++ /dev/null @@ -1,223 +0,0 @@ -// Basic prefix, suffix, and substring glob matching. -// -// Released under the Zero Clause BSD (0BSD) license: -// -// Copyright 2023 Isaac Freund -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -const std = @import("std"); -const mem = std.mem; - -/// Validate a glob, returning error.InvalidGlob if it is empty, "**" or has a -/// '*' at any position other than the first and/or last byte. -pub fn validate(glob: []const u8) error{InvalidGlob}!void { - switch (glob.len) { - 0 => return error.InvalidGlob, - 1 => {}, - 2 => if (glob[0] == '*' and glob[1] == '*') return error.InvalidGlob, - else => if (mem.indexOfScalar(u8, glob[1 .. glob.len - 1], '*') != null) { - return error.InvalidGlob; - }, - } -} - -test validate { - const testing = std.testing; - - try validate("*"); - try validate("a"); - try validate("*a"); - try validate("a*"); - try validate("*a*"); - try validate("ab"); - try validate("*ab"); - try validate("ab*"); - try validate("*ab*"); - try validate("abc"); - try validate("*abc"); - try validate("abc*"); - try validate("*abc*"); - - try testing.expectError(error.InvalidGlob, validate("")); - try testing.expectError(error.InvalidGlob, validate("**")); - try testing.expectError(error.InvalidGlob, validate("***")); - try testing.expectError(error.InvalidGlob, validate("a*c")); - try testing.expectError(error.InvalidGlob, validate("ab*c*")); - try testing.expectError(error.InvalidGlob, validate("*ab*c")); - try testing.expectError(error.InvalidGlob, validate("ab*c")); - try testing.expectError(error.InvalidGlob, validate("a*bc*")); - try testing.expectError(error.InvalidGlob, validate("**a")); - try testing.expectError(error.InvalidGlob, validate("abc**")); -} - -/// Return true if s is matched by glob. -/// Asserts that the glob is valid, see `validate()`. -pub fn match(s: []const u8, glob: []const u8) bool { - if (std.debug.runtime_safety) { - validate(glob) catch unreachable; - } - - if (glob.len == 1) { - return glob[0] == '*' or mem.eql(u8, s, glob); - } - - const suffix_match = glob[0] == '*'; - const prefix_match = glob[glob.len - 1] == '*'; - - if (suffix_match and prefix_match) { - return mem.indexOf(u8, s, glob[1 .. glob.len - 1]) != null; - } else if (suffix_match) { - return mem.endsWith(u8, s, glob[1..]); - } else if (prefix_match) { - return mem.startsWith(u8, s, glob[0 .. glob.len - 1]); - } else { - return mem.eql(u8, s, glob); - } -} - -test match { - const testing = std.testing; - - try testing.expect(match("", "*")); - - try testing.expect(match("a", "*")); - try testing.expect(match("a", "*a*")); - try testing.expect(match("a", "a*")); - try testing.expect(match("a", "*a")); - try testing.expect(match("a", "a")); - - try testing.expect(!match("a", "b")); - try testing.expect(!match("a", "*b*")); - try testing.expect(!match("a", "b*")); - try testing.expect(!match("a", "*b")); - - try testing.expect(match("ab", "*")); - try testing.expect(match("ab", "*a*")); - try testing.expect(match("ab", "*b*")); - try testing.expect(match("ab", "a*")); - try testing.expect(match("ab", "*b")); - try testing.expect(match("ab", "*ab*")); - try testing.expect(match("ab", "ab*")); - try testing.expect(match("ab", "*ab")); - try testing.expect(match("ab", "ab")); - - try testing.expect(!match("ab", "b*")); - try testing.expect(!match("ab", "*a")); - try testing.expect(!match("ab", "*c*")); - try testing.expect(!match("ab", "c*")); - try testing.expect(!match("ab", "*c")); - try testing.expect(!match("ab", "ac")); - try testing.expect(!match("ab", "*ac*")); - try testing.expect(!match("ab", "ac*")); - try testing.expect(!match("ab", "*ac")); - - try testing.expect(match("abc", "*")); - try testing.expect(match("abc", "*a*")); - try testing.expect(match("abc", "*b*")); - try testing.expect(match("abc", "*c*")); - try testing.expect(match("abc", "a*")); - try testing.expect(match("abc", "*c")); - try testing.expect(match("abc", "*ab*")); - try testing.expect(match("abc", "ab*")); - try testing.expect(match("abc", "*bc*")); - try testing.expect(match("abc", "*bc")); - try testing.expect(match("abc", "*abc*")); - try testing.expect(match("abc", "abc*")); - try testing.expect(match("abc", "*abc")); - try testing.expect(match("abc", "abc")); - - try testing.expect(!match("abc", "*a")); - try testing.expect(!match("abc", "*b")); - try testing.expect(!match("abc", "b*")); - try testing.expect(!match("abc", "c*")); - try testing.expect(!match("abc", "*ab")); - try testing.expect(!match("abc", "bc*")); - try testing.expect(!match("abc", "*d*")); - try testing.expect(!match("abc", "d*")); - try testing.expect(!match("abc", "*d")); -} - -/// Returns .lt if a is less general than b. -/// Returns .gt if a is more general than b. -/// Returns .eq if a and b are equally general. -/// Both a and b must be valid globs, see `validate()`. -pub fn order(a: []const u8, b: []const u8) std.math.Order { - if (std.debug.runtime_safety) { - validate(a) catch unreachable; - validate(b) catch unreachable; - } - - if (mem.eql(u8, a, "*") and mem.eql(u8, b, "*")) { - return .eq; - } else if (mem.eql(u8, a, "*")) { - return .gt; - } else if (mem.eql(u8, b, "*")) { - return .lt; - } - - const count_a = @as(u2, @intFromBool(a[0] == '*')) + @intFromBool(a[a.len - 1] == '*'); - const count_b = @as(u2, @intFromBool(b[0] == '*')) + @intFromBool(b[b.len - 1] == '*'); - - if (count_a == 0 and count_b == 0) { - return .eq; - } else if (count_a == count_b) { - // This may look backwards since e.g. "c*" is more general than "cc*" - return std.math.order(b.len, a.len); - } else { - return std.math.order(count_a, count_b); - } -} - -test order { - const testing = std.testing; - const Order = std.math.Order; - - try testing.expectEqual(Order.eq, order("*", "*")); - try testing.expectEqual(Order.eq, order("*a*", "*b*")); - try testing.expectEqual(Order.eq, order("a*", "*b")); - try testing.expectEqual(Order.eq, order("*a", "*b")); - try testing.expectEqual(Order.eq, order("*a", "b*")); - try testing.expectEqual(Order.eq, order("a*", "b*")); - - const descending = [_][]const u8{ - "*", - "*a*", - "*b*", - "*a*", - "*ab*", - "*bab*", - "*a", - "b*", - "*b", - "*a", - "a", - "bababab", - "b", - "a", - }; - - for (descending, 0..) |a, i| { - for (descending[i..]) |b| { - try testing.expect(order(a, b) != .lt); - } - } - - var ascending = descending; - mem.reverse([]const u8, &ascending); - - for (ascending, 0..) |a, i| { - for (ascending[i..]) |b| { - try testing.expect(order(a, b) != .gt); - } - } -} blob - 891f13c7809e3591eaf093916f106251c0469e8a (mode 644) blob + /dev/null --- completions/bash/riverctl +++ /dev/null @@ -1,147 +0,0 @@ -function __riverctl_completion () -{ - local rule_actions="float no-float ssd csd tags output position dimensions fullscreen no-fullscreen" - if [ "${COMP_CWORD}" -eq 1 ] - then - OPTS=" \ - keyboard-layout \ - keyboard-layout-file \ - close \ - exit \ - focus-output \ - focus-view \ - input \ - list-inputs \ - list-input-configs \ - move \ - resize \ - rule-add \ - rule-del \ - list-rules \ - snap \ - send-to-output \ - spawn \ - swap \ - toggle-float \ - toggle-fullscreen \ - zoom \ - default-layout \ - output-layout \ - send-layout-cmd \ - set-focused-tags \ - focus-previous-tags \ - send-to-previous-tags \ - set-view-tags \ - toggle-focused-tags \ - toggle-view-tags \ - spawn-tagmask \ - declare-mode \ - enter-mode \ - map \ - map-pointer \ - map-switch \ - unmap \ - unmap-pointer \ - unmap-switch \ - default-attach-mode \ - output-attach-mode \ - background-color \ - border-color-focused \ - border-color-unfocused \ - border-color-urgent \ - border-width \ - focus-follows-cursor \ - hide-cursor \ - set-repeat \ - set-cursor-warp \ - xcursor-theme" - COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[1]}")) - elif [ "${COMP_CWORD}" -eq 2 ] - then - case "${COMP_WORDS[1]}" in - "focus-output"|"send-to-output") OPTS="next previous up right down left" ;; - "focus-view"|"swap") OPTS="next previous up down left right" ;; - "input") OPTS="$(riverctl list-inputs | sed '/configured:/d')" ;; - "move"|"snap") OPTS="up down left right" ;; - "resize") OPTS="horizontal vertical" ;; - "rule-add"|"rule-del") OPTS="-app-id -title $rule_actions" ;; - "list-rules") OPTS="float ssd tags output position dimensions fullscreen" ;; - "map") OPTS="-release -repeat -layout" ;; - "unmap") OPTS="-release" ;; - "default-attach-mode"|"output-attach-mode") OPTS="top bottom above below after" ;; - "focus-follows-cursor") OPTS="disabled normal always" ;; - "set-cursor-warp") OPTS="disabled on-output-change on-focus-change" ;; - "hide-cursor") OPTS="timeout when-typing" ;; - *) return ;; - esac - COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[2]}")) - elif [ "${COMP_CWORD}" -eq 3 ] - then - if [ "${COMP_WORDS[1]}" == "input" ] - then - OPTS="events \ - accel-profile \ - pointer-accel \ - click-method \ - drag \ - drag-lock \ - disable-while-typing \ - disable-while-trackpointing \ - middle-emulation \ - natural-scroll \ - scroll-factor \ - left-handed \ - tap \ - tap-button-map \ - scroll-method \ - scroll-button \ - scroll-button-lock \ - map-to-output" - COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[3]}")) - elif [ "${COMP_WORDS[1]}" == "hide-cursor" ] - then - case "${COMP_WORDS[2]}" in - "when-typing") OPTS="enabled disabled" ;; - *) return ;; - esac - COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[3]}")) - fi - elif [ "${COMP_CWORD}" -eq 4 ] - then - if [ "${COMP_WORDS[1]}" == "input" ] - then - case "${COMP_WORDS[3]}" in - "events") OPTS="enabled disabled disabled-on-external-mouse" ;; - "accel-profile") OPTS="none flat adaptive" ;; - "click-method") OPTS="none button-areas clickfinger" ;; - "drag"|"drag-lock"|"disable-while-typing"|"middle-emulation"|"left-handed"|"tap"|"scroll-button-lock") OPTS="enabled disabled" ;; - "tap-button-map") OPTS="left-right-middle left-middle-right" ;; - "scroll-method") OPTS="none two-finger edge button" ;; - *) return ;; - esac - COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[4]}")) - elif [ "${COMP_WORDS[1]:0:5}" == "rule-" ] - then - case "${COMP_WORDS[2]}" in - "-app-id") OPTS="-title $rule_actions" ;; - "-title") OPTS="-app-id $rule_actions" ;; - *) return ;; - esac - COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[4]}")) - fi - elif [ "${COMP_CWORD}" -eq 6 ] - then - if [ "${COMP_WORDS[1]:0:5}" == "rule-" ] - then - case "${COMP_WORDS[4]}" in - "-app-id"|"-title") OPTS="$rule_actions" ;; - *) return ;; - esac - COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[6]}")) - fi - else - return - fi -} - -complete -F __riverctl_completion riverctl blob - 70aceb7399039c5ed6396ccad8090f85d094623a (mode 644) blob + /dev/null --- completions/fish/riverctl.fish +++ /dev/null @@ -1,136 +0,0 @@ -function __riverctl_list_input_devices - riverctl list-inputs | sed '/configured:/d' -end - -function __fish_riverctl_complete_arg - set -l cmd (commandline -opc) - if test (count $cmd) -eq $argv[1] - return 0 - end - return 1 -end - -# Remove any previous completion, such as options extracted from the manpage -complete -c riverctl -e -# Do not suggest files -complete -c riverctl -f -# Options -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -o 'h' -d 'Print a help message and exit' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -o 'version' -d 'Print the version number and exit' - -# Actions -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'close' -d 'Close the focued view' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'exit' -d 'Exit the compositor, terminating the Wayland session' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'focus-output' -d 'Focus the next or previous output' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'focus-view' -d 'Focus the next or previous view in the stack' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'input' -d 'Create a configuration rule for an input device' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'list-inputs' -d 'List all input devices' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'list-input-configs' -d 'List all input configurations' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'move' -d 'Move the focused view in the specified direction' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'resize' -d 'Resize the focused view along the given axis' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'snap' -d 'Snap the focused view to the specified screen edge' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'send-to-output' -d 'Send the focused view to the next/previous output' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'spawn' -d 'Run shell_command using /bin/sh -c' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'swap' -d 'Swap the focused view with the next/previous visible non-floating view' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'toggle-float' -d 'Toggle the floating state of the focused view' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'toggle-fullscreen' -d 'Toggle the fullscreen state of the focused view' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'zoom' -d 'Bump the focused view to the top of the layout stack' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'default-layout' -d 'Set the layout namespace to be used by all outputs by default' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'output-layout' -d 'Set the layout namespace of currently focused output' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'send-layout-cmd' -d 'Send command to the layout generator on the currently focused output with the given namespace' -# Tag managements -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'set-focused-tags' -d 'Show views with tags corresponding to the set bits of tags' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'set-view-tags' -d 'Assign the currently focused view the tags corresponding to the set bits of tags' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'toggle-focused-tags' -d 'Toggle visibility of views with tags corresponding to the set bits of tags' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'toggle-view-tags' -d 'Toggle the tags of the currently focused view' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'spawn-tagmask' -d 'Set a tagmask to filter the tags assigned to newly spawned views on the focused output' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'focus-previous-tags' -d 'Sets tags to their previous value on the focused output' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'send-to-previous-tags' -d 'Assign the currently focused view the previous tags of the focused output' -# Mappings -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'declare-mode' -d 'Create a new mode' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'enter-mode' -d 'Switch to given mode if it exists' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'map' -d 'Run command when key is pressed while modifiers are held down and in the specified mode' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'map-pointer' -d 'Move or resize views or run command when button and modifers are held down while in the specified mode' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'map-switch ' -d 'Run command when river receives a switch event in the specified mode' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'unmap' -d 'Remove the mapping defined by the arguments' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'unmap-pointer' -d 'Remove the pointer mapping defined by the arguments' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'unmap-switch' -d 'Remove the switch mapping defined by the arguments' -# Rules -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'rule-add' -d 'Apply an action to matching views' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'rule-del' -d 'Delete a rule added with rule-add' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'list-rules' -d 'Print rules in a given list' -# Configuration -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'default-attach-mode' -d 'Set the attach mode to be used by all outputs by default' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'output-attach-mode' -d 'Set the attach mode of the currently focused output' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'background-color' -d 'Set the background color' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'border-color-focused' -d 'Set the border color of focused views' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'border-color-unfocused' -d 'Set the border color of unfocused views' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'border-color-urgent' -d 'Set the border color of urgent views' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'border-width' -d 'Set the border width to pixels' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'focus-follows-cursor' -d 'Configure the focus behavior when moving cursor' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'hide-cursor' -d 'Hide cursor when typing or after inactivity' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'set-repeat' -d 'Set the keyboard repeat rate and repeat delay' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'set-cursor-warp' -d 'Set the cursor warp mode' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'xcursor-theme' -d 'Set the xcursor theme' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-layout' -d 'Set the keyboard layout' -complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-layout-file' -d 'Set the keyboard layout from a file.' - -# Subcommands -complete -c riverctl -n '__fish_seen_subcommand_from focus-output send-to-output' -n '__fish_riverctl_complete_arg 2' -a 'next previous up right down left' -complete -c riverctl -n '__fish_seen_subcommand_from focus-view swap' -n '__fish_riverctl_complete_arg 2' -a 'next previous up down left right' -complete -c riverctl -n '__fish_seen_subcommand_from move snap' -n '__fish_riverctl_complete_arg 2' -a 'up down left right' -complete -c riverctl -n '__fish_seen_subcommand_from resize' -n '__fish_riverctl_complete_arg 2' -a 'horizontal vertical' -complete -c riverctl -n '__fish_seen_subcommand_from map' -o 'release' -o 'repeat' -o 'layout' -complete -c riverctl -n '__fish_seen_subcommand_from unmap' -n '__fish_riverctl_complete_arg 2' -o 'release' -complete -c riverctl -n '__fish_seen_subcommand_from default-attach-mode' -n '__fish_riverctl_complete_arg 2' -a 'top bottom above below after' -complete -c riverctl -n '__fish_seen_subcommand_from output-attach-mode' -n '__fish_riverctl_complete_arg 2' -a 'top bottom above below after' -complete -c riverctl -n '__fish_seen_subcommand_from focus-follows-cursor' -n '__fish_riverctl_complete_arg 2' -a 'disabled normal always' -complete -c riverctl -n '__fish_seen_subcommand_from set-cursor-warp' -n '__fish_riverctl_complete_arg 2' -a 'disabled on-output-change on-focus-change' -complete -c riverctl -n '__fish_seen_subcommand_from list-rules' -n '__fish_riverctl_complete_arg 2' -a 'float ssd tags output position dimensions fullscreen' - -# Options and subcommands for 'rule-add' and 'rule-del' -set -l rule_actions float no-float ssd csd tags output position dimensions fullscreen no-fullscreen -complete -c riverctl -n '__fish_seen_subcommand_from rule-add rule-del' -n "not __fish_seen_subcommand_from $rule_actions" -n 'not __fish_seen_argument -o app-id' -o 'app-id' -r -complete -c riverctl -n '__fish_seen_subcommand_from rule-add rule-del' -n "not __fish_seen_subcommand_from $rule_actions" -n 'not __fish_seen_argument -o title' -o 'title' -r -complete -c riverctl -n '__fish_seen_subcommand_from rule-add rule-del' -n "not __fish_seen_subcommand_from $rule_actions" -n 'test (math (count (commandline -opc)) % 2) -eq 0' -a "$rule_actions" -set -e rule_actions - -# Subcommands for 'input' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 2' -a "(__riverctl_list_input_devices)" - -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'events' -d 'Configure whether the input device\'s events will be used' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'accel-profile' -d 'Set the pointer acceleration profile' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'pointer-accel' -d 'Set the pointer acceleration factor' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'click-method' -d 'Set the click method' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'drag' -d 'Enable or disable the tap-and-drag functionality' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'drag-lock' -d 'Enable or disable the drag lock functionality' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'disable-while-typing' -d 'Enable or disable the disable-while-typing functionality' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'disable-while-trackpointing' -d 'Enable or disable the disable-while-trackpointing functionality' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'middle-emulation' -d 'Enable or disable the middle-emulation functionality' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'natural-scroll' -d 'Enable or disable the natural-scroll functionality' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'scroll-factor' -d 'Set the scroll factor' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'left-handed' -d 'Enable or disable the left handed mode' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'tap' -d 'Enable or disable the tap functionality' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'tap-button-map' -d 'Configure the button mapping for tapping' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'scroll-method' -d 'Set the scroll method' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'scroll-button' -d 'Set the scroll button' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'scroll-button-lock' -d 'Enable or disable the scroll button lock functionality' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 3' -a 'map-to-output' -d 'Map to a given output' - -# Subcommands for the subcommands of 'input' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 4; and __fish_seen_subcommand_from drag drag-lock disable-while-typing disable-while-trackpointing middle-emulation natural-scroll left-handed tap scroll-button-lock' -a 'enabled disabled' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 4; and __fish_seen_subcommand_from events' -a 'enabled disabled disabled-on-external-mouse' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 4; and __fish_seen_subcommand_from accel-profile' -a 'none flat adaptive' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 4; and __fish_seen_subcommand_from click-method' -a 'none button-areas clickfinger' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 4; and __fish_seen_subcommand_from tap-button-map' -a 'left-right-middle left-middle-right' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 4; and __fish_seen_subcommand_from scroll-method' -a 'none' -d 'No scrolling' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 4; and __fish_seen_subcommand_from scroll-method' -a 'two-finger' -d 'Scroll by swiping with two fingers simultaneously' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 4; and __fish_seen_subcommand_from scroll-method' -a 'edge' -d 'Scroll by swiping along the edge' -complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 4; and __fish_seen_subcommand_from scroll-method' -a 'button' -d 'Scroll with pointer movement while holding down a button' - -# Subcommands for 'hide-cursor' -complete -c riverctl -n '__fish_seen_subcommand_from hide-cursor; and __fish_riverctl_complete_arg 2' -a 'timeout' -d 'Hide cursor if it wasn\'t moved in the last X millisecond, until it is moved again' -complete -c riverctl -n '__fish_seen_subcommand_from hide-cursor; and __fish_riverctl_complete_arg 2' -a 'when-typing' -d 'Enable or disable whether the cursor should be hidden when pressing any non-modifier key' - -# Subcommands for the subcommands of 'hide-cursor' -complete -c riverctl -n '__fish_seen_subcommand_from hide-cursor; and __fish_riverctl_complete_arg 3; and __fish_seen_subcommand_from when-typing' -a 'enabled disabled' blob - 8bfd8f0eb92ec52e1c4283e1df8f90e6b3f5e1c6 (mode 644) blob + /dev/null --- completions/zsh/_riverctl +++ /dev/null @@ -1,214 +0,0 @@ -#compdef riverctl -# -# Completion script for riverctl, part of river - -# This is the list of all riverctl first argument, i.e `riverctl `. -# If a command doesn't need completion for subcommands then you just need -# to add a line to this list. -# Format is ':' -_riverctl_commands() -{ - local -a riverctl_commands - - riverctl_commands=( - # Actions - 'close:Close the focused view' - 'exit:Exit the compositor, terminating the Wayland session' - 'focus-output:Focus the next or previous output' - 'focus-view:Focus the next or previous view in the stack' - 'move:Move the focused view in the specified direction' - 'resize:Resize the focused view along the given axis' - 'snap:Snap the focused view to the specified screen edge' - 'send-to-output:Send the focused view to the next or the previous output' - 'spawn:Run shell_command using /bin/sh -c' - 'swap:Swap the focused view with the next/previous visible non-floating view' - 'toggle-float:Toggle the floating state of the focused view' - 'toggle-fullscreen:Toggle the fullscreen state of the focused view' - 'zoom:Bump the focused view to the top of the layout stack' - 'default-layout:Set the layout namespace to be used by all outputs by default.' - 'output-layout:Set the layout namespace of currently focused output.' - 'send-layout-cmd:Send command to the layout generator on the currently focused output with matching namespace' - # Tag management - 'set-focused-tags:Show views with tags corresponding to the set bits of tags' - 'set-view-tags:Assign the currently focused view the tags corresponding to the set bits of tags' - 'toggle-focused-tags:Toggle visibility of views with tags corresponding to the set bits of tags' - 'toggle-view-tags:Toggle the tags of the currently focused view' - 'spawn-tagmask:Set a tagmask to filter the tags assigned to newly spawned views on the focused output' - 'focus-previous-tags:Sets tags to their previous value on the focused output' - 'send-to-previous-tags:Assign the currently focused view the previous tags of the focused output' - # Mappings - 'declare-mode:Create a new mode' - 'enter-mode:Switch to given mode if it exists' - 'map:Run command when key is pressed while modifiers are held down and in the specified mode' - 'map-pointer:Move or resize views or run command when button and modifiers are held down while in the specified mode' - 'map-switch:Run command when river receives a switch event in the specified mode' - 'unmap:Remove the mapping defined by the arguments' - 'unmap-pointer:Remove the pointer mapping defined by the arguments' - 'unmap-switch:Remove the switch mapping defined by the arguments' - # Rules - 'rule-add:Apply an action to matching views' - 'rule-del:Delete a rule added with rule-add' - 'list-rules:Print rules in a given list' - # Configuration - 'default-attach-mode:Configure where new views should attach to the view stack' - 'output-attach-mode:Configure where new views should attach to the view stack of the currently focuesed output' - 'background-color:Set the background color' - 'border-color-focused:Set the border color of focused views' - 'border-color-unfocused:Set the border color of unfocused views' - 'border-color-urgent:Set the border color of urgent views' - 'border-width:Set the border width to pixels' - 'focus-follows-cursor:Configure the focus behavior when moving cursor' - 'hide-cursor:Hide cursor when typing or after inactivity' - 'set-repeat:Set the keyboard repeat rate and repeat delay' - 'set-cursor-warp:Set the cursor warp mode.' - 'xcursor-theme:Set the xcursor theme' - 'keyboard-layout:Set the keyboard layout' - 'keyboard-layout-file:Set the keyboard layout from a file' - # Input - 'input:Configure input devices' - 'list-inputs:List all input devices' - 'list-input-configs:List all input configurations' - ) - - _describe -t command 'command' riverctl_commands -} - -# This is the function called for the completion. Commands added in the -# riverctl_commands above are generated in the `commands` case, there is -# nothing more to do for this. If a command has a subcommand then a new case -# need to be added in `args`. -# If this is a simple subcommand with simple multi choice, the easier way to -# do it is: -# ) _alternative 'arguments:args:(commands' \ - '*:: :->args' - - case "$state" in - commands) _alternative 'common-commands:common:_riverctl_commands' ;; - args) - case "$line[1]" in - focus-output) _alternative 'arguments:args:(next previous up right down left)' ;; - focus-view) _alternative 'arguments:args:(next previous up down left right)' ;; - keyboard-layout) _arguments '*::optional:(-rules -model -variant -options)' ;; - input) - _arguments '1: :->name' '2: :->commands' ':: :->args' - - case "$state" in - name) _alternative "arguments:args:($(riverctl list-inputs | grep -e '^[^[:space:]]'))" ;; - commands) - local -a input_subcommands - input_subcommands=( - 'events:Configure whether the input devices events will be used by river' - 'accel-profile:Set the pointer acceleration profile' - 'pointer-accel:Set the pointer acceleration factor' - 'click-method:Set the click method' - 'drag:Enable or disable the tap-and-drag functionality' - 'drag-lock:Enable or disable the drag lock functionality' - 'disable-while-typing:Enable or disable the disable-while-typing functionality' - 'disable-while-trackpointing:Enable or disable the disable-while-trackpointing functionality' - 'middle-emulation:Enable or disable the middle click emulation functionality' - 'natural-scroll:Enable or disable the natural scroll functionality' - 'scroll-factor:Set the scroll factor' - 'left-handed:Enable or disable the left handed mode' - 'tap:Enable or disable the tap functionality' - 'tap-button-map:Configure the button mapping for tapping' - 'scroll-method:Set the scroll method' - 'scroll-button:Set the scroll button' - 'scroll-button-lock:Enable or disable the scroll button lock functionality' - 'map-to-output:Map to a given output' - ) - - _describe -t command 'command' input_subcommands - ;; - args) - case "$line[2]" in - events) _alternative 'input-cmds:args:(enabled disabled disabled-on-external-mouse)' ;; - accel-profile) _alternative 'input-cmds:args:(none flat adaptive)' ;; - click-method) _alternative 'input-cmds:args:(none button-areas clickfinger)' ;; - drag) _alternative 'input-cmds:args:(enabled disabled)' ;; - drag-lock) _alternative 'input-cmds:args:(enabled disabled)' ;; - disable-while-typing) _alternative 'input-cmds:args:(enabled disabled)' ;; - disable-while-trackpointing) _alternative 'input-cmds:args:(enabled disabled)' ;; - middle-emulation) _alternative 'input-cmds:args:(enabled disabled)' ;; - natural-scroll) _alternative 'input-cmds:args:(enabled disabled)' ;; - left-handed) _alternative 'input-cmds:args:(enabled disabled)' ;; - tap) _alternative 'input-cmds:args:(enabled disabled)' ;; - scroll-button-lock) _alternative 'input-cmds:args:(enabled disabled)' ;; - tap-button-map) _alternative 'input-cmds:args:(left-right-middle left-middle-right)' ;; - scroll-method) _alternative 'input-cmds:args:(none two-finger edge button)' ;; - *) return 0 ;; - esac - ;; - esac - ;; - move) _alternative 'arguments:args:(up down left right)' ;; - resize) _alternative 'arguments:args:(horizontal vertical)' ;; - snap) _alternative 'arguments:args:(up down left right)' ;; - send-to-output) _arguments \ - '1::optional:(-current-tag)' \ - '::args:(next previous up right down left)' - ;; - swap) _alternative 'arguments:args:(next previous up down left right)' ;; - map) _alternative 'arguments:optional:(-release -repeat -layout)' ;; - unmap) _alternative 'arguments:optional:(-release)' ;; - map-switch | unmap-switch) - _arguments '1: :' '2:args:(lid tablet)' '3:args:->args' - case "$state" in - args) - case "$line[2]" in - lid) _alternative 'arguments:args:(close open)' ;; - tablet) _alternative 'arguments:args:(on off)' ;; - esac - ;; - esac - ;; - default-attach-mode) _alternative 'arguments:args:(top bottom above below after)' ;; - output-attach-mode) _alternative 'arguments:args:(top bottom above below after)' ;; - focus-follows-cursor) _alternative 'arguments:args:(disabled normal always)' ;; - set-cursor-warp) _alternative 'arguments:args:(disabled on-output-change on-focus-change)' ;; - hide-cursor) - _arguments '1: :->commands' ':: :->args' - - case "$state" in - commands) - local -a hide_cursor_subcommands - hide_cursor_subcommands=( - "timeout:Hide cursor if it wasn\'t moved in the last X millisecond, until it is moved again" - 'when-typing:Enable or disable whether the cursor should be hidden when pressing any non-modifier key' - ) - - _describe -t command 'command' hide_cursor_subcommands - ;; - args) - case "$line[1]" in - when-typing) _alternative 'hide-cursor-cmds:args:(enabled disabled)' ;; - *) return 0 ;; - esac - ;; - esac - ;; - rule-add | rule-del) - # This is not perfect as it only complete if there is - # either '-app-id' or '-title'. - # The empty action(2) mean that we need an argument - # but we don't generate anything for it. - # In case of a new rule added in river, we just need - # to add it to the third option between '()', - # i.e (float no-float ) - _arguments '1: :(-app-id -title)' '2: : ' ':: :(float no-float ssd csd tags output position dimensions fullscreen no-fullscreen)' - ;; - list-rules) _alternative 'arguments:args:(float ssd tags output position dimensions fullscreen)' ;; - *) return 0 ;; - esac - ;; - esac -} - -_riverctl "$@" blob - 0e1fdfb344af644cf3049eeec987336cac404fed (mode 644) blob + /dev/null --- contrib/layout.c +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Tiled layout for river, implemented in understandable, simple, commented code. - * Reading this code should help you get a basic understanding of how to use - * river-layout to create a basic layout generator. - * - * Q: Wow, this is a lot of code just for a layout! - * A: No, it really is not. Most of the code here is just generic Wayland client - * boilerplate. The actual layout part is pretty small. - * - * Q: Can I use this to port dwm layouts to river? - * A: Yes you can! You just need to replace the logic in layout_handle_layout_demand(). - * You don't even need to fully understand the protocol if all you want to - * do is just port some layouts. - * - * Q: I have no idea how any of this works. - * A: If all you want to do is create layouts, you do not need to understand - * the Wayland parts of the code. If you still want to understand it and are - * familiar with how Wayland clients work, read the protocol. If you are new - * to writing Wayland client code, you can read https://wayland-book.com, - * then read the protocol. - * - * Q: How do I build this? - * A: To build, you need to generate the header and code of the layout protocol - * extension and link against them. This is achieved with the following - * commands (You may want to setup a build system). - * - * wayland-scanner private-code < river-layout-v3.xml > river-layout-v3.c - * wayland-scanner client-header < river-layout-v3.xml > river-layout-v3.h - * gcc -Wall -Wextra -Wpedantic -Wno-unused-parameter -c -o layout.o layout.c - * gcc -Wall -Wextra -Wpedantic -Wno-unused-parameter -c -o river-layout-v3.o river-layout-v3.c - * gcc -o layout layout.o river-layout-v3.o -lwayland-client - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "river-layout-v3.h" - -/* A few macros to indulge the inner glibc user. */ -#define MIN(a, b) ( a < b ? a : b ) -#define MAX(a, b) ( a > b ? a : b ) -#define CLAMP(a, b, c) ( MIN(MAX(b, c), MAX(MIN(b, c), a)) ) - -struct Output -{ - struct wl_list link; - - struct wl_output *output; - struct river_layout_v3 *layout; - - uint32_t main_count; - double main_ratio; - uint32_t view_padding; - uint32_t outer_padding; - - bool configured; -}; - -/* In Wayland it's a good idea to have your main data global, since you'll need - * it everywhere anyway. - */ -struct wl_display *wl_display; -struct wl_registry *wl_registry; -struct wl_callback *sync_callback; -struct river_layout_manager_v3 *layout_manager; -struct wl_list outputs; -bool loop = true; -int ret = EXIT_FAILURE; - -static void layout_handle_layout_demand (void *data, struct river_layout_v3 *river_layout_v3, - uint32_t view_count, uint32_t width, uint32_t height, uint32_t tags, uint32_t serial) -{ - struct Output *output = (struct Output *)data; - - /* Simple tiled layout with no frills. - * - * If you want to create your own layout, just rip the following code - * out and replace it with your own logic. All dynamic tiling layouts - * you know, for example from dwm, can be easily ported to river this - * way. For more creative layouts, you probably also want to add custom - * values. Happy hacking! - */ - width -= 2 * output->outer_padding, height -= 2 * output->outer_padding; - unsigned int main_size, stack_size, view_x, view_y, view_width, view_height; - if ( output->main_count == 0 ) - { - main_size = 0; - stack_size = width; - } - else if ( view_count <= output->main_count ) - { - main_size = width; - stack_size = 0; - } - else - { - main_size = width * output->main_ratio; - stack_size = width - main_size; - } - for (unsigned int i = 0; i < view_count; i++) - { - if ( i < output->main_count ) /* main area. */ - { - view_x = 0; - view_width = main_size; - view_height = height / MIN(output->main_count, view_count); - view_y = i * view_height; - } - else /* Stack area. */ - { - view_x = main_size; - view_width = stack_size; - view_height = height / ( view_count - output->main_count); - view_y = (i - output->main_count) * view_height; - } - - river_layout_v3_push_view_dimensions(output->layout, - view_x + output->view_padding + output->outer_padding, - view_y + output->view_padding + output->outer_padding, - view_width - (2 * output->view_padding), - view_height - (2 * output->view_padding), - serial); - } - - /* Committing the layout means telling the server that your code is done - * laying out windows. Make sure you have pushed exactly the right - * amount of view dimensions, a mismatch is a protocol error. - * - * You also have to provide a layout name. This is a user facing string - * that the server can forward to status bars. You can use it to tell - * the user which layout is currently in use. You could also add some - * status information about your layout, but in this example we are - * boring and just use a static "[]=" like in dwm. - */ - river_layout_v3_commit(output->layout, "[]=", serial); -} - -static void layout_handle_namespace_in_use (void *data, struct river_layout_v3 *river_layout_v3) -{ - /* Oh no, the namespace we choose is already used by another client! - * All we can do now is destroy the river_layout object. Because we are - * lazy, we just abort and let our cleanup mechanism destroy it. A more - * sophisticated client could instead destroy only the one single - * affected river_layout object and recover from this mishap. Writing - * such a client is left as an exercise for the reader. - */ - fputs("Namespace already in use.\n", stderr); - loop = false; -} - -static bool skip_whitespace (char **ptr) -{ - if ( *ptr == NULL ) - return false; - while (isspace(**ptr)) - { - (*ptr)++; - if ( **ptr == '\0' ) - return false; - } - return true; -} - -static bool skip_nonwhitespace (char **ptr) -{ - if ( *ptr == NULL ) - return false; - while (! isspace(**ptr)) - { - (*ptr)++; - if ( **ptr == '\0' ) - return false; - } - return true; -} - -static const char *get_second_word (char **ptr, const char *name) -{ - /* Skip to the next word. */ - if ( !skip_nonwhitespace(ptr) || !skip_whitespace(ptr) ) - { - fprintf(stderr, "ERROR: Too few arguments. '%s' needs one argument.\n", name); - return NULL; - } - - /* Now we know where the second word begins. */ - const char *second_word = *ptr; - - /* Check if there is a third word. */ - if ( skip_nonwhitespace(ptr) && skip_whitespace(ptr) ) - { - fprintf(stderr, "ERROR: Too many arguments. '%s' needs one argument.\n", name); - return NULL; - } - - return second_word; -} - -static void handle_uint32_command (char **ptr, uint32_t *value, const char *name) -{ - const char *second_word = get_second_word(ptr, name); - if ( second_word == NULL ) - return; - const int32_t arg = atoi(second_word); - if ( *second_word == '+' || *second_word == '-' ) - *value = (uint32_t)MAX((int32_t)*value + arg, 0); - else - *value = (uint32_t)MAX(arg, 0); -} - -static void handle_float_command(char **ptr, double *value, const char *name, double clamp_upper, double clamp_lower) -{ - const char *second_word = get_second_word(ptr, name); - if ( second_word == NULL ) - return; - const double arg = atof(second_word); - if ( *second_word == '+' || *second_word == '-' ) - *value = CLAMP(*value + arg, clamp_upper, clamp_lower); - else - *value = CLAMP(arg, clamp_upper, clamp_lower); -} - -static bool word_comp (const char *word, const char *comp) -{ - if ( strncmp(word, comp, strlen(comp)) == 0 ) - { - const char *after_comp = word + strlen(comp); - if ( isspace(*after_comp) || *after_comp == '\0' ) - return true; - } - return false; - -} - -static void layout_handle_user_command (void *data, struct river_layout_v3 *river_layout_manager_v3, - const char *_command) -{ - /* The user_command event will be received whenever the user decided to - * send us a command. As an example, commands can be used to change the - * layout values. Parsing the commands is the job of the layout - * generator, the server just sends us the raw string. - * - * After this event is recevied, the views on the output will be - * re-arranged and so we will also receive a layout_demand event. - */ - - struct Output *output = (struct Output *)data; - - /* Skip preceding whitespace. */ - char *command = (char *)_command; - if (! skip_whitespace(&command)) - return; - - if (word_comp(command, "main_count")) - handle_uint32_command(&command, &output->main_count, "main_count"); - else if (word_comp(command, "view_padding")) - handle_uint32_command(&command, &output->view_padding, "view_padding"); - else if (word_comp(command, "outer_padding")) - handle_uint32_command(&command, &output->outer_padding, "outer_padding"); - else if (word_comp(command, "main_ratio")) - handle_float_command(&command, &output->main_ratio, "main_ratio", 0.1, 0.9); - else if (word_comp(command, "reset")) - { - /* This is an example of a command that does something different - * than just modifying a value. It resets all values to their - * defaults. - */ - - if ( skip_nonwhitespace(&command) && skip_whitespace(&command) ) - { - fputs("ERROR: Too many arguments. 'reset' has no arguments.\n", stderr); - return; - } - - output->main_count = 1; - output->main_ratio = 0.6; - output->view_padding = 5; - output->outer_padding = 5; - } - else - fprintf(stderr, "ERROR: Unknown command: %s\n", command); -} - -static const struct river_layout_v3_listener layout_listener = { - .namespace_in_use = layout_handle_namespace_in_use, - .layout_demand = layout_handle_layout_demand, - .user_command = layout_handle_user_command, -}; - -static void configure_output (struct Output *output) -{ - output->configured = true; - - /* The namespace of the layout is how the compositor chooses what layout - * to use. It can be any arbitrary string. It should describe roughly - * what kind of layout your client will create, so here we use "tile". - */ - output->layout = river_layout_manager_v3_get_layout(layout_manager, - output->output, "tile"); - river_layout_v3_add_listener(output->layout, &layout_listener, output); -} - -static bool create_output (struct wl_output *wl_output) -{ - struct Output *output = calloc(1, sizeof(struct Output)); - if ( output == NULL ) - { - fputs("Failed to allocate.\n", stderr); - return false; - } - - output->output = wl_output; - output->layout = NULL; - output->configured = false; - - /* These are the parameters of our layout. In this case, they are the - * ones you'd typically expect from a dynamic tiling layout, but if you - * are creative, you can do more. You can use any arbitrary amount of - * all kinds of values in your layout. If the user wants to change a - * value, the server lets us know using user_command event of the - * river_layout object. - * - * A layout generator is responsible for having sane defaults for all - * layout values. The server only sends user_command events when there - * actually is a command the user wants to send us. - */ - output->main_count = 1; - output->main_ratio = 0.6; - output->view_padding = 5; - output->outer_padding = 5; - - /* If we already have the river_layout_manager, we can get a - * river_layout object for this output. - */ - if ( layout_manager != NULL ) - configure_output(output); - - wl_list_insert(&outputs, &output->link); - return true; -} - -static void destroy_output (struct Output *output) -{ - if ( output->layout != NULL ) - river_layout_v3_destroy(output->layout); - wl_output_destroy(output->output); - wl_list_remove(&output->link); - free(output); -} - -static void destroy_all_outputs () -{ - struct Output *output, *tmp; - wl_list_for_each_safe(output, tmp, &outputs, link) - destroy_output(output); -} - -static void registry_handle_global (void *data, struct wl_registry *registry, - uint32_t name, const char *interface, uint32_t version) -{ - if ( strcmp(interface, river_layout_manager_v3_interface.name) == 0 ) - layout_manager = wl_registry_bind(registry, name, - &river_layout_manager_v3_interface, 1); - else if ( strcmp(interface, wl_output_interface.name) == 0 ) - { - struct wl_output *wl_output = wl_registry_bind(registry, name, - &wl_output_interface, version); - if (! create_output(wl_output)) - { - loop = false; - ret = EXIT_FAILURE; - } - } -} - -/* A no-op function we plug into listeners when we don't want to handle an event. */ -static void noop () {} - -static const struct wl_registry_listener registry_listener = { - .global = registry_handle_global, - .global_remove = noop -}; - -static void sync_handle_done (void *data, struct wl_callback *wl_callback, - uint32_t irrelevant) -{ - wl_callback_destroy(wl_callback); - sync_callback = NULL; - - /* When this function is called, the registry finished advertising all - * available globals. Let's check if we have everything we need. - */ - if ( layout_manager == NULL ) - { - fputs("Wayland compositor does not support river-layout-v3.\n", stderr); - ret = EXIT_FAILURE; - loop = false; - return; - } - - /* If outputs were registered before the river_layout_manager is - * available, they won't have a river_layout, so we need to create those - * here. - */ - struct Output *output; - wl_list_for_each(output, &outputs, link) - if (! output->configured) - configure_output(output); -} - -static const struct wl_callback_listener sync_callback_listener = { - .done = sync_handle_done, -}; - -static bool init_wayland (void) -{ - /* We query the display name here instead of letting wl_display_connect() - * figure it out itself, because libwayland (for legacy reasons) falls - * back to using "wayland-0" when $WAYLAND_DISPLAY is not set, which is - * generally not desirable. - */ - const char *display_name = getenv("WAYLAND_DISPLAY"); - if ( display_name == NULL ) - { - fputs("WAYLAND_DISPLAY is not set.\n", stderr); - return false; - } - - wl_display = wl_display_connect(display_name); - if ( wl_display == NULL ) - { - fputs("Can not connect to Wayland server.\n", stderr); - return false; - } - - wl_list_init(&outputs); - - /* The registry is a global object which is used to advertise all - * available global objects. - */ - wl_registry = wl_display_get_registry(wl_display); - wl_registry_add_listener(wl_registry, ®istry_listener, NULL); - - /* The sync callback we attach here will be called when all previous - * requests have been handled by the server. This allows us to know the - * end of the startup, at which point all necessary globals should be - * bound. - */ - sync_callback = wl_display_sync(wl_display); - wl_callback_add_listener(sync_callback, &sync_callback_listener, NULL); - - return true; -} - -static void finish_wayland (void) -{ - if ( wl_display == NULL ) - return; - - destroy_all_outputs(); - - if ( sync_callback != NULL ) - wl_callback_destroy(sync_callback); - if ( layout_manager != NULL ) - river_layout_manager_v3_destroy(layout_manager); - - wl_registry_destroy(wl_registry); - wl_display_disconnect(wl_display); -} - -int main (int argc, char *argv[]) -{ - if (init_wayland()) - { - ret = EXIT_SUCCESS; - while ( loop && wl_display_dispatch(wl_display) != -1 ); - } - finish_wayland(); - return ret; -} blob - 53134494ece34d16d5edb65d0334fa93ce3f41ed (mode 755) blob + /dev/null --- contrib/layout.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 -# -# Fibonacci spiral layout for river, implemented in simple python. Reading this -# code should help you get a basic understanding of how to use river-layout to -# create a basic layout generator. -# -# This depends on pywayland: https://github.com/flacjacket/pywayland/ -# -# Q: Wow, this looks complicated! -# A: For simple layouts, you really only need to care about what's in the -# layout_handle_layout_demand() function. And the rest isn't as complicated -# as it looks. -# -# Q: The script runs but nothing happens! How can I see this layout? -# A: Once started, to set this layout as default use the command: -# riverctl default-layout layout.py - -import mmap -import time -from pywayland.client import Display -from pywayland.protocol.wayland import WlOutput -try: - from pywayland.protocol.river_layout_v3 import RiverLayoutManagerV3 -except: - river_layout_help = """ - Your pywayland package does not have bindings for river-layout-v3. - You can generate the bindings with the following command: - python3 -m pywayland.scanner -i /usr/share/wayland/wayland.xml river-layout-v3.xml - It is recommended to use a virtual environment to avoid modifying your - system-wide python installation, See: https://docs.python.org/3/library/venv.html - """ - print(river_layout_help) - quit() - -layout_manager = None -outputs = [] -loop = True - -def layout_handle_layout_demand(layout, view_count, usable_w, usable_h, tags, serial): - x = 0 - y = 0 - w = usable_w - h = usable_h - for i in range(0, view_count - 1): - if i % 2 == 0: - w //= 2 - if i % 4 == 2: - layout.push_view_dimensions(x + w, y, w, h, serial) - else: - layout.push_view_dimensions(x, y, w, h, serial) - x += w - else: - h //= 2 - if i % 4 == 3: - layout.push_view_dimensions(x, y + h, w, h, serial) - else: - layout.push_view_dimensions(x, y, w, h, serial) - y += h - layout.push_view_dimensions(x, y, w, h, serial) - - # Committing the layout means telling the server that your code is done - # laying out windows. Make sure you have pushed exactly the right amount of - # view dimensions, a mismatch is a fatal protocol error. - # - # You also have to provide a layout name. This is a user facing string that - # the server can forward to status bars. You can use it to tell the user - # which layout is currently in use. You could also add some status - # information status information about your layout, which is what we do here. - layout.commit(f"{view_count} windows laid out by python", serial) - -def layout_handle_namespace_in_use(layout): - # Oh no, the namespace we choose is already used by another client! All we - # can do now is destroy the layout object. Because we are lazy, we just - # abort and let our cleanup mechanism destroy it. A more sophisticated - # client could instead destroy only the one single affected layout object - # and recover from this mishap. Writing such a client is left as an exercise - # for the reader. - print("Namespace already in use!") - global loop - loop = False - -class Output(object): - def __init__(self): - self.output = None - self.layout = None - self.id = None - - def destroy(self): - if self.layout is not None: - self.layout.destroy() - if self.output is not None: - self.output.destroy() - - def configure(self): - global layout_manager - if self.layout is None and layout_manager is not None: - # We need to set a namespace, which is used to identify our layout. - self.layout = layout_manager.get_layout(self.output, "layout.py") - self.layout.user_data = self - self.layout.dispatcher["layout_demand"] = layout_handle_layout_demand - self.layout.dispatcher["namespace_in_use"] = layout_handle_namespace_in_use - -def registry_handle_global(registry, id, interface, version): - global layout_manager - global output - if interface == 'river_layout_manager_v3': - layout_manager = registry.bind(id, RiverLayoutManagerV3, version) - elif interface == 'wl_output': - output = Output() - output.output = registry.bind(id, WlOutput, version) - output.id = id - output.configure() - outputs.append(output) - -def registry_handle_global_remove(registry, id): - for output in outputs: - if output.id == id: - output.destroy() - outputs.remove(output) - -display = Display() -display.connect() - -registry = display.get_registry() -registry.dispatcher["global"] = registry_handle_global -registry.dispatcher["global_remove"] = registry_handle_global_remove - -display.dispatch(block=True) -display.roundtrip() - -if layout_manager is None: - print("No layout_manager, aborting") - quit() - -for output in outputs: - output.configure() - -while loop and display.dispatch(block=True) != -1: - pass - -# Destroy outputs -for output in outputs: - output.destroy() - outputs.remove(output) - -display.disconnect() blob - 9da8feaedf0f306e46b8237ea53c2dc8044ffdf4 (mode 644) blob + /dev/null --- doc/river.1.scd +++ /dev/null @@ -1,64 +0,0 @@ -RIVER(1) - -# NAME - -river - dynamic tiling Wayland compositor - -# SYNOPSIS - -*river* [_options_] - -# DESCRIPTION - -*river* is a dynamic tiling Wayland compositor. Window management is based on -a stack of views laid out dynamically by an external layout generator. Tags -are used instead of workspaces allowing for increased flexibility. - -All configuration and control happens at runtime through Wayland protocols, -including several river-specific protocol extensions. The *riverctl*(1) -utility may be used to communicate with river over these protocols. - -# OPTIONS - -*-h* - Print a help message and exit. - -*-version* - Print the version number and exit. - -*-c* _shell_command_ - Override the default search paths for an init executable: instead - _shell_command_ will be run with _/bin/sh -c_. See the *CONFIGURATION* - section for more details. - -*-log-level* [*error*|*warning*|*info*|*debug*] - Set the log level of river. At the *error* log level, only errors - are logged. At the *debug* log level, everything is logged including - verbose debug messages. - -*-no-xwayland* - Disable xwayland at runtime even if river has been built with support. - -# CONFIGURATION - -On startup river will run an executable file at $XDG_CONFIG_HOME/river/init if -such an executable exists. If $XDG_CONFIG_HOME is not set, ~/.config/river/init -will be used instead. - -The executable init file will be run as a process group leader after river's -Wayland server is initialized but before entering the main loop. On exit, -river will send SIGTERM to this process group. - -Usually this executable is a shell script invoking *riverctl*(1) to create -mappings, start programs such as a layout generator or status bar, and -perform other configuration. - -# AUTHORS - -Maintained by Isaac Freund who is assisted by open -source contributors. For more information about river's development, see -. - -# SEE ALSO - -*riverctl*(1), *rivertile*(1) blob - /dev/null blob + b4863148963bf7d560a845e1fc1129b7f85a30e8 (mode 644) --- /dev/null +++ doc/ponton.1.scd @@ -0,0 +1,22 @@ +RIVER-CLASSIC(1) + +# NAME + +ponton - classic window manager for river + +# SYNOPSIS + +*ponton* + +# DESCRIPTION + +*ponton* is a separate window manager client for the non-monolithic +*river*(1) compositor. It implements the river window-management protocol and +owns classic policy such as focus, tags, bindings and layout decisions. + +It creates the control socket at $XDG_RUNTIME_DIR/ponton.sock +for use by *riverctl*(1). + +# SEE ALSO + +*river*(1), *riverctl*(1) blob - 9c1f2236e399ff3d000c26c507ed390416617b09 blob + bb0166425611580dcdc12d47161c52cb5ec69cda --- doc/riverctl.1.scd +++ doc/riverctl.1.scd @@ -2,557 +2,205 @@ RIVERCTL(1) # NAME -riverctl - command-line interface for controlling river +riverctl - command-line interface for ponton # SYNOPSIS -*riverctl* [_options_] _command_ [_command specific arguments_] +*riverctl* _command_ [_argument_...] # DESCRIPTION -*riverctl* is a command-line utility used to control and configure river -over the Wayland protocol. +*riverctl* sends one command to the running *ponton* window manager. +The socket path is $XDG_RUNTIME_DIR/ponton.sock, so *ponton* +must be started before using *riverctl*. -# OPTIONS - -*-h* - Print a help message and exit. - -*-version* - Print the version number and exit. - -# TERMINOLOGY - -This manual uses terms that some may find confusing, coming mostly from their -usage among other Wayland projects. - -The *compositor*, display server, Wayland server etc. are ways to refer to river -itself. - -A *view* (or *toplevel*) is what most call a window. - -An *output* is a synonym for a screen or monitor. - -*Tags* are river's way of dividing views of an output into groups (not -necessarily disjunct), an analogy to workspaces. - # COMMANDS -## ACTIONS - -*close* - Close the focused view. - *exit* - Exit the compositor, terminating the Wayland session. + Request that river ends the current Wayland session. -*focus-output* *next*|*previous*|*up*|*right*|*down*|*left*|_name_ - Focus the next or previous output, the closest output in any direction - or an output by name. +*close* + Close the focused window. -*focus-view* [*-skip-floating*] *next*|*previous*|*up*|*down*|*left*|*right* - Focus the next or previous view in the stack or the closest view in - any direction. +*focus-view* *next*|*previous* + Focus the next or previous visible window. - - *-skip-floating*: Skip floating views, only focusing tiled ones. +*set-focused-tags* _mask_ + Show the given tag bitmask on the output. -*move* *up*|*down*|*left*|*right* _delta_ - Move the focused view in the specified direction by _delta_ logical - pixels. The view will be set to floating. +*set-view-tags* _mask_ + Assign the given tag bitmask to the focused window. -*resize* *horizontal*|*vertical* _delta_ - Resize the focused view along the given axis by _delta_ logical - pixels. The view will be set to floating. +*declare-mode* _name_ + Create a new mapping mode. -*snap* *up*|*down*|*left*|*right* - Snap the focused view to the specified screen edge. The view will - be set to floating. +*enter-mode* _name_ + Switch all seats to the given mapping mode. -*send-to-output* [*-current-tags*] *next*|*previous*|*up*|*right*|*down*|*left*|_name_ - Send the focused view to the next or previous output, the closest - output in any direction or to an output by name. +*map* _mode_ _modifiers_ _keysym_ _command_... + Bind a key in the given mode. Modifiers combine with _+_, for + example _Super+Shift_. If the command starts with _spawn_ the rest + runs with _/bin/sh -c_, otherwise it runs as a window-manager command. - - *-current-tags*: Assign the currently focused tags of the destination - output to the view. +*map-pointer* _mode_ _modifiers_ _button_ _move-view_|_resize-view_|_command_... + Bind a pointer button in the given mode, for example _BTN_LEFT_. + _move-view_ and _resize-view_ start an interactive operation on the + focused window. Any other action runs as a command. -*spawn* _shell_command_ - Run _shell_command_ using `/bin/sh -c _shell_command_`. Note that - *spawn* only takes a single argument. To spawn a command taking - multiple arguments, wrapping the command in quotes is recommended. +*main-ratio* _ratio_|_+delta_|_-delta_ + Set or adjust the main area ratio between 0.1 and 0.9. -*swap* *next*|*previous*|*up*|*down*|*left*|*right* - Swap the focused view with the next or previous non-floating view in the - stack or the closest non-floating view in any direction. +*main-count* _count_|_+delta_|_-delta_ + Set or adjust the number of windows in the main area. +*main-location* _top_|_right_|_bottom_|_left_ + Set which side holds the main area. + +*zoom* + Move the focused window to the front of the layout stack. + *toggle-float* - Toggle the floating state of the focused view. + Toggle the floating state of the focused window. *toggle-fullscreen* - Toggle the fullscreen state of the focused view. + Toggle the fullscreen state of the focused window. -*zoom* - Bump the focused view to the top of the layout stack. If the top - view in the stack is already focused, bump the second view. +*rule-add* [-app-id glob] [-title glob] action [value] + Add a rule applied to newly managed windows, where action is one of + float, fullscreen, ssd, csd or tags. Globs allow a star as the first + or last character. The tags action takes a tag bitmask value. -*default-layout* _namespace_ - Set the layout namespace to be used by all outputs by default. +*rule-del* [-app-id glob] [-title glob] action + Remove the first matching rule. -*output-layout* _namespace_ - Set the layout namespace of currently focused output, overriding - the value set with *default-layout* if any. +*spawn* _command_... + Run a shell command with _/bin/sh -c_. -*send-layout-cmd* _namespace_ _command_ - Send _command_ to the layout generator on the currently focused output - with the given _namespace_, if any. What commands a layout generator - understands depends on the layout generator. For rivertile, see the - documentation in the *rivertile*(1) man page. +*swap* _next_|_previous_ + Swap the focused window with its tiled neighbor. -## TAG MANAGEMENT +*move* _up_|_down_|_left_|_right_ _delta_ + Float the focused window and move it by _delta_ pixels. -Tags are similar to workspaces but more flexible. You can assign views multiple -tags and focus multiple tags simultaneously. Bitfields are used to describe -sets of tags when interfacing with river. As such, the following commands -take a normal base 10 number as their argument but the semantics are best -understood in binary. The binary number 000000001 represents a set containing -only tag 1 while 100001101 represents a set containing tags 1, 3, 4, and 9. +*resize* _horizontal_|_vertical_ _delta_ + Float the focused window and resize it by _delta_ pixels. -When a view spawns it is assigned the currently focused tags of the output. +*snap* _up_|_down_|_left_|_right_ + Float the focused window and snap it to a screen edge. -At least one tag must always be focused and each view must be assigned at -least one tag. Operations that would violate either of these requirements -are ignored by river. +*focus-output* _next_|_previous_|_up_|_down_|_left_|_right_|_name_ + Focus an output by position or connector name. -*set-focused-tags* _tags_ - Show views with tags corresponding to the set bits of _tags_ on the - currently focused output. +*focus-view* _up_|_down_|_left_|_right_ + Focus the nearest tiled window in the given direction. -*set-view-tags* _tags_ - Assign the currently focused view the tags corresponding to the set - bits of _tags_. +*attach-mode* _top_|_bottom_ +*default-attach-mode* _top_|_bottom_ + Set where new windows attach in the layout stack. -*toggle-focused-tags* _tags_ - Toggle visibility of views with tags corresponding to the set bits - of _tags_ on the currently focused output. +*output-attach-mode* _top_|_bottom_ + Override the attach mode for the focused output. -*toggle-view-tags* _tags_ - Toggle the tags of the currently focused view corresponding to the - set bits of _tags_. +*spawn-tagmask* _mask_ + Assign the tag mask to newly spawned windows. -*spawn-tagmask* _tagmask_ - Set a _tagmask_ to filter the tags assigned to newly spawned views. This mask - will be applied to the tags of new views with a bitwise and. If, for example, - the tags 000011111 are focused and the spawn _tagmask_ is 111110001, a - new view will be assigned the tags 000010001. If no tags would remain after - filtering, the _tagmask_ is ignored. - *focus-previous-tags* - Sets tags to their previous value on the currently focused output, - allowing jumping back and forth between 2 tag setups. + Swap the focused output tags with the previous set. *send-to-previous-tags* - Assign the currently focused view the previous tags of the currently - focused output. + Assign the previous output tags to the focused window. -## MAPPINGS +*send-to-output* [_-current-tags_] _next_|_previous_|_up_|_down_|_left_|_right_|_name_ + Move the focused window to another output. With _-current-tags_ the + window takes the destination output tags. -Mappings are modal in river. Each mapping is associated with a mode and -is only active while in that mode. There are two special modes: "normal" -and "locked". The normal mode is the initial mode on startup. The locked -mode is automatically entered while the session is locked (e.g. due to -a screenlocker). It cannot be entered or exited manually. +*toggle-focused-tags* _mask_ + Toggle tag bits on the focused output. -The following modifiers are available for use in mappings: +*toggle-view-tags* _mask_ + Toggle tag bits on the focused window. - - Shift - - Control - - Mod1 (Alt) - - Mod3 - - Mod4 (Super) - - Mod5 - - None - -Alt and Super are aliases for Mod1 and Mod4 respectively. None allows creating -a mapping without modifiers. - -Keys are specified by their XKB keysym name. See -_/usr/include/xkbcommon/xkbcommon-keysyms.h_ for the complete list. - -Mouse buttons are specified by Linux input event code names. The most commonly -used values are: - - - BTN_LEFT - left mouse button - - BTN_RIGHT - right mouse button - - BTN_MIDDLE - middle mouse button - -A complete list may be found in _/usr/include/linux/input-event-codes.h_ - -*declare-mode* _name_ - Create a new mode called _name_. - -*enter-mode* _name_ - Switch to given mode if it exists. - -*map* [*-release*|*-repeat*|*-layout* _index_] _mode_ _modifiers_ _key_ _command_ - Run _command_ when _key_ is pressed while _modifiers_ are held down - and in the specified _mode_. - - - *-release*: if passed activate on key release instead of key press - - *-repeat*: if passed activate repeatedly until key release; may not - be used with *-release* - - *-layout*: if passed, a specific layout is pinned to the mapping. - When the mapping is checked against a pressed key, this layout is - used to translate the key independent of the active layout - - _index_: zero-based index of a layout set with the *keyboard-layout* - command. If the index is out of range, the *-layout* option will - have no effect - - _mode_: name of the mode for which to create the mapping - - _modifiers_: one or more of the modifiers listed above, separated - by a plus sign (+). - - _key_: an XKB keysym name as described above - - _command_: any command that may be run with riverctl - -*map-pointer* _mode_ _modifiers_ _button_ _action_|_command_ - Move or resize views or run _command_ when _button_ and _modifiers_ are held - down while in the specified _mode_. The view under the cursor will be - focused. - - - _mode_: name of the mode for which to create the mapping - - _modifiers_: one or more of the modifiers listed above, separated - by a plus sign (+). - - _button_: the name of a Linux input event code as described above - - _action_: one of the following values: - - move-view - - resize-view - - _command_: any command that may be run with riverctl - -*map-switch* _mode_ *lid*|*tablet* _state_ _command_ - Run _command_ when river receives a certain switch event. - - - _mode_: name of the mode for which to create the mapping - - _lid_|_tablet_: 'lid switch' and 'tablet mode switch' are supported - - _state_: - - possible states for _lid_: - - close - - open - - possible states for _tablet_: - - on - - off - - _command_: any command that may be run with riverctl - -*unmap* [*-release*] _mode_ _modifiers_ _key_ - Remove the mapping defined by the arguments: - - - *-release*: if passed unmap the key release instead of the key press - - _mode_: name of the mode for which to remove the mapping - - _modifiers_: one or more of the modifiers listed above, separated - by a plus sign (+). - - _key_: an XKB keysym name as described above - -*unmap-pointer* _mode_ _modifiers_ _button_ - Remove the pointer mapping defined by the arguments: - - - _mode_: name of the mode for which to remove the mapping - - _modifiers_: one or more of the modifiers listed above, separated - by a plus sign (+). - - _button_: the name of a Linux input event code as described above - -*unmap-switch* _mode_ *lid*|*tablet* _state_ - Remove the switch mapping defined by the arguments: - - - _mode_: name of the mode for which to remove the mapping - - _lid_|_tablet_: the switch for which to remove the mapping - - _state_: a state as listed above - -## RULES - -Rules match the app-id and title of views against a _glob_ pattern. A _glob_ -is a string that may optionally have an _\*_ at the beginning and/or end. An -_\*_ in a _glob_ matches zero or more arbitrary characters in the app-id -or title. - -For example, _abc_ is matched by _a\*_, _\*a\*_, _\*b\*_, _\*c_, _abc_, and -_\*_ but not matched by _\*a_, _b\*_, _\*b_, _c\*_, or _ab_. Note that _\*_ -matches everything while _\*\*_ and the empty string are invalid. - -*rule-add* [*-app-id* _glob_|*-title* _glob_] _action_ [_arguments_] - Add a rule that applies an _action_ to views with *app-id* and *title* - matched by the respective _glob_. Omitting *-app-id* or *-title* - is equivalent to passing *-app-id* _\*_ or *-title* _\*_. - Some actions require one or more _arguments_. - - The supported _action_ types are: - - - *float*: Make the view floating. Applies only to new views. - - *no-float*: Don't make the view floating. Applies only to - new views. - - *ssd*: Use server-side decorations for the view. Applies to new - and existing views. - - *csd*: Use client-side decorations for the view. Applies to new - and existing views. - - *tags*: Set the initial tags of the view. Requires the tags as - an argument. Applies only to new views. - - *output*: Set the initial output of the view. Requires the output - as an argument. Applies only to new views. The output can be specified - either by connector name (such as _HDMI-A-1_, or _DP-2_), or by - identifier in the form of _MAKE MODEL SERIAL_, for example for an output - with make: _HP Inc._, model: _HP 22w_, and serial: _CNC93720WF_, the - identifier would be: _HP Inc. HP 22w CNC93720WF_. If the make, model, or - serial is unknown, the word "Unknown" is used instead. - - *position*: Set the initial position of the view, clamping to the - bounds of the output. Requires x and y coordinates of the view as - arguments, both of which must be non-negative. Applies only to new views. - - *dimensions*: Set the initial dimensions of the view, clamping to the - constraints of the view. Requires width and height of the view as - arguments, both of which must be non-negative. Applies only to new views. - - *fullscreen*: Make the view fullscreen. Applies only to new views. - - *no-fullscreen*: Don't make the view fullscreen. Applies only to - new views. - - *tearing*: Allow the view to tear when fullscreen regardless of the - view's preference. Applies to new and existing views. - - *no-tearing*: Disable tearing for the view regardless of the view's - preference. Applies to new and existing views. - - Both *float* and *no-float* rules are added to the same list, - which means that adding a *no-float* rule with the same arguments - as a *float* rule will overwrite it. The same holds for *ssd* and - *csd*, *fullscreen* and *no-fullscreen*, *tearing* and - *no-tearing* rules. - - If multiple rules in a list match a given view the most specific - rule will be applied. For example with the following rules - ``` - app-id title action - foo bar ssd - foo * csd - * bar csd - * baz ssd - ``` - a view with app-id 'foo' and title 'bar' would get ssd despite matching - two csd rules as the first rule is most specific. Furthermore a view - with app-id 'foo' and title 'baz' would get csd despite matching the - last rule in the list since app-id specificity takes priority over - title specificity. - - If a view is not matched by any rule, river will respect the csd/ssd - wishes of the client and may start the view floating based on simple - heuristics intended to catch popup-like views. - - If a view is started fullscreen or is not floating, then *position* and - *dimensions* rules will have no effect A view must be matched by a *float* - rule in order for them to take effect. - -*rule-del* [*-app-id* _glob_|*-title* _glob_] _action_ - Delete a rule created using *rule-add* with the given arguments. - -*list-rules* *float*|*ssd*|*tags*|*position*|*dimensions*|*fullscreen* - Print the specified rule list. The output is ordered from most specific - to least specific, the same order in which views are checked against - when searching for a match. Only the first matching rule in the list - has an effect on a given view. - -## CONFIGURATION - -*default-attach-mode* *top*|*bottom*|*above*|*below*|*after * - Set the attach mode to be used by all outputs by default. - - Possible values: - - top: Prepends the newly spawned view at the top of the stack. - - bottom: Appends the newly spawned view at the bottom of the stack. - - above: Inserts the newly spawned view above the currently focused view. - - below: Inserts the newly spawned view below the currently focused view. - - after : Inserts the newly spawned view after N views in the stack. - - Note that the deprecated *attach-mode* command is aliased to - *default-attach-mode* for backwards compatibility. - -*output-attach-mode* *top*|*bottom*|*above*|*below*|*after * - Set the attach mode of the currently focused output, overriding the value of - default-attach-mode if any. - -*allow-tearing* *enabled*|*disabled* - Allow fullscreen views to tear if requested by the view. See also the - *tearing* rule to force enable tearing for specific views. - -*background-color* _0xRRGGBB_|_0xRRGGBBAA_ - Set the background color. - -*border-color-focused* _0xRRGGBB_|_0xRRGGBBAA_ - Set the border color of focused views. - -*border-color-unfocused* _0xRRGGBB_|_0xRRGGBBAA_ - Set the border color of unfocused views. - -*border-color-urgent* _0xRRGGBB_|_0xRRGGBBAA_ - Set the border color of urgent views. - *border-width* _pixels_ - Set the border width to _pixels_. + Set the border width drawn around windows. -*focus-follows-cursor* *disabled*|*normal*|*always* - There are three available modes: +*border-color-focused* _0xRRGGBB[AA]_ + Set the border color of focused windows. - - _disabled_: Moving the cursor does not affect focus. This is - the default. - - _normal_: Moving the cursor over a view will focus that view. - Moving the cursor within a view will not re-focus that view if - focus has moved elsewhere. - - _always_: Moving the cursor will always focus whatever view is - under the cursor. +*border-color-unfocused* _0xRRGGBB[AA]_ + Set the border color of unfocused windows. - If the view to be focused is on an output that does not have focus, - focus is switched to that output. +*focus-follows-cursor* _disabled_|_normal_|_always_ + Focus windows when the pointer enters them. _always_ currently behaves + like _normal_ because the protocol only reports pointer enter and leave. -*hide-cursor* *timeout* _timeout_ - Hide the cursor if it wasn't moved in the last _timeout_ milliseconds - until it is moved again. The default value is 0, which disables - automatically hiding the cursor. Show the cursor again on any movement. +*set-cursor-warp* _disabled_|_on-focus_ + Warp the pointer to the center of newly focused windows. -*hide-cursor* *when-typing* *enabled*|*disabled* - Hide the cursor when pressing any non-modifier key. Show the cursor - again on any movement. +*xcursor-theme* _name_ _size_ + Set the cursor theme for all seats. -*set-cursor-warp* *disabled*|*on-output-change*|*on-focus-change* - Set the cursor warp mode. There are two available modes: - - - _disabled_: Cursor will not be warped. This is the default. - - _on-output-change_: When a different output is focused, the cursor will be - warped to its center. - - _on-focus-change_: When a different view/output is focused, the cursor will be - warped to its center. - *set-repeat* _rate_ _delay_ - Set the keyboard repeat rate to _rate_ key repeats per second and - repeat delay to _delay_ milliseconds. The default is a rate of 25 - repeats per second and a delay of 600ms. + Set keyboard repeat rate and delay on all keyboards. -*xcursor-theme* _theme_name_ [_size_] - Set the xcursor theme to _theme_name_ and optionally set the _size_. - The theme of the default seat determines the default for Xwayland - and is made available through the _XCURSOR_THEME_ and _XCURSOR_SIZE_ - environment variables. - -## INPUT CONFIGURATION - *list-inputs* - List all input devices. + List known input devices with their types. *list-input-configs* - List all input configurations. + List input devices supporting libinput configuration. -*keyboard-layout* [-rules _rules_] [-model _model_] [-variant _variant_] \ -[-options _options_] _layout_ - Set the XKB layout for all keyboards. Defaults from libxkbcommon are used for - everything left unspecified. Note that _layout_ may be a comma separated list - of layouts (e.g. "us,de") which may be switched between using various key - combinations configured through the options argument (e.g. -options - "grp:ctrl_space_toggle"). See *xkeyboard-config*(7) for possible values and - more information. +*list-rules* + List configured window rules. +*unmap* _mode_ _modifiers_ _keysym_ + Remove a key binding. + +*unmap-pointer* _mode_ _modifiers_ _button_ + Remove a pointer binding. + +*input* _pattern_ [_category_] _setting_ [_value_] + Configure matching input devices. The pattern is a glob over device + names. The optional category filters by _touchpad_, _pointer_, _mouse_, + _keyboard_, _touch_ or _tablet_. Settings include tap, drag, + drag-lock, three-finger-drag, natural-scroll, left-handed, click-method, + clickfinger-button-map, middle-emulation, scroll-method, scroll-button, + scroll-button-lock, dwt, dwtp, send-events, accel-profile, pointer-accel + and scroll-factor. Boolean settings accept _enabled_ or _disabled_ + (_enable_ and _disable_ also work) and dashes and underscores mix. + +*keyboard-layout* [_-rules_ _rules_] [_-model_ _model_] _layout_ [_-variant_ _variant_] [_-options_ _options_] + Compile a keymap from xkb rule names and apply it to all keyboards. + *keyboard-layout-file* _path_ - Set the XKB layout for all keyboards from an XKB keymap file at the provided - path. Documentation for the XKB keymap file format can be found at the - following URL: - https://xkbcommon.org/doc/current/keymap-text-format-v1.html + Load a keymap file and apply it to all keyboards. -The _input_ command can be used to create a configuration rule for an input -device identified by its _name_. -The _name_ of an input device consists of its type, its decimal vendor id, -its decimal product id and finally its self-advertised name, separated by -. -Simple globbing patterns are supported, see the rules section for further -information on globs. +# PROTOCOL -A list of all device properties that can be configured may be found below. -However note that not every input device supports every property. +Commands use one newline-terminated request. Requests are bounded to 4096 bytes. +The socket is private to the current runtime directory. -*input* _name_ *events* *enabled*|*disabled*|*disabled-on-external-mouse* - Configure whether the input devices events will be used by river. +# NOT SUPPORTED -*input* _name_ *accel-profile* *none*|*flat*|*adaptive* - Set the pointer acceleration profile of the input device. +The following ponton 0.3 commands have no equivalent. The 0.3 +compositor-side control, status and layout-generator protocols are gone and +the new river protocols expose no replacement surface. -*input* _name_ *pointer-accel* _factor_ - Set the pointer acceleration factor of the input device. Needs a float - between -1.0 and 1.0. +_send-layout-cmd_, _default-layout_, _output-layout_ + Layout is built into ponton. There is no external layout + generator process. Use _main-ratio_, _main-count_ and _main-location_. -*input* _name_ *click-method* *none*|*button-areas*|*clickfinger* - Set the click method of the input device. +_background-color_, _hide-cursor_ + Compositor rendering has no window-manager control surface. -*input* _name_ *drag* *enabled*|*disabled* - Enable or disable the tap-and-drag functionality of the input device. +_map-switch_, _unmap-switch_ + No switch-binding protocol exists. Bindings come from _map_ and + _map-pointer_ only. -*input* _name_ *drag-lock* *enabled*|*disabled* - Enable or disable the drag lock functionality of the input device. +_keyboard-group-create_, _keyboard-group-add_, _keyboard-group-remove_, _keyboard-group-destroy_ + All keyboards share the window-manager keymap set with + _keyboard-layout_, so groups add nothing. -*input* _name_ *disable-while-typing* *enabled*|*disabled* - Enable or disable the disable-while-typing functionality of the input device. +_list-input-configs_ remains for listing configurable devices. -*input* _name_ *disable-while-trackpointing* *enabled*|*disabled* - Enable or disable the disable-while-trackpointing functionality of the input device. - -*input* _name_ *middle-emulation* *enabled*|*disabled* - Enable or disable the middle click emulation functionality of the input device. - -*input* _name_ *natural-scroll* *enabled*|*disabled* - Enable or disable the natural scroll functionality of the input device. If - active, the scroll direction is inverted. - -*input* _name_ *scroll-factor* _factor_ - Set the scroll factor of the input device. Accepts a postive value - greater than 0. For example, a _factor_ of 0.5 will make scrolling twice - as slow while a _factor_ of 3 will make scrolling 3 times as fast. - -*input* _name_ *left-handed* *enabled*|*disabled* - Enable or disable the left handed mode of the input device. - -*input* _name_ *tap* *enabled*|*disabled* - Enable or disable the tap functionality of the input device. - -*input* _name_ *tap-button-map* *left-right-middle*|*left-middle-right* - Configure the button mapping for tapping. - - - _left-right-middle_: 1 finger tap equals left click, 2 finger tap equals - right click, 3 finger tap equals middle click. - - _left-middle-right_: 1 finger tap equals left click, 2 finger tap equals - middle click, 3 finger tap equals right click. - -*input* _name_ *scroll-method* *none*|*two-finger*|*edge*|*button* - Set the scroll method of the input device. - - - _none_: No scrolling - - _two-finger_: Scroll by swiping with two fingers simultaneously - - _edge_: Scroll by swiping along the edge - - _button_: Scroll with pointer movement while holding down a button - -*input* _name_ *scroll-button* _button_ - Set the scroll button of an input device. _button_ is the name of a Linux - input event code. - -*input* _name_ *scroll-button-lock* *enabled*|*disabled* - Enable or disable the scroll button lock functionality of the input device. If - active, the button does not need to be held down. One press makes the button - considered to be held down, and a second press releases the button. - -*input* _name_ *map-to-output* _output_|*disabled* - Maps the input to a given output. This is valid even if the output isn't - currently active and will lead to the device being mapped once it is - connected. - -# EXAMPLES - -Bind Super+Return in normal mode to spawn a *foot*(1) terminal: - - riverctl map normal Mod4 Return spawn 'foot --app-id=foobar' - -Bind Super+Shift+J to swap the focused view with the next visible view: - - riverctl map normal Mod4+Shift J swap next - -# AUTHORS - -Maintained by Isaac Freund who is assisted by open -source contributors. For more information about river's development, see -. - # SEE ALSO -*river*(1), *rivertile*(1) +*river*(1), *ponton*(1) blob - a73ebde3cf05d38b1f87c2c85e11a63cf48b0966 (mode 644) blob + /dev/null --- doc/rivertile.1.scd +++ /dev/null @@ -1,88 +0,0 @@ -RIVERTILE(1) - -# NAME - -rivertile - tiled layout generator for river - -# SYNOPSIS - -*rivertile* [_options_] - -# DESCRIPTION - -*rivertile* is a layout generator for *river*(1). It provides a simple tiled -layout with split main/secondary stacks. The initial state may be configured -with various options passed on startup. Some values may additionally be -modified while rivertile is running with the help of *riverctl*(1). - -# OPTIONS - -*-h* - Print a help message and exit. - -*-version* - Print the version number and exit. - -*-view-padding* _pixels_ - Set the padding around views in pixels. (Default: 6) - -*-outer-padding* _pixels_ - Set the padding around the edge of the layout area in pixels. - (Default: 6) - -*-main-location* [*top*|*bottom*|*left*|*right*] - Set the initial location of the main area in the layout. - (Default: *left*) - -*-main-count* _count_ - Set the initial number of views in the main area of the - layout. (Default: 1) - -*-main-ratio* _ratio_ - Set the initial ratio of the main area to total layout area. The - _ratio_ must be between 0.1 and 0.9, inclusive. (Default: 0.6) - -# COMMANDS - -These commands may be sent to rivertile at runtime with the help of -*riverctl*(1). - -*main-location* [*top*|*bottom*|*left*|*right*] - Set the location of the main area in the layout. - -*main-count* _value_ - Set or modify the number of views in the main area of the layout. If - _value_ is prefixed by a +/- sign, _value_ is added/subtracted from the - current count. If there is no sign, the main count is set to _value_. - Note that the main count cannot be decreased below 1. - -*main-ratio* _value_ - Set or modify the ratio of the main area to total layout area. If - _value_ is prefixed by a +/- sign, _value_ is added/subtracted from - the current ratio. If there is no sign, the main ratio is set to - _value_. Note that the ratio will always be clamped to the range - 0.1 to 0.9. - -# EXAMPLES - -Start *rivertile* with 4 pixels outer padding and the *top* main location: - - rivertile -outer-padding 4 -main-location top - -Increase the main ratio by 0.1 at runtime: - - riverctl send-layout-cmd rivertile "main-ratio +0.1" - -Set the main count to 3 at runtime: - - riverctl send-layout-cmd rivertile "main-count 3" - -# AUTHORS - -Maintained by Isaac Freund who is assisted by open -source contributors. For more information about river's development, see -. - -# SEE ALSO - -*river*(1), *riverctl*(1) blob - 06595b5ffc147674893f52b1a58ebd467c0266b4 blob + c261ded06b3ad1339b67743ee0b1b50b1e1909b9 --- example/init +++ example/init @@ -1,162 +1,144 @@ -#!/bin/sh +#!/bin/bash +set -x -# This is the example configuration file for river. -# -# If you wish to edit this, you will probably want to copy it to -# $XDG_CONFIG_HOME/river/init or $HOME/.config/river/init first. -# -# See the river(1), riverctl(1), and rivertile(1) man pages for complete -# documentation. +mod="Mod1" +alt="Mod4" -# Note: the "Super" modifier is also known as Logo, GUI, Windows, Mod4, etc. +term="kitty --single-instance" +wobsock="$XDG_RUNTIME_DIR/wob.sock" -# Super+Shift+Return to start an instance of foot (https://codeberg.org/dnkl/foot) -riverctl map normal Super+Shift Return spawn foot +dmenu_args="-vi -fn 'Ttyp0 OTB:size=10' -l 10 -h 20 -nb '#101010' -nf '#999999' -sb '#999999' -sf '#101010' -nhb '#1b1b1b' -nhf '#8a8a8a'" -# Super+Q to close the focused view -riverctl map normal Super Q close +shepherd & -# Super+Shift+E to exit river -riverctl map normal Super+Shift E exit +ponton & -# Super+J and Super+K to focus the next/previous view in the layout stack -riverctl map normal Super J focus-view next -riverctl map normal Super K focus-view previous +# rivertile is gone: layout is built into ponton. +riverctl main-ratio 0.50 -# Super+Shift+J and Super+Shift+K to swap the focused view with the next/previous -# view in the layout stack -riverctl map normal Super+Shift J swap next -riverctl map normal Super+Shift K swap previous +# background-color has no protocol surface and is dropped. +riverctl border-color-focused "0x2fafff" +riverctl border-color-unfocused "0x222222" +riverctl border-width 1 -# Super+Period and Super+Comma to focus the next/previous output -riverctl map normal Super Period focus-output next -riverctl map normal Super Comma focus-output previous +# border-color-focused-inactive does not exist in ponton, dropped. -# Super+Shift+{Period,Comma} to send the focused view to the next/previous output -riverctl map normal Super+Shift Period send-to-output next -riverctl map normal Super+Shift Comma send-to-output previous - -# Super+Return to bump the focused view to the top of the layout stack -riverctl map normal Super Return zoom - -# Super+H and Super+L to decrease/increase the main ratio of rivertile(1) -riverctl map normal Super H send-layout-cmd rivertile "main-ratio -0.05" -riverctl map normal Super L send-layout-cmd rivertile "main-ratio +0.05" - -# Super+Shift+H and Super+Shift+L to increment/decrement the main count of rivertile(1) -riverctl map normal Super+Shift H send-layout-cmd rivertile "main-count +1" -riverctl map normal Super+Shift L send-layout-cmd rivertile "main-count -1" - -# Super+Alt+{H,J,K,L} to move views -riverctl map normal Super+Alt H move left 100 -riverctl map normal Super+Alt J move down 100 -riverctl map normal Super+Alt K move up 100 -riverctl map normal Super+Alt L move right 100 - -# Super+Alt+Control+{H,J,K,L} to snap views to screen edges -riverctl map normal Super+Alt+Control H snap left -riverctl map normal Super+Alt+Control J snap down -riverctl map normal Super+Alt+Control K snap up -riverctl map normal Super+Alt+Control L snap right - -# Super+Alt+Shift+{H,J,K,L} to resize views -riverctl map normal Super+Alt+Shift H resize horizontal -100 -riverctl map normal Super+Alt+Shift J resize vertical 100 -riverctl map normal Super+Alt+Shift K resize vertical -100 -riverctl map normal Super+Alt+Shift L resize horizontal 100 - -# Super + Left Mouse Button to move views -riverctl map-pointer normal Super BTN_LEFT move-view - -# Super + Right Mouse Button to resize views -riverctl map-pointer normal Super BTN_RIGHT resize-view - -# Super + Middle Mouse Button to toggle float -riverctl map-pointer normal Super BTN_MIDDLE toggle-float - -for i in $(seq 1 9) -do - tags=$((1 << ($i - 1))) - - # Super+[1-9] to focus tag [0-8] - riverctl map normal Super $i set-focused-tags $tags - - # Super+Shift+[1-9] to tag focused view with tag [0-8] - riverctl map normal Super+Shift $i set-view-tags $tags - - # Super+Control+[1-9] to toggle focus of tag [0-8] - riverctl map normal Super+Control $i toggle-focused-tags $tags - - # Super+Shift+Control+[1-9] to toggle tag [0-8] of focused view - riverctl map normal Super+Shift+Control $i toggle-view-tags $tags -done - -# Super+0 to focus all tags -# Super+Shift+0 to tag focused view with all tags -all_tags=$(((1 << 32) - 1)) -riverctl map normal Super 0 set-focused-tags $all_tags -riverctl map normal Super+Shift 0 set-view-tags $all_tags - -# Super+Space to toggle float -riverctl map normal Super Space toggle-float - -# Super+F to toggle fullscreen -riverctl map normal Super F toggle-fullscreen - -# Super+{Up,Right,Down,Left} to change layout orientation -riverctl map normal Super Up send-layout-cmd rivertile "main-location top" -riverctl map normal Super Right send-layout-cmd rivertile "main-location right" -riverctl map normal Super Down send-layout-cmd rivertile "main-location bottom" -riverctl map normal Super Left send-layout-cmd rivertile "main-location left" - -# Declare a passthrough mode. This mode has only a single mapping to return to -# normal mode. This makes it useful for testing a nested wayland compositor -riverctl declare-mode passthrough - -# Super+F11 to enter passthrough mode -riverctl map normal Super F11 enter-mode passthrough - -# Super+F11 to return to normal mode -riverctl map passthrough Super F11 enter-mode normal - -# Various media key mapping examples for both normal and locked mode which do -# not have a modifier -for mode in normal locked -do - # Eject the optical drive (well if you still have one that is) - riverctl map $mode None XF86Eject spawn 'eject -T' - - # Control pulse audio volume with pamixer (https://github.com/cdemoulins/pamixer) - riverctl map $mode None XF86AudioRaiseVolume spawn 'pamixer -i 5' - riverctl map $mode None XF86AudioLowerVolume spawn 'pamixer -d 5' - riverctl map $mode None XF86AudioMute spawn 'pamixer --toggle-mute' - - # Control MPRIS aware media players with playerctl (https://github.com/altdesktop/playerctl) - riverctl map $mode None XF86AudioMedia spawn 'playerctl play-pause' - riverctl map $mode None XF86AudioPlay spawn 'playerctl play-pause' - riverctl map $mode None XF86AudioPrev spawn 'playerctl previous' - riverctl map $mode None XF86AudioNext spawn 'playerctl next' - - # Control screen backlight brightness with brightnessctl (https://github.com/Hummer12007/brightnessctl) - riverctl map $mode None XF86MonBrightnessUp spawn 'brightnessctl set +5%' - riverctl map $mode None XF86MonBrightnessDown spawn 'brightnessctl set 5%-' -done - -# Set background and border color -riverctl background-color 0x002b36 -riverctl border-color-focused 0x93a1a1 -riverctl border-color-unfocused 0x586e75 - -# Set keyboard repeat rate riverctl set-repeat 50 300 -# Make all views with an app-id that starts with "float" and title "foo" start floating. -riverctl rule-add -app-id 'float*' -title 'foo' float +# default-layout is gone with the layout-generator process. +riverctl xcursor-theme McMojave 16 -# Make all views with app-id "bar" and any title use client-side decorations -riverctl rule-add -app-id "bar" csd +riverctl input "pointer-1189-32769-BenQ_ZOWIE_BenQ_ZOWIE_Gaming_Mouse" pointer-accel 0.0 +riverctl input "pointer-1133-45108-Logitech_MX_Master_3S" pointer-accel 0.0 +riverctl input "pointer-1133-16500-Logitech_G305" pointer-accel 0.0 -# Set the default layout generator to be rivertile and start it. -# River will send the process group of the init executable SIGTERM on exit. -riverctl default-layout rivertile -rivertile -view-padding 6 -outer-padding 6 & +riverctl input "*" touchpad tap disable +riverctl input "*" touchpad drag disable +riverctl input "*" touchpad click_method none +riverctl input "*" touchpad scroll_method two_finger + +riverctl attach-mode top +riverctl focus-follows-cursor normal + +riverctl map normal Super Return zoom + +riverctl map normal $mod+Shift Return spawn "$term" +riverctl map normal $alt+Shift Return spawn "$term" +riverctl map normal $mod Return spawn "tmuxc -n" + +riverctl map normal $mod p spawn "dmenu_run $dmenu_args" +riverctl map normal $alt p spawn "dmenu_run $dmenu_args" + +riverctl map normal $mod equal spawn "tmuxc -l" +riverctl map normal $alt equal spawn "tmuxc -l" + +riverctl map normal $mod m spawn "tmuxc -M" +riverctl map normal $alt m spawn "tmuxc -M" + +riverctl map normal Control+$alt l spawn "waylock" +riverctl map normal Control+$mod l spawn "monofetch" + +riverctl map normal $mod+Shift o spawn "todomenu $dmenu_args" +riverctl map normal $alt+Shift d spawn "clipmenu $dmenu_args" +riverctl map normal $alt+Shift l spawn "langmenu $dmenu_args" +riverctl map normal $alt+Shift h spawn "huemenu $dmenu_args" + +riverctl map normal $mod+Shift m spawn "plsmenu $dmenu_args" +riverctl map normal $alt+Shift m spawn "plsmenu $dmenu_args" + +riverctl map normal $mod+Shift b spawn "btmenu con $dmenu_args" +riverctl map normal $alt+Shift b spawn "btmenu dis $dmenu_args" + +riverctl map normal $mod+Shift p spawn "passmenu $dmenu_args" +riverctl map normal $alt+Shift p spawn "passmenu $dmenu_args" + +riverctl map normal $mod+Shift s spawn "screenshot" +riverctl map normal $alt+Shift s spawn "screenshot" + +riverctl map normal $mod+Shift t spawn "yank-to-todo" +riverctl map normal $alt+Shift t spawn "tempmenu $dmenu_args" + +riverctl map normal None XF86AudioMute spawn "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle && (wpctl get-volume @DEFAULT_AUDIO_SINK@ | grep -q MUTED && echo 0 > $wobsock) || wpctl get-volume @DEFAULT_AUDIO_SINK@ | sed 's/[^0-9]//g' > $wobsock" +riverctl map normal None XF86AudioLowerVolume spawn "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- && wpctl get-volume @DEFAULT_AUDIO_SINK@ | sed 's/[^0-9]//g' > $wobsock" +riverctl map normal None XF86AudioRaiseVolume spawn "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ && wpctl get-volume @DEFAULT_AUDIO_SINK@ | sed 's/[^0-9]//g' > $wobsock" + +riverctl map normal None XF86MonBrightnessUp spawn "brightnessctl set +5% | sed -En 's/.*\(([0-9]+)%\).*/\1/p' > $wobsock" +riverctl map normal None XF86MonBrightnessDown spawn "brightnessctl set 5%- | sed -En 's/.*\(([0-9]+)%\).*/\1/p' > $wobsock" + +riverctl map normal $mod+Shift c close +riverctl map normal $alt+Shift c close + +riverctl map normal $mod j focus-view next +riverctl map normal $mod k focus-view previous + +riverctl map normal $mod+Shift j swap next +riverctl map normal $mod+Shift k swap previous + +riverctl map normal $mod h main-ratio -0.05 +riverctl map normal $mod l main-ratio +0.05 + +riverctl map normal $mod i main-count +1 +riverctl map normal $mod d main-count -1 + +riverctl map normal $mod+Shift Space toggle-float + +riverctl map normal $mod q toggle-fullscreen +riverctl map normal $alt+Shift f toggle-fullscreen + +riverctl map normal $mod Space spawn "fnottctl dismiss" +riverctl map normal $alt Space spawn "fnottctl dismiss all" + +for i in $(seq 1 9); do + tags=$((1 << (i - 1))) + riverctl map normal $mod "$i" set-focused-tags "$tags" + riverctl map normal $mod+Shift "$i" set-view-tags "$tags" + riverctl map normal $mod+Control "$i" toggle-focused-tags "$tags" + riverctl map normal $mod+Shift+Control "$i" toggle-view-tags "$tags" + + riverctl map normal $alt "$i" set-focused-tags "$tags" + riverctl map normal $alt+Shift "$i" set-view-tags "$tags" + riverctl map normal $alt+Control "$i" toggle-focused-tags "$tags" + riverctl map normal $alt+Shift+Control "$i" toggle-view-tags "$tags" +done + +riverctl map normal $mod 0 set-focused-tags $(((1 << 32) - 1)) +riverctl map normal $mod+Shift 0 set-view-tags $(((1 << 32) - 1)) + +riverctl map normal $alt 0 set-focused-tags $(((1 << 32) - 1)) +riverctl map normal $alt+Shift 0 set-view-tags $(((1 << 32) - 1)) + +riverctl map-pointer normal $mod BTN_LEFT move-view +riverctl map-pointer normal $mod BTN_RIGHT resize-view +riverctl map-pointer normal $mod BTN_MIDDLE toggle-float + +riverctl map normal $mod period focus-output next +riverctl map normal $mod comma focus-output previous + +riverctl map normal $mod+Shift period send-to-output next +riverctl map normal $mod+Shift comma send-to-output previous + +riverctl map normal $alt Tab focus-previous-tags +riverctl map normal $alt+Shift Tab send-to-previous-tags + +riverctl map normal $mod Tab focus-previous-tags +riverctl map normal $mod+Shift Tab send-to-previous-tags blob - aa5fc4dc6d59b94591d7a932dc9c7cb8338d6afe (mode 644) blob + /dev/null --- protocol/river-control-unstable-v1.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - Copyright 2020 The River Developers - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - - - This interface allows clients to run compositor commands and receive a - success/failure response with output or a failure message respectively. - - Each command is built up in a series of add_argument requests and - executed with a run_command request. The first argument is the command - to be run. - - A complete list of commands should be made available in the man page of - the compositor. - - - - - This request indicates that the client will not use the - river_control object any more. Objects that have been created - through this instance are not affected. - - - - - - Arguments are stored by the server in the order they were sent until - the run_command request is made. - - - - - - - Execute the command built up using the add_argument request for the - given seat. - - - - - - - - - This object is created by the run_command request. Exactly one of the - success or failure events will be sent. This object will be destroyed - by the compositor after one of the events is sent. - - - - - Sent when the command has been successfully received and executed by - the compositor. Some commands may produce output, in which case the - output argument will be a non-empty string. - - - - - - - Sent when the command could not be carried out. This could be due to - sending a non-existent command, no command, not enough arguments, too - many arguments, invalid arguments, etc. - - - - - blob - /dev/null blob + e0f549887fcf0056170ad23b740d88ef41f89788 (mode 644) --- /dev/null +++ protocol/river-input-management-v1.xml @@ -0,0 +1,244 @@ + + + + SPDX-FileCopyrightText: © 2025 Isaac Freund + SPDX-License-Identifier: MIT + + 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 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. + + + + This protocol supports creating/destroying seats, assigning input devices to + seats, and configuring input devices (e.g. setting keyboard repeat rate). + + The key words "must", "must not", "required", "shall", "shall not", + "should", "should not", "recommended", "may", and "optional" in this + document are to be interpreted as described in IETF RFC 2119. + + + + + Input manager global interface. + + + + + + + + + This request indicates that the client no longer wishes to receive + events on this object. + + The Wayland protocol is asynchronous, which means the server may send + further events until the stop request is processed. The client must wait + for a river_input_manager_v1.finished event before destroying this + object. + + + + + + This event indicates that the server will send no further events on this + object. The client should destroy the object. See + river_input_manager_v1.destroy for more information. + + + + + + This request should be called after the finished event has been received + to complete destruction of the object. + + It is a protocol error to make this request before the finished event + has been received. + + If a client wishes to destroy this object it should send a + river_input_manager_v1.stop request and wait for a + river_input_manager_v1.finished event. Once the finished event is + received it is safe to destroy this object and any other objects created + through this interface. + + + + + + Create a new seat with the given name. Has no effect if a seat with the + given name already exists. + + The default seat with name "default" always exists and does not need to + be explicitly created. + + + + + + + Destroy the seat with the given name. Has no effect if a seat with the + given name does not exist. + + The default seat with name "default" cannot be destroyed and attempting + to destroy it will have no effect. + + Any input devices assigned to the destroyed seat at the time of + destruction are assigned to the default seat. + + + + + + + A new input device has been created. + + + + + + + + An input device represents a physical keyboard, mouse, touchscreen, or + drawing tablet tool. It is assigned to exactly one seat at a time. + By default, all input devices are assigned to the default seat. + + + + + + + + + + + This request indicates that the client will no longer use the input + device object and that it may be safely destroyed. + + + + + + This event indicates that the input device has been removed. + + The server will send no further events on this object and ignore any + request (other than river_input_device_v1.destroy) made after this event is + sent. The client should destroy this object with the + river_input_device_v1.destroy request to free up resources. + + + + + + + + + + + + + The type of the input device. This event is sent once when the + river_input_device_v1 object is created. The device type cannot + change during the lifetime of the object. + + + + + + + The name of the input device. This event is sent once when the + river_input_device_v1 object is created. The device name cannot + change during the lifetime of the object. + + + + + + + Assign the input device to a seat. All input devices not explicitly + assigned to a seat are considered assigned to the default seat. + + Has no effect if a seat with the given name does not exist. + + + + + + + Set repeat rate and delay for a keyboard input device. Has no effect if + the device is not a keyboard. + + Negative values for either rate or delay are illegal. A rate of zero + will disable any repeating (regardless of the value of delay). + + + + + + + + Set the scroll factor for a pointer input device. Has no effect if the + device is not a pointer. + + For example, a factor of 0.5 will make scrolling twice as slow while a + factor of 3.0 will make scrolling 3 times as fast. + + Setting a scroll factor less than 0 is a protocol error. + + + + + + + Map the input device to the given output. Has no effect if the device is + not a pointer, touch, or tablet device. + + If mapped to both an output and a rectangle, the rectangle has priority. + + Passing null clears an existing mapping. + + + + + + + Map the input device to the given rectangle in the global compositor + coordinate space. Has no effect if the device is not a pointer, touch, + or tablet device. + + If mapped to both an output and a rectangle, the rectangle has priority. + + Width and height must be greater than or equal to 0. + + Passing 0 for width or height clears an existing mapping. + + + + + + + + + + This event is sent after all information about the input device has + been sent. + + This allows changes to one or more river_input_device_v1 properties to + be seen as atomic, even if they happen via multiple events. + + + + blob - 8a1bdce0503433563da187e4e38c50a7b67ff34a (mode 644) blob + /dev/null --- protocol/river-layout-v3.xml +++ /dev/null @@ -1,196 +0,0 @@ - - - - Copyright 2020-2021 The River Developers - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - - This protocol specifies a way for clients to propose arbitrary positions - and dimensions for a set of views on a specific output of a compositor - through the river_layout_v3 object. - - Layouts are a strictly linear list of views, the position and dimensions - of which are supplied by the client. Any complex underlying data structure - a client may use when generating the layout is lost in transmission. This - is an intentional limitation. - - Additionally, this protocol allows the compositor to deliver arbitrary - user-provided commands associated with a layout to clients. A client - may use these commands to implement runtime configuration/control, or - may ignore them entirely. How the user provides these commands to the - compositor is not specified by this protocol and left to compositor policy. - - Warning! The protocol described in this file is currently in the - testing phase. Backward compatible changes may be added together with - the corresponding interface version bump. Backward incompatible changes - can only be done by creating a new major version of the extension. - - - - - A global factory for river_layout_v3 objects. - - - - - This request indicates that the client will not use the - river_layout_manager object any more. Objects that have been created - through this instance are not affected. - - - - - - This creates a new river_layout_v3 object for the given wl_output. - - All layout related communication is done through this interface. - - The namespace is used by the compositor to decide which river_layout_v3 - object will receive layout demands for the output. - - The namespace is required to be be unique per-output. Furthermore, - two separate clients may not share a namespace on separate outputs. If - these conditions are not upheld, the the namespace_in_use event will - be sent directly after creation of the river_layout_v3 object. - - - - - - - - - - This interface allows clients to receive layout demands from the - compositor for a specific output and subsequently propose positions and - dimensions of individual views. - - - - - - - - - - This request indicates that the client will not use the river_layout_v3 - object any more. - - - - - - After this event is sent, all requests aside from the destroy event - will be ignored by the server. If the client wishes to try again with - a different namespace they must create a new river_layout_v3 object. - - - - - - The compositor sends this event to inform the client that it requires a - layout for a set of views. - - The usable width and height indicate the space in which the client - can safely position views without interfering with desktop widgets - such as panels. - - The serial of this event is used to identify subsequent requests as - belonging to this layout demand. Beware that the client might need - to handle multiple layout demands at the same time. - - The server will ignore responses to all but the most recent layout - demand. Thus, clients are only required to respond to the most recent - layout_demand received. If a newer layout_demand is received before - the client has finished responding to an old demand, the client should - abort work on the old demand as any further work would be wasted. - - - - - - - - - - - This request proposes a size and position for a view in the layout demand - with matching serial. - - A client must send this request for every view that is part of the - layout demand. The number of views in the layout is given by the - view_count argument of the layout_demand event. Pushing too many or - too few view dimensions is a protocol error. - - The x and y coordinates are relative to the usable area of the output, - with (0,0) as the top left corner. - - - - - - - - - - - This request indicates that the client is done pushing dimensions - and the compositor may apply the layout. This completes the layout - demand with matching serial, any other requests sent with the serial - are a protocol error. - - The layout_name argument is a user-facing name or short description - of the layout that is being committed. The compositor may for example - display this on a status bar, though what exactly is done with it is - left to the compositor's discretion. - - The compositor is free to use this proposed layout however it chooses, - including ignoring it. - - - - - - - - This event informs the client of a command sent to it by the user. - - The semantic meaning of the command is left for the client to - decide. It is also free to ignore it entirely if it so chooses. - - A layout_demand will be sent after this event if the compositor is - currently using this layout object to arrange the output. - - If version 2 or higher of the river_layout_v3 object is bound, the - user_command_tags event is guaranteed to be sent directly before the - user_command event. - - - - - - - If version 2 or higher of the river_layout_v3 object is bound, this - event will be sent directly before every user_command event. This allows - layout generators to be aware of the active tags when a user command is - sent. This is necessary for generators wanting to keep settings on a - per-tag basis. - - - - - blob - /dev/null blob + 0167e9d27e3f0809754610119d8a1fba8a7daa89 (mode 644) --- /dev/null +++ protocol/river-layer-shell-v1.xml @@ -0,0 +1,191 @@ + + + + SPDX-FileCopyrightText: © 2025 Isaac Freund + SPDX-License-Identifier: MIT + + 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 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. + + + + This protocol allows the river-window-management-v1 window manager to + support the wlr-layer-shell-unstable-v1 protocol. + + The key words "must", "must not", "required", "shall", "shall not", + "should", "should not", "recommended", "may", and "optional" in this + document are to be interpreted as described in IETF RFC 2119. + + + + + This global interface should only be advertised to the client if the + river_window_manager_v1 global is also advertised. Binding this interface + indicates that the window manager supports layer shell. + + If the window manager does not bind this interface, the compositor should + not allow clients to map layer surfaces. This can be achieved by + closing layer surfaces immediately. + + + + + + + + + This request indicates that the client will no longer use the + river_layer_shell_v1 object. + + + + + + It is a protocol error to make this request more than once for a given + river_output_v1 object. + + + + + + + + It is a protocol error to make this request more than once for a given + river_seat_v1 object. + + + + + + + + + The lifetime of this object is tied to the corresponding river_output_v1. + This object is made inert when the river_output_v1.removed event is sent + and should be destroyed. + + + + + This request indicates that the client will no longer use the + river_layer_shell_output_v1 object and that it may be safely destroyed. + + This request should be made after the river_output_v1.removed event is + received to complete destruction of the output. + + + + + + This event indicates the area of the output remaining after subtracting + the exclusive zones of layer surfaces. Exclusive zones are a hint, the + window manager is free to ignore this area hint if it wishes. + + The x and y values are in the global coordinate space, not relative to + the position of the output. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + + + Mark this output as the default for new layer surfaces which do not + request a specific output themselves. This request overrides any + previous set_default request on any river_layer_shell_output_v1 object. + + If no set_default request is made or if the default output is destroyed, + the default output is undefined until the next set_default request. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + The lifetime of this object is tied to the corresponding river_seat_v1. + This object is made inert when the river_seat_v1.removed event is sent and + should be destroyed. + + + + + This request indicates that the client will no longer use the + river_layer_shell_seat_v1 object and that it may be safely destroyed. + + This request should be made after the river_seat_v1.removed event is + received to complete destruction of the seat. + + + + + + A layer shell surface will be given exclusive keyboard focus at the end + of the manage sequence in which this event is sent. The window manager + may want to update window decorations or similar to indicate that no + window is focused. + + Until the focus_non_exclusive or focus_none event is sent, all window + manager requests to change focus are ignored. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + A layer shell surface will be given non-exclusive keyboard focus at the + end of the manage sequence in which this event is sent. The window + manager may want to update window decorations or similar to indicate + that no window is focused. + + The window manager continues to control focus and may choose to focus a + different window/shell surface at any time. If the window manager sets + focus during the same manage sequence in which this event is sent, the + layer surface will not be focused. + + If the layer surface with non-exclusive focus is closed or the window + manager chooses to move focus away from the layer surface, a focus_none + event will be sent in the next manage sequence. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + No layer shell surface will have keyboard focus at the end of the manage + sequence in which this event is sent. The window manager may want to + return focus to whichever window last had focus, for example. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + blob - e9629dde1d615fbbb4e1f89ecf3f3db3803a75e4 (mode 644) blob + /dev/null --- protocol/river-status-unstable-v1.xml +++ /dev/null @@ -1,148 +0,0 @@ - - - - Copyright 2020 The River Developers - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - - - A global factory for objects that receive status information specific - to river. It could be used to implement, for example, a status bar. - - - - - This request indicates that the client will not use the - river_status_manager object any more. Objects that have been created - through this instance are not affected. - - - - - - This creates a new river_output_status object for the given wl_output. - - - - - - - - This creates a new river_seat_status object for the given wl_seat. - - - - - - - - - This interface allows clients to receive information about the current - windowing state of an output. - - - - - This request indicates that the client will not use the - river_output_status object any more. - - - - - - Sent once binding the interface and again whenever the tag focus of - the output changes. - - - - - - - Sent once on binding the interface and again whenever the tag state - of the output changes. - - - - - - - Sent once on binding the interface and again whenever the set of - tags with at least one urgent view changes. - - - - - - - Sent once on binding the interface should a layout name exist and again - whenever the name changes. - - - - - - - Sent when the current layout name has been removed without a new one - being set, for example when the active layout generator disconnects. - - - - - - - This interface allows clients to receive information about the current - focus of a seat. Note that (un)focused_output events will only be sent - if the client has bound the relevant wl_output globals. - - - - - This request indicates that the client will not use the - river_seat_status object any more. - - - - - - Sent on binding the interface and again whenever an output gains focus. - - - - - - - Sent whenever an output loses focus. - - - - - - - Sent once on binding the interface and again whenever the focused - view or a property thereof changes. The title may be an empty string - if no view is focused or the focused view did not set a title. - - - - - - - Sent once on binding the interface and again whenever a new mode - is entered (e.g. with riverctl enter-mode foobar). - - - - - blob - /dev/null blob + 46fad578f21b47accd3af8fa02028537b0c2d679 (mode 644) --- /dev/null +++ protocol/river-libinput-config-v1.xml @@ -0,0 +1,901 @@ + + + + SPDX-FileCopyrightText: © 2025 Isaac Freund + SPDX-License-Identifier: MIT + + 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 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. + + + + This protocol exposes libinput device configuration APIs. The libinput + documentation should be referred to for detailed information on libinput's + behavior. + + Note that the compositor will not be able to expose libinput devices through + this protocol when it does not have access to the hardware, for example when + running nested in another Wayland compositor or X11 session. + + This protocol is designed so that (hopefully) any backwards compatible + change to libinput's API can be matched with a backwards compatible change + to this protocol. + + Note: the libinput API uses floating point types (float and double in C) + which are not (yet?) natively supported by the Wayland protocol. However, + the Wayland protocol does support sending arbitrary bytes through the array + argument type. This protocol uses e.g. type="array" summary="double" to + indicate a native-endian IEEE-754 64-bit double value. + + The key words "must", "must not", "required", "shall", "shall not", + "should", "should not", "recommended", "may", and "optional" in this + document are to be interpreted as described in IETF RFC 2119. + + + + + Global interface for configuring libinput devices. This global should + only be advertised if river_input_manager_v1 is advertised as well. + + + + + + + + + + This request indicates that the client no longer wishes to receive + events on this object. + + The Wayland protocol is asynchronous, which means the server may send + further events until the stop request is processed. The client must wait + for a river_libinput_config_v1.finished event before destroying this + object. + + + + + + This event indicates that the server will send no further events on this + object. The client should destroy the object. See + river_libinput_config_v1.destroy for more information. + + + + + + This request should be called after the finished event has been received + to complete destruction of the object. + + It is a protocol error to make this request before the finished event + has been received. + + If a client wishes to destroy this object it should send a + river_libinput_config_v1.stop request and wait for a + river_libinput_config_v1.finished event. Once the finished event is + received it is safe to destroy this object and any other objects created + through this interface. + + + + + + A new libinput device has been created. Not every river_input_device_v1 + is necessarily a libinput device as well. + + + + + + + Create a acceleration config which can be applied + with river_libinput_device_v1.apply_accel_config. + + + + + + + + + In general, *_support events will be sent exactly once directly after the + river_libinput_device_v1 is created. *_default events will be sent after + *_support events if the config option is supported, and *_current events + willl be sent after the *_default events and again whenever the config + option is changed. + + + + + + + + + This request indicates that the client will no longer use the input + device object and that it may be safely destroyed. + + + + + + This event indicates that the libinput device has been removed. + + The server will send no further events on this object and ignore any + request (other than river_libinput_device_v1.destroy) made after this + event is sent. The client should destroy this object with the + river_libinput_device_v1.destroy request to free up resources. + + + + + + The river_input_device_v1 corresponding to this libinput device. + This event will always be the first event sent on the + river_libinput_device_v1 object, and it will be sent exactly once. + + + + + + + + + + + + + Supported send events modes. + + + + + + + Default send events mode. + + + + + + + Current send events mode. + + + + + + + Set the send events mode for the device. + + + + + + + + + + + + + The number of fingers supported for tap-to-click/drag. + If finger_count is 0, tap-to-click and drag are unsupported. + + + + + + + Default tap-to-click state. + + + + + + + Current tap-to-click state. + + + + + + + Configure tap-to-click on this device, with a default mapping of + 1, 2, 3 finger tap mapping to left, right, middle click, respectively. + + + + + + + + + + + + + Default tap-to-click button map. + + + + + + + Current tap-to-click button map. + + + + + + + Set the finger number to button number mapping for tap-to-click. The + default mapping on most devices is to have a 1, 2 and 3 finger tap to + map to the left, right and middle button, respectively. + + + + + + + + + + + + + Default tap-and-drag state. + + + + + + + Current tap-and-drag state. + + + + + + + Configure tap-and-drag functionality on the device. + + + + + + + + + + + + + + Default drag lock state. + + + + + + + Current drag lock state. + + + + + + + Configure drag-lock during tapping on this device. When enabled, a + finger may be lifted and put back on the touchpad and the drag process + continues. A timeout for lifting the finger is optional. When disabled, + lifting the finger during a tap-and-drag will immediately stop the drag. + See the libinput documentation for more details. + + + + + + + + The number of fingers supported for three/four finger drag. + If finger_count is less than 3, three finger drag is unsupported. + + + + + + + + + + + + + Default three finger drag state. + + + + + + + Current three finger drag state. + + + + + + + Configure three finger drag functionality for the device. + + + + + + + + A calibration matrix is supported if the supported argument is non-zero. + + + + + + + Default calibration matrix. + + + + + + + Current calibration matrix. + + + + + + + Set calibration matrix. + + + + + + + + + + + + + + + + + + + + + + Supported acceleration profiles. + + + + + + + Default acceleration profile. + + + + + + + Current acceleration profile. + + + + + + + Set the acceleration profile. + + + + + + + + Default acceleration speed. + + + + + + + Current acceleration speed. + + + + + + + Set the acceleration speed within a range of [-1, 1], where 0 is + the default acceleration for this device, -1 is the slowest acceleration + and 1 is the maximum acceleration available on this device. + + + + + + + + Apply a pointer accleration config. + + + + + + + + Natural scroll is supported if the supported argument is non-zero. + + + + + + + + + + + + Default natural scroll. + + + + + + + Current natural scroll. + + + + + + + Set natural scroll state. + + + + + + + + Left-handed mode is supported if the supported argument is non-zero. + + + + + + + + + + + + Default left-handed mode. + + + + + + + Current left-handed mode. + + + + + + + Set left-handed mode state. + + + + + + + + + + + + + + + + + + + + The click methods supported by the device. + + + + + + + Default click method. + + + + + + + Current click method. + + + + + + + Set click method. + + + + + + + + + + + + + Default clickfinger button map. + Supported if click_methods.clickfinger is supported. + + + + + + + Current clickfinger button map. + Supported if click_methods.clickfinger is supported. + + + + + + + Set clickfinger button map. + Supported if click_methods.clickfinger is supported. + + + + + + + + Middle mouse button emulation is supported if the supported argument is + non-zero. + + + + + + + + + + + + Default middle mouse button emulation. + + + + + + + Current middle mouse button emulation. + + + + + + + Set middle mouse button emulation state. + + + + + + + + + + + + + + + + + + + + + + The scroll methods supported by the device. + + + + + + + Default scroll method. + + + + + + + Current scroll method. + + + + + + + Set scroll method. + + + + + + + + Default scroll button. + Supported if scroll_methods.on_button_down is supported. + + + + + + + Current scroll button. + Supported if scroll_methods.on_button_down is supported. + + + + + + + Set scroll button. + Supported if scroll_methods.on_button_down is supported. + + + + + + + + + + + + + Default scroll button lock state. + Supported if scroll_methods.on_button_down is supported. + + + + + + + Current scroll button lock state. + Supported if scroll_methods.on_button_down is supported. + + + + + + + Set scroll button lock state. + Supported if scroll_methods.on_button_down is supported. + + + + + + + + Disable-while-typing is supported if the supported argument is + non-zero. + + + + + + + + + + + + Default disable-while-typing state. + + + + + + + Current disable-while-typing state. + + + + + + + Set disable-while-typing state. + + + + + + + + Disable-while-trackpointing is supported if the supported argument is + non-zero. + + + + + + + + + + + + Default disable-while-trackpointing state. + + + + + + + Current disable-while-trackpointing state. + + + + + + + Set disable-while-trackpointing state. + + + + + + + + Rotation is supported if the supported argument is non-zero. + + + + + + + Default rotation angle. + + + + + + + Current rotation angle. + + + + + + + Set rotation angle in degrees clockwise off the logical neutral + position. Angle must be in the range [0-360). + + + + + + + + This event is sent after all information about the libinput device has + been sent. + + This allows changes to one or more river_libinput_device_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + + The result returned by libinput on setting configuration for a device. + + + + + + + + + This request indicates that the client will no longer use the accel + config object and that it may be safely destroyed. + + + + + + + + + + + + Defines the acceleration function for a given movement type + in an acceleration configuration with custom accel profile. + + + + + + + + + + + The result returned by libinput on setting configuration for a device. + + + + + The configuration was successfully applied to the device. + + + + + + The configuration is unsupported by the device and was ignored. + + + + + + The configuration is invalid and was ignored. + + + + blob - 5095c91b817b820edda92f0f5f8d8d0aebd0a22e (mode 644) blob + /dev/null --- protocol/virtual-keyboard-unstable-v1.xml +++ /dev/null @@ -1,113 +0,0 @@ - - - - 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. - - - - - - blob - /dev/null blob + 046c9336fe5aca1f8ce93dd083f8a658b64c4e10 (mode 644) --- /dev/null +++ protocol/river-touch-gestures-v1.xml @@ -0,0 +1,338 @@ + + + + SPDX-FileCopyrightText: © 2026 Isaac Freund + SPDX-License-Identifier: MIT + + 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 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. + + + + This protocol allows the river-window-management-v1 window manager to + define touchscreen gestures and the conditions under which they should be + triggered. It also provides continuous updates on e.g. motion and pinch + scale while a gesture is in progress. + + The key words "must", "must not", "required", "shall", "shall not", + "should", "should not", "recommended", "may", and "optional" in this + document are to be interpreted as described in IETF RFC 2119. + + + + + This global interface should only be advertised to the client if the + river_window_manager_v1 global is also advertised. + + + + + + + + + This request indicates that the client will no longer use the + river_touch_gestures_v1 object. + + + + + + Create an object to manage seat-specific touch gestures state. + + It is a protocol error to make this request more than once for a given + river_seat_v1 object. + + + + + + + + + This object manages touch gesture state associated with a specific seat. + + + + + + + + + This request indicates that the client will no longer use the gestures + seat object and that it may be safely destroyed. + + + + + + The arbitration timeout defines the maximum time after the first touch + down event the compositor should wait before deciding that the touch + sequence is not a gesture and instead routing the touch input to the + respective clients through the wl_touch interface. + + This timeout should be short enough that it subjectively does not + negatively impact the responsiveness. The default if this request is + never made for the river_touch_gestures_seat_v1 is 100 milliseconds. + + Setting the timeout to 0 milliseconds effectively disables all gestures + with e.g. a finger count greater than 1 or with a non-zero motion + threshold. However, a gesture with finger count 1 and edge trigger area + defined with the river_touch_gesture_v1.set_edge request could still be + triggered even with a 0 millisecond arbitration timeout. + + This request may be made at any time but will only take effect on the + next touch sequence to be started. + + + + + + + Define a new touch gesture. + + The finger_count argument defines the number of simultaneous touch + points necessary to trigger the gesture. The finger_count must be + greater than 0. + + The maximum finger count supported is limited by touchscreen hardware. + If the requested finger count is greater than the maximum supported, + the gesture will never be triggered. + + The new gesture is not enabled until the river_touch_gesture_v1.enable + request is made during a manage sequence. + + + + + + + + + Define the requirements for a gesture to be triggered and receive + information on e.g. motion and pinch scale when triggered. + + + + + + + + + + + This request indicates that the client will no longer use the object and + that it may be safely destroyed. + + + + + + This request should be made after all initial configuration has been + completed and the window manager wishes the gesture to be able to be + triggered. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + This request may be used to temporarily disable the gesture. It may + be later re-enabled with the enable request. + + This request does not affect an in progress gesture that has already + been started. It will prevent the gesture from being triggered again. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + This event is sent when the required finger_count number of touch points + are active and all threshold requirements are met. After this event is + sent, no other gesture will be triggered until all touch points are + released and the river_touch_gesture_v1.end event is sent. + + If the threshold requirements for multiple river_touch_gesture_v1 + objects are met at the same time, it is compositor policy which gesture + is triggered. + + The wl_touch.cancel event is sent to all surfaces with touch focus, all + touch input is eaten till end of gesture. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + All touch points have been released and the gesture is ended. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + The gesture is canceled, for example because the hardware palm detection + decided that the touch input should have been ignored. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + The number of active touch points changed since the gesture was started. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + The total change in centroid position since gesture start. + + The centroid is defined as the mean position of all touch points. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + The pinch distance is defined as the diagonal of the bounding box of all + touch points. The pinch scale is defined as the ratio of the current + pinch distance to the distance at gesture start. + + Thus, the scale is initially 1.0. If the touch points are moved closer + together, the scale decreases and if the touch points are moved further + apart it increases. + + This event is only sent if there are 2 or more touch points. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + Set the minimum centroid motion required to trigger the gesture. + + Distance is measured between the current centroid and the centroid of + the touch down events. + + If this request is never made, the threshold is considered to be 0. + + The threshold argument must be greater than or equal to 0. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + + + + + + + + + Set the direction and minimum centroid motion distance required to + trigger the gesture. + + If direction is none, there is no directional motion required to trigger + the gesture and the min_distance argument is ignored. + + If this request is never made, the direction is considered to be none. + + The min_distance argument must be greater than or equal to 0. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + + Set the pinch scale threshold required to trigger the gesture. + + If threshold_scale is less than 1.0 then the scale must be less than + threshold_scale for the gesture to be triggered. If threshold_scale is + greater than 1.0 then the scale must be greater than threshold_scale for + the gesture to be triggered. + + If this request is never made, threshold_scale is considered to be 1.0. + + This request is ignored if the gesture's finger_count is less than 2. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + + + + + + + + + + Set the touchscreen edge from which the gesture must start and the + maximum allowed distance from that edge. + + If edge is none, then there is no required edge to start from and the + max_distance argument is ignored. + + If this request is never made, the edge is considered to be none. + + The max_distance argument must be greater than or equal to 0. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + blob - d62fd51e90d18247a5b8097395c3291b7099a380 (mode 644) blob + /dev/null --- protocol/wlr-layer-shell-unstable-v1.xml +++ /dev/null @@ -1,390 +0,0 @@ - - - - Copyright © 2017 Drew DeVault - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - Clients can use this interface to assign the surface_layer role to - wl_surfaces. Such surfaces are assigned to a "layer" of the output and - rendered with a defined z-depth respective to each other. They may also be - anchored to the edges and corners of a screen and specify input handling - semantics. This interface should be suitable for the implementation of - many desktop shell components, and a broad number of other applications - that interact with the desktop. - - - - - Create a layer surface for an existing surface. This assigns the role of - layer_surface, or raises a protocol error if another role is already - assigned. - - Creating a layer surface from a wl_surface which has a buffer attached - or committed is a client error, and any attempts by a client to attach - or manipulate a buffer prior to the first layer_surface.configure call - must also be treated as errors. - - After creating a layer_surface object and setting it up, the client - must perform an initial commit without any buffer attached. - The compositor will reply with a layer_surface.configure event. - The client must acknowledge it and is then allowed to attach a buffer - to map the surface. - - You may pass NULL for output to allow the compositor to decide which - output to use. Generally this will be the one that the user most - recently interacted with. - - Clients can specify a namespace that defines the purpose of the layer - surface. - - - - - - - - - - - - - - - - - These values indicate which layers a surface can be rendered in. They - are ordered by z depth, bottom-most first. Traditional shell surfaces - will typically be rendered between the bottom and top layers. - Fullscreen shell surfaces are typically rendered at the top layer. - Multiple surfaces can share a single layer, and ordering within a - single layer is undefined. - - - - - - - - - - - - - This request indicates that the client will not use the layer_shell - object any more. Objects that have been created through this instance - are not affected. - - - - - - - An interface that may be implemented by a wl_surface, for surfaces that - are designed to be rendered as a layer of a stacked desktop-like - environment. - - Layer surface state (layer, size, anchor, exclusive zone, - margin, interactivity) is double-buffered, and will be applied at the - time wl_surface.commit of the corresponding wl_surface is called. - - Attaching a null buffer to a layer surface unmaps it. - - Unmapping a layer_surface means that the surface cannot be shown by the - compositor until it is explicitly mapped again. The layer_surface - returns to the state it had right after layer_shell.get_layer_surface. - The client can re-map the surface by performing a commit without any - buffer attached, waiting for a configure event and handling it as usual. - - - - - Sets the size of the surface in surface-local coordinates. The - compositor will display the surface centered with respect to its - anchors. - - If you pass 0 for either value, the compositor will assign it and - inform you of the assignment in the configure event. You must set your - anchor to opposite edges in the dimensions you omit; not doing so is a - protocol error. Both values are 0 by default. - - Size is double-buffered, see wl_surface.commit. - - - - - - - - Requests that the compositor anchor the surface to the specified edges - and corners. If two orthogonal edges are specified (e.g. 'top' and - 'left'), then the anchor point will be the intersection of the edges - (e.g. the top left corner of the output); otherwise the anchor point - will be centered on that edge, or in the center if none is specified. - - Anchor is double-buffered, see wl_surface.commit. - - - - - - - Requests that the compositor avoids occluding an area with other - surfaces. The compositor's use of this information is - implementation-dependent - do not assume that this region will not - actually be occluded. - - A positive value is only meaningful if the surface is anchored to one - edge or an edge and both perpendicular edges. If the surface is not - anchored, anchored to only two perpendicular edges (a corner), anchored - to only two parallel edges or anchored to all edges, a positive value - will be treated the same as zero. - - A positive zone is the distance from the edge in surface-local - coordinates to consider exclusive. - - Surfaces that do not wish to have an exclusive zone may instead specify - how they should interact with surfaces that do. If set to zero, the - surface indicates that it would like to be moved to avoid occluding - surfaces with a positive exclusive zone. If set to -1, the surface - indicates that it would not like to be moved to accommodate for other - surfaces, and the compositor should extend it all the way to the edges - it is anchored to. - - For example, a panel might set its exclusive zone to 10, so that - maximized shell surfaces are not shown on top of it. A notification - might set its exclusive zone to 0, so that it is moved to avoid - occluding the panel, but shell surfaces are shown underneath it. A - wallpaper or lock screen might set their exclusive zone to -1, so that - they stretch below or over the panel. - - The default value is 0. - - Exclusive zone is double-buffered, see wl_surface.commit. - - - - - - - Requests that the surface be placed some distance away from the anchor - point on the output, in surface-local coordinates. Setting this value - for edges you are not anchored to has no effect. - - The exclusive zone includes the margin. - - Margin is double-buffered, see wl_surface.commit. - - - - - - - - - - Types of keyboard interaction possible for layer shell surfaces. The - rationale for this is twofold: (1) some applications are not interested - in keyboard events and not allowing them to be focused can improve the - desktop experience; (2) some applications will want to take exclusive - keyboard focus. - - - - - This value indicates that this surface is not interested in keyboard - events and the compositor should never assign it the keyboard focus. - - This is the default value, set for newly created layer shell surfaces. - - This is useful for e.g. desktop widgets that display information or - only have interaction with non-keyboard input devices. - - - - - Request exclusive keyboard focus if this surface is above the shell surface layer. - - For the top and overlay layers, the seat will always give - exclusive keyboard focus to the top-most layer which has keyboard - interactivity set to exclusive. If this layer contains multiple - surfaces with keyboard interactivity set to exclusive, the compositor - determines the one receiving keyboard events in an implementation- - defined manner. In this case, no guarantee is made when this surface - will receive keyboard focus (if ever). - - For the bottom and background layers, the compositor is allowed to use - normal focus semantics. - - This setting is mainly intended for applications that need to ensure - they receive all keyboard events, such as a lock screen or a password - prompt. - - - - - This requests the compositor to allow this surface to be focused and - unfocused by the user in an implementation-defined manner. The user - should be able to unfocus this surface even regardless of the layer - it is on. - - Typically, the compositor will want to use its normal mechanism to - manage keyboard focus between layer shell surfaces with this setting - and regular toplevels on the desktop layer (e.g. click to focus). - Nevertheless, it is possible for a compositor to require a special - interaction to focus or unfocus layer shell surfaces (e.g. requiring - a click even if focus follows the mouse normally, or providing a - keybinding to switch focus between layers). - - This setting is mainly intended for desktop shell components (e.g. - panels) that allow keyboard interaction. Using this option can allow - implementing a desktop shell that can be fully usable without the - mouse. - - - - - - - Set how keyboard events are delivered to this surface. By default, - layer shell surfaces do not receive keyboard events; this request can - be used to change this. - - This setting is inherited by child surfaces set by the get_popup - request. - - Layer surfaces receive pointer, touch, and tablet events normally. If - you do not want to receive them, set the input region on your surface - to an empty region. - - Keyboard interactivity is double-buffered, see wl_surface.commit. - - - - - - - This assigns an xdg_popup's parent to this layer_surface. This popup - should have been created via xdg_surface::get_popup with the parent set - to NULL, and this request must be invoked before committing the popup's - initial state. - - See the documentation of xdg_popup for more details about what an - xdg_popup is and how it is used. - - - - - - - When a configure event is received, if a client commits the - surface in response to the configure event, then the client - must make an ack_configure request sometime before the commit - request, passing along the serial of the configure event. - - If the client receives multiple configure events before it - can respond to one, it only has to ack the last configure event. - - A client is not required to commit immediately after sending - an ack_configure request - it may even ack_configure several times - before its next surface commit. - - A client may send multiple ack_configure requests before committing, but - only the last request sent before a commit indicates which configure - event the client really is responding to. - - - - - - - This request destroys the layer surface. - - - - - - The configure event asks the client to resize its surface. - - Clients should arrange their surface for the new states, and then send - an ack_configure request with the serial sent in this configure event at - some point before committing the new surface. - - The client is free to dismiss all but the last configure event it - received. - - The width and height arguments specify the size of the window in - surface-local coordinates. - - The size is a hint, in the sense that the client is free to ignore it if - it doesn't resize, pick a smaller size (to satisfy aspect ratio or - resize in steps of NxM pixels). If the client picks a smaller size and - is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the - surface will be centered on this axis. - - If the width or height arguments are zero, it means the client should - decide its own window dimension. - - - - - - - - - The closed event is sent by the compositor when the surface will no - longer be shown. The output may have been destroyed or the user may - have asked for it to be removed. Further changes to the surface will be - ignored. The client should destroy the resource after receiving this - event, and create a new surface if they so choose. - - - - - - - - - - - - - - - - - - - - - - Change the layer that the surface is rendered on. - - Layer is double-buffered, see wl_surface.commit. - - - - - blob - /dev/null blob + 2194b09a9fb1bb9421b7a5aa369406c49150dc0a (mode 644) --- /dev/null +++ protocol/river-window-management-v1.xml @@ -0,0 +1,2045 @@ + + + + SPDX-FileCopyrightText: © 2024 Isaac Freund + SPDX-License-Identifier: MIT + + 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 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. + + + + This protocol allows a single "window manager" client to determine the + window management policy of the compositor. State is globally + double-buffered allowing for frame perfect state changes involving multiple + windows. + + The key words "must", "must not", "required", "shall", "shall not", + "should", "should not", "recommended", "may", and "optional" in this + document are to be interpreted as described in IETF RFC 2119. + + + + + This global interface should only be advertised to the window manager + process. Only one window management client may be active at a time. The + compositor should use the unavailable event if necessary to enforce this. + + There are two disjoint categories of state managed by this protocol: + + Window management state influences the communication between the + compositor and individual windows (e.g. xdg_toplevels). Window management + state includes window dimensions, fullscreen state, keyboard focus, + keyboard bindings, and more. + + Rendering state only affects the rendered output of the compositor and + does not influence communication between the compositor and individual + windows. Rendering state includes the position and rendering order of + windows, shell surfaces, decoration surfaces, borders, and more. + + Window management state may only be modified by the window manager as part + of a manage sequence. A manage sequence is started with the manage_start + event and ended with the manage_finish request. It is a protocol error to + modify window management state outside of a manage sequence. + + A manage sequence is always followed by at least one render sequence. A + render sequence is started with the render_start event and ended with the + render_finish request. + + Rendering state may be modified by the window manager during a manage + sequence or a render sequence. Regardless of when the rendering state is + modified, it is applied with the next render_finish request. It is a + protocol error to modify rendering state outside of a manage or render + sequence. + + The server will start a manage sequence by sending new state and the + manage_start event as soon as possible whenever there is a change in state + that must be communicated with the window manager. + + If the window manager client needs to ensure a manage sequence is started + due to a state change the compositor is not aware of, it may send the + manage_dirty request. + + The server will start a render sequence by sending new state and the + render_start event as soon as possible whenever there is a change in + window dimensions that must be communicated with the window manager. + Multiple render sequences may be made consecutively without a manage + sequence in between, for example if a window independently changes its own + dimensions. + + To summarize, the main loop of this protocol is as follows: + + 1. The server sends events indicating all changes since the last + manage sequence followed by the manage_start event. + + 2. The client sends requests modifying window management state or + rendering state (as defined above) followed by the manage_finish + request. + + 3. The server sends new state to windows and waits for responses. + + 4. The server sends new window dimensions to the client followed by the + render_start event. + + 5. The client sends requests modifying rendering state (as defined above) + followed by the render_finish request. + + 6. If window dimensions change, loop back to step 4. + If state that requires a manage sequence changes or if the client makes + a manage_dirty request, loop back to step 1. + + For the purposes of frame perfection, the server may delay rendering new + state committed by the windows in step 3 until after step 5 is finished. + + It is a protocol error for the client to make a manage_finish or + render_finish request that violates this ordering. + + + + + + + + + + + This event indicates that window management is not available to the + client, perhaps due to another window management client already running. + The circumstances causing this event to be sent are compositor policy. + + If sent, this event is guaranteed to be the first and only event sent by + the server. + + The server will send no further events on this object. The client should + destroy this object and all objects created through this interface. + + + + + + This request indicates that the client no longer wishes to receive + events on this object. + + The Wayland protocol is asynchronous, which means the server may send + further events until the stop request is processed. The client must wait + for a river_window_manager_v1.finished event before destroying this + object. + + + + + + This event indicates that the server will send no further events on this + object. The client should destroy the object. See + river_window_manager_v1.destroy for more information. + + + + + + This request should be called after the finished event has been received + to complete destruction of the object. + + If a client wishes to destroy this object it should send a + river_window_manager_v1.stop request and wait for a + river_window_manager_v1.finished event. Once the finished event is + received it is safe to destroy this object and any other objects created + through this interface. + + + + + + This event indicates that the server has sent events indicating all + state changes since the last manage sequence. + + In response to this event, the client should make requests modifying + window management state as it chooses. Then, the client must make the + manage_finish request. + + See the description of the river_window_manager_v1 interface for a + complete overview of the manage/render sequence loop. + + + + + + This request indicates that the client has made all changes to window + management state it wishes to include in the current manage sequence and + that the server should atomically send these state changes to the + windows and continue with the manage sequence. + + After sending this request, it is a protocol error for the client to + make further changes to window management state until the next + manage_start event is received. + + See the description of the river_window_manager_v1 interface for a + complete overview of the manage/render sequence loop. + + + + + + This request ensures a manage sequence is started and that a + manage_start event is sent by the server. If this request is made during + an ongoing manage sequence, a new manage sequence will be started as + soon as the current one is completed. + + The client may want to use this request due to an internal state change + that the compositor is not aware of (e.g. a dbus event) which should + affect window management or rendering state. + + + + + + This event indicates that the server has sent all + river_window_v1.dimensions events necessary. + + In response to this event, the client should make requests modifying + rendering state as it chooses. Then, the client must make the + render_finish request. + + See the description of the river_window_manager_v1 interface for a + complete overview of the manage/render sequence loop. + + + + + + This request indicates that the client has made all changes to rendering + state it wishes to include in the current manage sequence and that the + server should atomically apply and display these state changes to the + user. + + After sending this request, it is a protocol error for the client to + make further changes to rendering state until the next manage_start or + render_start event is received, whichever comes first. + + See the description of the river_window_manager_v1 interface for a + complete overview of the manage/render sequence loop. + + + + + + This event indicates that the session has been locked. + + The window manager may wish to restrict which key bindings are available + while locked or otherwise use this information. + + If the session is currently locked when the river_window_manager_v1 + object is created, the session_locked event will be sent in the first + manage sequence. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + This event indicates that the session has been unlocked. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + A new window has been created. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + A new logical output has been created, perhaps due to a new physical + monitor being plugged in or perhaps due to a change in configuration. + + This event will be followed by river_output_v1.position and dimensions + events as well as a manage_start event after all other new state has + been sent by the server. + + + + + + + A new seat has been created. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + Create a new shell surface for window manager UI and assign the + river_shell_surface_v1 role to the surface. + + Providing a wl_surface which already has a role or already has a buffer + attached or committed is a protocol error. + + + + + + + + End the current Wayland session and exit the compositor. + All Wayland clients running in the current session, including + the window manager, will be disconnected. + + Window managers should only make this request if the user explicitly + asks to exit the Wayland session, not for example on normal window + manager termination. + + + + + + + This represents a logical window. For example, a window may correspond to + an xdg_toplevel or Xwayland window. + + A newly created window will not be displayed until the window manager + makes a propose_dimensions or fullscreen request as part of a manage + sequence, the server replies with a dimensions event as part of a render + sequence, and that render sequence is finished. + + + + + + + + + + + + This request indicates that the client will no longer use the window + object and that it may be safely destroyed. + + This request should be made after the river_window_v1.closed event or + river_window_manager_v1.finished is received to complete destruction of + the window. + + + + + + The window has been closed by the server, perhaps due to an + xdg_toplevel.close request or similar. + + The server will send no further events on this object and ignore any + request other than river_window_v1.destroy made after this event is + sent. The client should destroy this object with the + river_window_v1.destroy request to free up resources. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + Request that the window be closed. The window may ignore this request or + only close after some delay, perhaps opening a dialog asking the user to + save their work or similar. + + The server will send a river_window_v1.closed event if/when the window + has been closed. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + Get the node in the render list corresponding to the window. + + It is a protocol error to make this request more than once for a single + window. + + + + + + + This event informs the window manager of the window's preferred min/max + dimensions. These preferences are a hint, and the window manager is free + to propose dimensions outside of these bounds. + + All min/max width/height values must be strictly greater than or equal + to 0. A value of 0 indicates that the window has no preference for that + value. + + If the max_width/max_height is greater than 0, the min_width/min_height + must be strictly less than or equal to the max_width/max_height. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + + + This event indicates the dimensions of the window in the compositor's + logical coordinate space. The width and height must be strictly greater + than zero. + + Note that the dimensions of a river_window_v1 refer to the dimensions of + the window content and are unaffected by the presence of borders or + decoration surfaces. + + This event is sent as part of a render sequence before the render_start + event. + + It may be sent due to a propose_dimensions or fullscreen request in a + previous manage sequence or because a window independently decides to + change its dimensions. + + The window will not be displayed until the first dimensions event is + received and the render sequence is finished. + + + + + + + + This request proposes dimensions for the window in the compositor's + logical coordinate space. + + The width and height must be greater than or equal to zero. If the width + or height is zero the window will be allowed to decide its own + dimensions. + + The window may not take the exact dimensions proposed. The actual + dimensions taken by the window will be sent in a subsequent + river_window_v1.dimensions event. For example, a terminal emulator may + only allow dimensions that are multiple of the cell size. + + When a propose_dimensions request is made, the server must send a + dimensions event in response as soon as possible. It may not be possible + to send a dimensions event in the very next render sequence if, for + example, the window takes too long to respond to the proposed + dimensions. In this case, the server will send the dimensions event in a + future render sequence. + + Note that the dimensions of a river_window_v1 refer to the dimensions of + the window content and are unaffected by the presence of borders or + decoration surfaces. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + + Request that the window be hidden. Has no effect if the window is + already hidden. Hides any window borders and decorations as well. + + Newly created windows are considered shown unless explicitly hidden with + the hide request. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + Request that the window be shown. Has no effect if the window is not + hidden. Does not guarantee that the window is visible as it may be + completely obscured by other windows placed above it for example. + + Newly created windows are considered shown unless explicitly hidden with + the hide request. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + The window set an application ID. + + The app_id argument will be null if the window has never set an + application ID or if the window cleared its application ID. (Xwayland + windows may do this for example, though xdg-toplevels may not.) + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + The window set a title. + + The title argument will be null if the window has never set a title or + if the window cleared its title. (Xwayland windows may do this for + example, though xdg-toplevels may not.) + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + The window set a parent window. If this event is never received or if + the parent argument is null then the window has no parent. + + A surface with a parent set might be a dialog, file picker, or similar + for the parent window. + + Child windows should generally be rendered directly above their parent. + + The compositor must guarantee that there are no loops in the window + tree: a parent must not be the descendant of one of its children. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + + + + + + + Information from the window about the supported and preferred client + side/server side decoration options. + + This event may be sent multiple times over the lifetime of the window if + the window changes its preferences. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + Tell the client to use client side decoration and draw its own title + bar, borders, etc. + + This is the default if neither this request nor the use_ssd request is + ever made. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + Tell the client to use server side decoration and not draw any client + side decorations. + + This request will have no effect if the client only supports client side + decoration, see the decoration_hint event. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + + + + + + + + This request decorates the window with borders drawn by the compositor + on the specified edges of the window. Borders are drawn above the window + content. + + Corners are drawn only between borders on adjacent edges. If e.g. the + left edge has a border and the top edge does not, the border drawn on + the left edge will not extend vertically beyond the top edge of the + window. + + Borders are not drawn while the window is fullscreen. + + The color is defined by four 32-bit RGBA values. Unless specified in + another protocol extension, the RGBA values use pre-multiplied alpha. + + The valid range for the RGBA values is from 0x00000000 to 0xffffffff. + These values are interpreted as a percentage: + - 0x00000000 means 0% of the given color component + - 0xffffffff means 100% of the given color component + + Setting the edges to none or the width to 0 disables the borders. + Setting a negative width is a protocol error. + + This request completely overrides all previous set_borders requests. + Only the most recent set_borders request has an effect. + + Note that the position/dimensions of a river_window_v1 refer to the + position/dimensions of the window content and are unaffected by the + presence of borders or decoration surfaces. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + + + + + + + Inform the window that it is part of a tiled layout and adjacent to + other elements in the tiled layout on the given edges. + + The window should use this information to change the style of its client + side decorations and avoid drawing e.g. drop shadows outside of the + window dimensions on the tiled edges. + + Setting the edges argument to none informs the window that it is not + part of a tiled layout. If this request is never made, the window is + informed that it is not part of a tiled layout. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + Create a decoration surface and assign the river_decoration_v1 role to + the surface. The created decoration is placed above the window in + rendering order, see the description of river_decoration_v1. + + Providing a wl_surface which already has a role or already has a buffer + attached or committed is a protocol error. + + + + + + + + Create a decoration surface and assign the river_decoration_v1 role to + the surface. The created decoration is placed below the window in + rendering order, see the description of river_decoration_v1. + + Providing a wl_surface which already has a role or already has a buffer + attached or committed is a protocol error. + + + + + + + + This event informs the window manager that the window has requested to + be interactively moved using the pointer. The seat argument indicates the + seat for the move. + + The xdg-shell protocol for example allows windows to request that an + interactive move be started, perhaps when a client-side rendered + titlebar is dragged. + + The window manager may use the river_seat_v1.op_start_pointer request to + interactively move the window or ignore this event entirely. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + This event informs the window manager that the window has requested to + be interactively resized using the pointer. The seat argument indicates + the seat for the resize. + + The edges argument indicates which edges the window has requested to be + resized from. The edges argument will never be none and will never have + both top and bottom or both left and right edges set. + + The xdg-shell protocol for example allows windows to request that an + interactive resize be started, perhaps when the corner of client-side + rendered decorations is dragged. + + The window manager may use the river_seat_v1.op_start_pointer request to + interactively resize the window or ignore this event entirely. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + Inform the window that it is being resized. The window manager should + use this request to inform windows that are the target of an interactive + resize for example. + + The window manager remains responsible for handling the position and + dimensions of the window while it is resizing. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + Inform the window that it is no longer being resized. The window manager + should use this request to inform windows that are the target of an + interactive resize that the interactive resize has ended for example. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + + + + + + + This request informs the window of the capabilities supported by the + window manager. If the window manager, for example, ignores requests to + be maximized from the window it should not tell the window that it + supports the maximize capability. + + The window might use this information to, for example, only show a + maximize button if the window manager supports the maximize capability. + + The window manager client should use this request to set capabilities + for all new windows. If this request is never made, the compositor will + inform windows that all capabilities are supported. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + The xdg-shell protocol for example allows windows to request that a + window menu be shown, for example when the user right clicks on client + side window decorations. + + A window menu might include options to maximize or minimize the window. + + The window manager is free to ignore this request and decide what the + window menu contains if it does choose to show one. + + The x and y arguments indicate where the window requested that the + window menu be shown. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + The xdg-shell protocol for example allows windows to request to be + maximized. + + The window manager is free to honor this request using + river_window_v1.inform_maximized or ignore it. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + The xdg-shell protocol for example allows windows to request to be + unmaximized. + + The window manager is free to honor this request using + river_window_v1.inform_unmaximized or ignore it. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + Inform the window that it is maximized. The window might use this + information to adapt the style of its client-side window decorations for + example. + + The window manager remains responsible for handling the position and + dimensions of the window while it is maximized. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + Inform the window that it is unmaximized. The window might use this + information to adapt the style of its client-side window decorations for + example. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + The xdg-shell protocol for example allows windows to request that they + be made fullscreen and allows them to provide an optional output hint. + + If the output argument is null, the window has no preference and the + window manager should choose an output. + + The window manager is free to honor this request using + river_window_v1.fullscreen or ignore it. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + The xdg-shell protocol for example allows windows to request to exit + fullscreen. + + The window manager is free to honor this request using + river_window_v1.exit_fullscreen or ignore it. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + Inform the window that it is fullscreen. The window might use this + information to adapt the style of its client-side window decorations for + example. + + This request does not affect the size/position of the window or cause it + to become the only window rendered, see the river_window_v1.fullscreen + and exit_fullscreen requests for that. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + Inform the window that it is not fullscreen. The window might use this + information to adapt the style of its client-side window decorations for + example. + + This request does not affect the size/position of the window or cause it + to become the only window rendered, see the river_window_v1.fullscreen + and exit_fullscreen requests for that. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + Make the window fullscreen on the given output. If multiple windows are + fullscreen on the same output at the same time only the "top" window in + rendering order shall be displayed. If the window is already fullscreen + on a different output, the window is switched to the new output. + + All river_shell_surface_v1 objects above the top fullscreen window in + the rendering order will continue to be rendered. + + The compositor will handle the position and dimensions of the window + while it is fullscreen. The set_position and propose_dimensions requests + shall not affect the current position and dimensions of a fullscreen + window. + + When a fullscreen request is made, the server must send a dimensions + event in response as soon as possible. It may not be possible to send a + dimensions event in the very next render sequence if, for example, the + window takes too long to respond. In this case, the server will send the + dimensions event in a future render sequence. + + The compositor will clip window content, decoration surfaces, and + borders to the given output's dimensions while the window is fullscreen. + The effects of set_clip_box and set_content_clip_box are ignored while + the window is fullscreen. + + If the output on which a window is currently fullscreen is removed, the + windowing state is modified as if there were an exit_fullscreen request + made in the same manage sequence as the river_output_v1.removed event. + + This request does not inform the window that it is fullscreen, see the + river_window_v1.inform_fullscreen and inform_not_fullscreen requests. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + Make the window not fullscreen. + + The position and dimensions are undefined after this request is made + until a manage sequence in which the window manager makes the + propose_dimensions and set_position requests is completed. + + The window manager should make propose_dimensions and set_position + requests in the same manage sequence as the exit_fullscreen request for + frame perfection. + + This request does not inform the window that it is fullscreen, see the + river_window_v1.inform_fullscreen and inform_not_fullscreen requests. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + The xdg-shell protocol for example allows windows to request to be + minimized. + + The window manager is free to ignore this request, hide the window, or + do whatever else it chooses. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + Clip the window, including borders and decoration surfaces, to the box + specified by the x, y, width, and height arguments. The x/y position of + the box is relative to the top left corner of the window. + + The width and height arguments must be greater than or equal to 0. + + Setting a clip box with 0 width or height disables clipping. + + The clip box is ignored while the window is fullscreen. + + Both set_clip_box and set_content_clip_box may be enabled simultaneously. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + + + + + This event gives an unreliable PID of the process that created the + window. Obtaining this information is inherently racy due to PID reuse. + Therefore, this PID must not be used for anything security sensitive. + + Note also that a single process may create multiple windows, so there is + not necessarily a 1-to-1 mapping from PID to window. Multiple windows + may have the same PID. + + This event is sent once when the river_window_v1 is created and never + sent again. + + + + + + + Clip the content of the window, excluding borders and decoration + surfaces, to the box specified by the x, y, width, and height arguments. + The x/y position of the box is relative to the top left corner of the + window. + + Borders drawn by the compositor (see set_borders) are placed around the + intersection of the window content (as defined by the dimensions event) + and the content clip box when content clipping is enabled. + + The width and height arguments must be greater than or equal to 0. + + Setting a box with 0 width or height disables content clipping. + + The content clip box is ignored while the window is fullscreen. + + Both set_clip_box and set_content_clip_box may be enabled simultaneously. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + + + + + This event communicates the window's preferred presentation mode. + + This event will be followed by a render_start event after all other new + state has been sent by the server. + + + + + + + The identifier is a string that contains up to 32 printable ASCII bytes. + The identifier must not be an empty string. + + It is compositor policy how the identifier is generated, but the following + properties must be upheld: + + 1. The identifier must uniquely identify the window. Two windows must not + share the same identifier. + + 2. The identifier must not be reused. This avoids races around window + creation/destruction when identifiers are used in out-of-band IPC. + + If the compositor implements the ext-foreign-toplevel-list-v1 protocol, + the river_window_v1.identifier event must match the corresponding + ext_foreign_toplevel_handle_v1.identifier event. + + This event is sent once when the river_window_v1 is created and never + sent again. + + + + + + + Recommend that the window keep its dimensions within a given + maximum width/height. This recommendation is only a hint and the window + may ignore it. + + Setting the width and height to 0 indicates that there are no bounds + and is equivalent to having never made this request. + + Setting width or height to a negative value is a protocol error. + + The server should communicate this hint to an xdg_toplevel window with + the xdg_toplevel.configure_bounds event for example. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + + This event informs the window manager of the number of active screen + capture sessions for the window. + + This event is sent once when the river_window_v1 is created and again + whenever the number of capture sessions changes. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + This event informs the window manager that the window has requested to + be interactively moved using touch input. The seat argument indicates + the seat for the move and the touch point argument indicates the + transient ID of the touch point used. + + The xdg-shell protocol for example allows windows to request that an + interactive move be started, perhaps when a client-side rendered + titlebar is dragged. + + The window manager may use the river_seat_v1.op_start_touch request to + interactively move the window or ignore this event entirely. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + This event informs the window manager that the window has requested to + be interactively resized using touch input. The seat argument indicates + the seat for the resize and the touch point argument indicates the + transient ID of the touch point used. + + The edges argument indicates which edges the window has requested to be + resized from. The edges argument will never be none and will never have + both top and bottom or both left and right edges set. + + The xdg-shell protocol for example allows windows to request that an + interactive resize be started, perhaps when the corner of client-side + rendered decorations is dragged. + + The window manager may use the river_seat_v1.op_start_touch request to + interactively resize the window or ignore this event entirely. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + + + The rendering order of windows with decorations is follows: + + 1. Decorations created with get_decoration_below at the bottom + 2. Window content + 3. Borders configured with river_window_v1.set_borders + 4. Decorations created with get_decoration_above at the top + + The relative ordering of decoration surfaces above/below a window is + undefined by this protocol and left up to the compositor. + + + + + + + + + This request indicates that the client will no longer use the decoration + object and that it may be safely destroyed. + + + + + + This request sets the offset of the decoration surface from the top left + corner of the window. + + If this request is never sent, the x and y offsets are undefined by this + protocol and left up to the compositor. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + + + Synchronize application of the next wl_surface.commit request on the + decoration surface with rest of the state atomically applied with the + next river_window_manager_v1.render_finish request. + + The client must make a wl_surface.commit request on the decoration + surface after this request and before the render_finish request, failure + to do so is a protocol error. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + + The window manager might use a shell surface to display a status bar, + background image, desktop notifications, launcher, desktop menu, or + whatever else it wants. + + + + + + + + + + This request indicates that the client will no longer use the shell + surface object and that it may be safely destroyed. + + + + + + Get the node in the render list corresponding to the shell surface. + + It is a protocol error to make this request more than once for a single + shell surface. + + + + + + + Synchronize application of the next wl_surface.commit request on the + shell surface with rest of the rendering state atomically applied with + the next river_window_manager_v1.render_finish request. + + The client must make a wl_surface.commit request on the shell surface + after this request and before the render_finish request, failure to do + so is a protocol error. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + + The render list is a list of nodes that determines the rendering order of + the compositor. Nodes may correspond to windows or shell surfaces. The + relative ordering of nodes may be changed with the place_above and + place_below requests, changing the rendering order. + + The initial position of a node in the render list is undefined, the window + manager client must use the place_above or place_below request to + guarantee a specific rendering order. + + + + + This request indicates that the client will no longer use the node + object and that it may be safely destroyed. + + + + + + Set the absolute position of the node in the compositor's logical + coordinate space. The x and y coordinates may be positive or negative. + + Note that the position of a river_window_v1 refers to the position of + the window content and is unaffected by the presence of borders or + decoration surfaces. + + If this request is never sent, the position of the node is undefined by + this protocol and left up to the compositor. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + + + This request places the node above all other nodes in the compositor's + render list. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + This request places the node below all other nodes in the compositor's + render list. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + This request places the node directly above another node in the + compositor's render list. + + Attempting to place a node above itself has no effect. + + Given nodes A, B, C currently rendered in that order with C on top + and A on the bottom, the following example demonstrates the behavior + of this request and the meaning of "directly above": + + 1. A.place_above(C) -> B, C, A + 2. A.place_above(B) -> B, A, C + 3. B.place_above(A) -> A, B, C + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + + This request places the node directly below another node in the + compositor's render list. + + Attempting to place a node below itself has no effect. + + Given nodes A, B, C currently rendered in that order with C on top + and A on the bottom, the following example demonstrates the behavior + of this request and the meaning of "directly below": + + 1. C.place_below(A) -> C, A, B + 2. C.place_below(B) -> A, C, B + 3. B.place_below(C) -> A, B, C + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + + + An area in the compositor's logical coordinate space that should be + treated as a single output for window management purposes. This area may + correspond to a single physical output or multiple physical outputs in the + case of mirroring or tiled monitors depending on the hardware and + compositor configuration. + + + + + This request indicates that the client will no longer use the output + object and that it may be safely destroyed. + + This request should be made after the river_output_v1.removed event is + received to complete destruction of the output. + + + + + + This event indicates that the logical output is no longer conceptually + part of window management space. + + The server will send no further events on this object and ignore any + request (other than river_output_v1.destroy) made after this event is + sent. The client should destroy this object with the + river_output_v1.destroy request to free up resources. + + This event may be sent because a corresponding physical output has been + physically unplugged or because some output configuration has changed. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + The wl_output object corresponding to the river_output_v1. The argument + is the global name of the wl_output advertised with wl_registry.global. + + It is guaranteed that the corresponding wl_output is advertised before + this event is sent. + + This event is sent exactly once. The wl_output associated with a + river_output_v1 cannot change. It is guaranteed that there is a 1-to-1 + mapping between wl_output and river_output_v1 objects. + + The global_remove event for the corresponding wl_output may be sent + before the river_output_v1.removed event. This is due to the fact that + river_output_v1 state changes are synced to the river window management + manage sequence while changes to globals are not. + + Rationale: The window manager may need information provided by the + wl_output interface such as the name/description. It also may need the + wl_output object to start screencopy for example. + + + + + + + This event indicates the position of the output in the compositor's + logical coordinate space. The x and y coordinates may be positive or + negative. + + This event is sent once when the river_output_v1 is created and again + whenever the position changes. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + The server must guarantee that the position and dimensions events do not + cause the areas of multiple logical outputs to overlap when the + corresponding manage_start event is received. + + + + + + + + This event indicates the dimensions of the output in the compositor's + logical coordinate space. The width and height will always be strictly + greater than zero. + + This event is sent once when the river_output_v1 is created and again + whenever the dimensions change. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + The server must guarantee that the position and dimensions events do not + cause the areas of multiple logical outputs to overlap when the + corresponding manage_start event is received. + + + + + + + + + + + + + Output page-flips should be synchronized to the vertical blanking + period, eliminating tearing. This is the default presentation mode. + + + + + Output page-flips should not be synchronized to the vertical blanking + period, visual screen tearing may occur. + + + + + + + Set the preferred presentation mode of the output. The compositor should + always respect the preference of the window manager if possible. If this + request is never made, the preferred presentation mode is vsync. + + This request modifies rendering state and may only be made as part of a + manage or render sequence, see the river_window_manager_v1 description. + + + + + + + This event informs the window manager of the number of active screen + capture sessions for the output. + + This event is sent once when the river_output_v1 is created and again + whenever the number of capture sessions changes. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + This object represents a single user's collection of input devices. It + allows the window manager to route keyboard input to windows, get + high-level information about pointer input, define pointer bindings, etc. + + For keyboard bindings, see the river-xkb-bindings-v1 protocol. + + Since version 4: The cursor surface/shape set by the window manager on the + wl_pointer of this seat is used when no client has pointer focus, for + example during a pointer operation. Since the window manager is allowed to + set cursor surface/shape even when it does not have pointer focus, the + compositor must ignore the serial argument of wl_pointer.set_cursor and + wp_cursor_shape_device_v1.set_shape requests made by the window manager. + + The most recent cursor surface/shape set by the window manager is + remembered by the compositor and restored whenever no client has pointer + focus. If the window manager never sets a cursor surface/shape, the + "default" shape is used. + + + + + This request indicates that the client will no longer use the seat + object and that it may be safely destroyed. + + This request should be made after the river_seat_v1.removed event is + received to complete destruction of the seat. + + + + + + This event indicates that seat is no longer in use and should be + destroyed. + + The server will send no further events on this object and ignore any + request (other than river_seat_v1.destroy) made after this event is + sent. The client should destroy this object with the + river_seat_v1.destroy request to free up resources. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + The wl_seat object corresponding to the river_seat_v1. The argument is + the global name of the wl_seat advertised with wl_registry.global. + + It is guaranteed that the corresponding wl_seat is advertised before + this event is sent. + + This event is sent exactly once. The wl_seat associated with a + river_seat_v1 cannot change. It is guaranteed that there is a 1-to-1 + mapping between wl_seat and river_seat_v1 objects. + + The global_remove event for the corresponding wl_seat may be sent before + the river_seat_v1.removed event. This is due to the fact that + river_seat_v1 state changes are synced to the river window management + manage sequence while changes to globals are not. + + Rationale: The window manager may want to trigger window management + state changes based on normal input events received by its shell + surfaces for example. + + + + + + + Request that the compositor send keyboard input to the given window. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + Request that the compositor send keyboard input to the given shell + surface. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + Request that the compositor not send keyboard input to any client. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + The seat's pointer entered the given window's area. + + The area of a window is defined to include the area defined by the + window dimensions, borders configured using river_window_v1.set_borders, + and the input regions of decoration surfaces. In particular, it does not + include input regions of surfaces belonging to the window that extend + outside the window dimensions. + + The pointer of a seat may only enter a single window at a time. When the + pointer moves between windows, the pointer_leave event for the old + window must be sent before the pointer_enter event for the new window. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + The seat's pointer left the window for which pointer_enter was most + recently sent. See pointer_enter for details. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + A window has been interacted with beyond the pointer merely passing over + it. This event might be sent due to a pointer button press or due to a + touch/tablet tool interaction with the window. + + There are no guarantees regarding how this event is sent in relation to + the pointer_enter and pointer_leave events as the interaction may use + touch or tablet tool input. + + Rationale: this event gives window managers necessary information to + determine when to send keyboard focus, raise a window that already has + keyboard focus, etc. Rather than expose all pointer, touch, and tablet + events to window managers, a policy over mechanism approach is taken. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + A shell surface has been interacted with beyond the pointer merely + passing over it. This event might be sent due to a pointer button press + or due to a touch/tablet tool interaction with the shell_surface. + + There are no guarantees regarding how this event is sent in relation to + the pointer_enter and pointer_leave events as the interaction may use + touch or tablet tool input. + + Rationale: While the shell surface does receive all wl_pointer, + wl_touch, etc. input events for the surface directly, these events do + not necessarily trigger a manage sequence and therefore do not allow the + window manager to update focus or perform other actions in response to + the input in a race-free way. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + Start an interactive pointer operation. During the operation, op_delta + events will be sent based on pointer input. + + When all pointer buttons are released, the op_release event is sent. + + The pointer operation continues until the op_end request is made during + a manage sequence and that manage sequence is finished. + + The window manager may use this operation to implement interactive + move/resize of windows by setting the position of windows and proposing + dimensions based off of the op_delta events. + + This request is ignored if a pointer operation is already in progress. + + The compositor must ensure that no client has pointer focus from this + seat during the pointer operation. This means that the window manager + has control over the pointer's cursor surface/shape during the pointer + operation. See the river_seat_v1 description. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + This event indicates the total change in position since the start of the + pointer operation. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + All pointer buttons on the pointer device driving the operation have + been released. + + The compositor will continue to send op_delta events until the op is + ended with the op_end request. + + This event is sent at most once during a pointer operation. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + End an interactive pointer operation. + + This request is ignored if there is no pointer operation in progress. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + This enum is used to describe the keyboard modifiers that must be held + down to trigger a key binding or pointer binding. + + Note that river and wlroots use the values 2 and 16 for capslock and + numlock internally. It doesn't make sense to use locked modifiers for + bindings however so these values are not included in this enum. + + + + + + + + + + + + + Define a pointer binding in terms of a pointer button, keyboard + modifiers, and other configurable properties. + + The button argument is a Linux input event code defined in the + linux/input-event-codes.h header file (e.g. BTN_RIGHT). + + The new pointer binding is not enabled until initial configuration is + completed and the enable request is made during a manage sequence. + + + + + + + + + Set the XCursor theme for the seat. This theme is used for cursors + rendered by the compositor, but not necessarily for cursors rendered by + clients. + + Note: The window manager may also wish to set the XCURSOR_THEME and + XCURSOR_SIZE environment variable for programs it starts. + + + + + + + + The current position of the pointer in the compositor's logical + coordinate space. + + This state is special in that a change in pointer position alone must + not cause the compositor to start a manage sequence. + + Assuming the seat has a pointer, this event must be sent in every manage + sequence unless there is no change in x/y position since the last time this + event was sent. + + + + + + + + Warp the pointer to the given position in the compositor's logical + coordinate space. + + If the given position is outside the bounds of all outputs, the pointer + will be warped to the closest point inside an output instead. + + If an op_start_pointer request is made during the same manage sequence + as a pointer_warp request, the warp is applied first by the server + regardless of the relative ordering of the two requests. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + + + Start an interactive touch operation. During the operation, + op_delta_touch events will be sent based movement of the given touch + point. + + When the touch point is released, the op_release_touch event is sent and + the operation is automatically ended. + + The window manager may end the operation before the touch point is released + using the op_end_touch request. + + The window manager may use this operation to implement interactive + move/resize of windows by setting the position of windows and proposing + dimensions based off of the op_delta_touch events. + + This request is ignored if a touch operation is already in progress for + the given touch point or if the given touch point does not exist. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + This event indicates the total change in position since the start of the + operation for the given touch point. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + + + The touch point for the operation has been released and the operation is + ended. + + No further op_delta_touch events will be sent for the operation. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + The touch point for the operation has been canceled. For example, this + might happen due to palm detection determining that the touch point was + actually a accidental palm contact point all along and should have been + ignored from the start. + + The client should ideally behave as if this operation was never started. + + No further op_delta_touch events will be sent for the operation. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + End a touch operation for the given touch point. + + This request is ignored if there is no operation in progress for the + given touch point or if the operation has already been ended by the + op_release_touch event. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + + This object allows the window manager to configure a pointer binding and + receive events when the binding is triggered. + + The new pointer binding is not enabled until the enable request is made + during a manage sequence. + + Normally, all pointer button events are sent to the surface with pointer + focus by the compositor. Pointer button events that trigger a pointer + binding are not sent to the surface with pointer focus. + + If multiple pointer bindings would be triggered by a single physical + pointer event on the compositor side, it is compositor policy which + pointer binding(s) will receive press/release events or if all of the + matched pointer bindings receive press/release events. + + + + + This request indicates that the client will no longer use the pointer + binding object and that it may be safely destroyed. + + + + + + This request should be made after all initial configuration has been + completed and the window manager wishes the pointer binding to be able + to be triggered. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + This request may be used to temporarily disable the pointer binding. It + may be later re-enabled with the enable request. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + This event indicates that the pointer button triggering the binding has + been pressed. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + The compositor should wait for the manage sequence to complete before + processing further input events. This allows the window manager client + to, for example, modify key bindings and keyboard focus without racing + against future input events. The window manager should of course respond + as soon as possible as the capacity of the compositor to buffer incoming + input events is finite. + + + + + + This event indicates that the pointer button triggering the binding has + been released. + + Releasing the modifiers for the binding without releasing the pointer + button does not trigger the release event. This event is sent when the + pointer button is released, even if the modifiers have changed since the + pressed event. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + The compositor should wait for the manage sequence to complete before + processing further input events. This allows the window manager client + to, for example, modify key bindings and keyboard focus without racing + against future input events. The window manager should of course respond + as soon as possible as the capacity of the compositor to buffer incoming + input events is finite. + + + + blob - 20dbb77604a2f94c6e6116aeb343e971acca2841 (mode 644) blob + /dev/null --- protocol/wlr-output-power-management-unstable-v1.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - Copyright © 2019 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. - - - - This protocol allows clients to control power management modes - of outputs that are currently part of the compositor space. The - intent is to allow special clients like desktop shells to power - down outputs when the system is idle. - - To modify outputs not currently part of the compositor space see - wlr-output-management. - - Warning! The protocol described in this file is experimental and - backward incompatible changes may be made. Backward compatible changes - may be added together with the corresponding interface version bump. - Backward incompatible changes are done by bumping the version number in - the protocol and interface names and resetting the interface version. - Once the protocol is to be declared stable, the 'z' prefix and the - version number in the protocol and interface names are removed and the - interface version number is reset. - - - - - This interface is a manager that allows creating per-output power - management mode controls. - - - - - Create an output power management mode control that can be used to - adjust the power management mode for a given output. - - - - - - - - All objects created by the manager will still remain valid, until their - appropriate destroy request has been called. - - - - - - - This object offers requests to set the power management mode of - an output. - - - - - - - - - - - - - - Set an output's power save mode to the given mode. The mode change - is effective immediately. If the output does not support the given - mode a failed event is sent. - - - - - - - Report the power management mode change of an output. - - The mode event is sent after an output changed its power - management mode. The reason can be a client using set_mode or the - compositor deciding to change an output's mode. - This event is also sent immediately when the object is created - so the client is informed about the current power management mode. - - - - - - - This event indicates that the output power management mode control - is no longer valid. This can happen for a number of reasons, - including: - - The output doesn't support power management - - Another client already has exclusive power management mode control - for this output - - The output disappeared - - Upon receiving this event, the client should destroy this object. - - - - - - Destroys the output power management mode control object. - - - - blob - /dev/null blob + 55fb72f0c4dceb29c6500b7eaf8a149d16c96393 (mode 644) --- /dev/null +++ protocol/river-xkb-bindings-v1.xml @@ -0,0 +1,314 @@ + + + + SPDX-FileCopyrightText: © 2025 Isaac Freund + SPDX-License-Identifier: MIT + + 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 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. + + + + This protocol allows the river-window-management-v1 window manager to + define key bindings in terms of xkbcommon keysyms and other configurable + properties. + + The key words "must", "must not", "required", "shall", "shall not", + "should", "should not", "recommended", "may", and "optional" in this + document are to be interpreted as described in IETF RFC 2119. + + + + + This global interface should only be advertised to the client if the + river_window_manager_v1 global is also advertised. + + + + + + + + + This request indicates that the client will no longer use the + river_xkb_bindings_v1 object. + + + + + + Define a key binding for the given seat in terms of an xkbcommon keysym + and other configurable properties. + + The new key binding is not enabled until initial configuration is + completed and the enable request is made during a manage sequence. + + + + + + + + + + Create an object to manage seat-specific xkb bindings state. + + It is a protocol error to make this request more than once for a given + river_seat_v1 object. + + + + + + + + + This object allows the window manager to configure a xkbcommon key binding + and receive events when the key binding is triggered. + + The new key binding is not enabled until the enable request is made during + a manage sequence. + + Normally, all key events are sent to the surface with keyboard focus by + the compositor. Key events that trigger a key binding are not sent to the + surface with keyboard focus. + + If multiple key bindings would be triggered by a single physical key event + on the compositor side, it is compositor policy which key binding(s) will + receive press/release events or if all of the matched key bindings receive + press/release events. + + Key bindings might be matched by the same physical key event due to shared + keysym and modifiers. The layout override feature may also cause the same + physical key event to trigger two key bindings with different keysyms and + different layout overrides configured. + + + + + This request indicates that the client will no longer use the xkb key + binding object and that it may be safely destroyed. + + + + + + Specify an xkb layout that should be used to translate key events for + the purpose of triggering this key binding irrespective of the currently + active xkb layout. + + The layout argument is a 0-indexed xkbcommon layout number for the + keyboard that generated the key event. + + If this request is never made, the currently active xkb layout of the + keyboard that generated the key event will be used. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + This request should be made after all initial configuration has been + completed and the window manager wishes the key binding to be able to be + triggered. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + This request may be used to temporarily disable the key binding. It may + be later re-enabled with the enable request. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + This event indicates that the physical key triggering the binding has + been pressed. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + The compositor should wait for the manage sequence to complete before + processing further input events. This allows the window manager client + to, for example, modify key bindings and keyboard focus without racing + against future input events. The window manager should of course respond + as soon as possible as the capacity of the compositor to buffer incoming + input events is finite. + + + + + + This event indicates that the physical key triggering the binding has + been released. + + Releasing the modifiers for the binding without releasing the "main" + physical key that produces the bound keysym does not trigger the release + event. This event is sent when the "main" key is released, even if the + modifiers have changed since the pressed event. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + The compositor should wait for the manage sequence to complete before + processing further input events. This allows the window manager client + to, for example, modify key bindings and keyboard focus without racing + against future input events. The window manager should of course respond + as soon as possible as the capacity of the compositor to buffer incoming + input events is finite. + + + + + + This event indicates that repeating should be stopped for the binding if + the window manager has been repeating some action since the pressed + event. + + This event is generally sent when some other (possibly unbound) key is + pressed after the pressed event is sent and before the released event + is sent for this binding. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + + This object manages xkb bindings state associated with a specific seat. + + + + + This request indicates that the client will no longer use the object and + that it may be safely destroyed. + + + + + + Ensure that the next non-modifier key press and corresponding release + events for this seat are not sent to the currently focused surface. + + If the next non-modifier key press triggers a binding, the + pressed/released events are sent to the river_xkb_binding_v1 object as + usual. + + If the next non-modifier key press does not trigger a binding, the + ate_unbound_key event is sent instead. + + Rationale: the window manager may wish to implement "chorded" + keybindings where triggering a binding activates a "submap" with a + different set of keybindings. Without a way to eat the next key + press event, there is no good way for the window manager to know that it + should error out and exit the submap when a key not bound in the submap + is pressed. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + This requests cancels the effect of the latest ensure_next_key_eaten + request if no key has been eaten due to the request yet. This request + has no effect if a key has already been eaten or no + ensure_next_key_eaten was made. + + Rationale: the window manager may wish cancel an uncompleted "chorded" + keybinding after a timeout of a few seconds. Note that since this + timeout use-case requires the window manager to trigger a manage sequence + with the river_window_manager_v1.manage_dirty request it is possible that + the ate_unbound_key key event may be sent before the window manager has + a chance to make the cancel_ensure_next_key_eaten request. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + An unbound key press event was eaten due to the ensure_next_key_eaten + request. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + + + + + Request that the server send the modifiers_update event whenever a state + change occurs for at least one of the modifiers specified by the + modifiers argument. + + The window manager should make this request with the modifiers argument + set to 0 when it no longer wishes to take action based on a change in + modifiers. + + This request modifies window management state and may only be made as + part of a manage sequence, see the river_window_manager_v1 description. + + + + + + + The set of currently active modifiers for the seat changed. This event + is only sent when there is a change in state for modifiers marked as + watched using the modifiers_watch request. + + The old and new arguments convey the set of modifiers active before and + after the change. All modifiers are included in the old and new + arguments, including modifiers that are not watched. + + Since this event is only sent when there is a change in state for + watched modifiers, it follows that at least one watched modifier is + active in old but inactive in new or vice-versa. + + This event will be followed by a manage_start event after all other new + state has been sent by the server. + + The compositor should wait for the manage sequence to complete before + processing further input events. This allows the window manager client + to, for example, modify key bindings and keyboard focus without racing + against future input events. The window manager should of course respond + as soon as possible as the capacity of the compositor to buffer incoming + input events is finite. + + + + + + blob - /dev/null blob + ec04f305001e656d2c9b7ef1bbc5e1499d24fd8c (mode 644) --- /dev/null +++ protocol/river-xkb-config-v1.xml @@ -0,0 +1,317 @@ + + + + SPDX-FileCopyrightText: © 2026 Isaac Freund + SPDX-License-Identifier: MIT + + 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 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. + + + + This protocol allow a client to set the xkbcommon keymap of individual + keyboard input devices. It also allows switching between the layouts of a + keymap and toggling capslock/numlock/scrolllock state. + + The key words "must", "must not", "required", "shall", "shall not", + "should", "should not", "recommended", "may", and "optional" in this + document are to be interpreted as described in IETF RFC 2119. + + + + + Global interface for configuring xkb devices. + + This global should only be advertised if river_input_manager_v1 is + advertised as well. + + + + + + + + + + This request indicates that the client no longer wishes to receive + events on this object. + + The Wayland protocol is asynchronous, which means the server may send + further events until the stop request is processed. The client must wait + for a river_xkb_config_v1.finished event before destroying this object. + + + + + + This event indicates that the server will send no further events on this + object. The client should destroy the object. See + river_xkb_config_v1.destroy for more information. + + + + + + This request should be called after the finished event has been received + to complete destruction of the object. + + It is a protocol error to make this request before the finished event + has been received. + + If a client wishes to destroy this object it should send a + river_xkb_config_v1.stop request and wait for a + river_xkb_config_v1.finished event. Once the finished event is received + it is safe to destroy this object and any other objects created through + this interface. + + + + + + + + + + + The server must be able to mmap the fd with MAP_PRIVATE. + The server will fstat the fd to obtain the size of the keymap. + The client must not modify the contents of the fd after making this request. + The client should seal the fd with fcntl. + + + + + + + + + A new xkbcommon keyboard has been created. Not every + river_input_device_v1 is necessarily an xkbcommon keyboard as well. + + + + + + + + This object is the result of attempting to create an xkbcommon keymap. + + + + + This request indicates that the client will no longer use the keymap + object and that it may be safely destroyed. + + + + + + The keymap object was successfully created and may be used with the + river_xkb_keyboard_v1.set_keymap request. + + + + + + The compositor failed to create a keymap from the given parameters. + + It is a protocol error to use this keymap object with + river_xkb_keyboard_v1.set_keymap. + + + + + + + + This object represent a physical keyboard which has its configuration and + state managed by xkbcommon. + + + + + + + + + This request indicates that the client will no longer use the keyboard + object and that it may be safely destroyed. + + + + + + This event indicates that the xkb keyboard has been removed. + + The server will send no further events on this object and ignore any + request (other than river_xkb_keyboard_v1.destroy) made after this event + is sent. The client should destroy this object with the + river_xkb_keyboard_v1.destroy request to free up resources. + + + + + + The river_input_device_v1 corresponding to this xkb keyboard. This event + will always be the first event sent on the river_xkb_keyboard_v1 object, + and it will be sent exactly once. + + + + + + + Set the keymap for the keyboard. + + Setting a keymap will reset all layout/modifier state. + + It is a protocol error to pass a keymap object for which the + river_xkb_keymap_v1.success event was not received. + + + + + + + Set the active layout for the keyboard's keymap. Has no effect if the + layout index is out of bounds for the current keymap. + + + + + + + Set the active layout for the keyboard's keymap. Has no effect if there + is no layout with the give name for the keyboard's keymap. + + + + + + + The currently active layout index and name. The name arg may be null if + the active layout does not have a name. + + This event is sent once when the river_xkb_keyboard_v1 is created and + again whenever the layout changes. + + + + + + + + Enable capslock for the keyboard. + + + + + + Disable capslock for the keyboard. + + + + + + Capslock is currently enabled for the keyboard. + + This event is sent once when the river_xkb_keyboard_v1 is created and + again whenever the capslock state changes. + + + + + + Capslock is currently disabled for the keyboard. + + This event is sent once when the river_xkb_keyboard_v1 is created and + again whenever the capslock state changes. + + + + + + Enable numlock for the keyboard. + + + + + + Disable numlock for the keyboard. + + + + + + Numlock is currently enabled for the keyboard. + + This event is sent once when the river_xkb_keyboard_v1 is created and + again whenever the numlock state changes. + + + + + + Numlock is currently disabled for the keyboard. + + This event is sent once when the river_xkb_keyboard_v1 is created and + again whenever the numlock state changes. + + + + + + This event is sent after all information about the keyboard has been + sent. + + This allows changes to one or more river_xkb_keyboard_v1 properties to + be seen as atomic, even if they happen via multiple events. + + + + + + Enable scrolllock for the keyboard. + + + + + + Disable scrolllock for the keyboard. + + + + + + Scrolllock is currently enabled for the keyboard. + + This event is sent once when the river_xkb_keyboard_v1 is created and + again whenever the scrolllock state changes. + + + + + + Scrolllock is currently disabled for the keyboard. + + This event is sent once when the river_xkb_keyboard_v1 is created and + again whenever the scrolllock state changes. + + + + blob - /dev/null blob + 1ac03fe428344ae0996e339beb1eb8ab8d300cc3 (mode 644) --- /dev/null +++ protocol/upstream/virtual-keyboard-unstable-v1.xml @@ -0,0 +1,114 @@ + + + + + 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. + + + + + + blob - /dev/null blob + 450970e94d66ca393316758efdbc0bc5f316fc0c (mode 644) --- /dev/null +++ protocol/upstream/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,408 @@ + + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + + + + + Requests an edge for the exclusive zone to apply. The exclusive + edge will be automatically deduced from anchor points when possible, + but when the surface is anchored to a corner, it will be necessary + to set it explicitly to disambiguate, as it is not possible to deduce + which one of the two corner edges should be used. + + The edge must be one the surface is anchored to, otherwise the + invalid_exclusive_edge protocol error will be raised. + + + + + blob - /dev/null blob + 94165797df5219b4d5ccd1f1ff05a15415149a9b (mode 644) --- /dev/null +++ protocol/upstream/wlr-output-power-management-unstable-v1.xml @@ -0,0 +1,129 @@ + + + + + Copyright © 2019 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. + + + + This protocol allows clients to control power management modes + of outputs that are currently part of the compositor space. The + intent is to allow special clients like desktop shells to power + down outputs when the system is idle. + + To modify outputs not currently part of the compositor space see + wlr-output-management. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-output power + management mode controls. + + + + + Create an output power management mode control that can be used to + adjust the power management mode for a given output. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object offers requests to set the power management mode of + an output. + + + + + + + + + + + + + + Set an output's power save mode to the given mode. The mode change + is effective immediately. If the output does not support the given + mode a failed event is sent. + + + + + + + Report the power management mode change of an output. + + The mode event is sent after an output changed its power + management mode. The reason can be a client using set_mode or the + compositor deciding to change an output's mode. + This event is also sent immediately when the object is created + so the client is informed about the current power management mode. + + + + + + + This event indicates that the output power management mode control + is no longer valid. This can happen for a number of reasons, + including: + - The output doesn't support power management + - Another client already has exclusive power management mode control + for this output + - The output disappeared + + Upon receiving this event, the client should destroy this object. + + + + + + Destroys the output power management mode control object. + + + + blob - /dev/null blob + fab31ae6f8fe082b5f68754865be0632430e2bd0 (mode 644) --- /dev/null +++ ponton/Binding.zig @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const Binding = @This(); + +const std = @import("std"); +const river = @import("wayland").client.river; +const xkb = @import("xkbcommon"); + +const WindowManager = @import("WindowManager.zig"); + +const gpa = std.heap.c_allocator; + +object: *river.XkbBindingV1, +keysym: u32, +modifiers: river.SeatV1.Modifiers, +command: [:0]const u8, +mode_index: usize = 0, +manager: *WindowManager, +enabled: bool = false, + +pub fn init( + binding: *Binding, + object: *river.XkbBindingV1, + keysym: u32, + modifiers: river.SeatV1.Modifiers, + command: [:0]const u8, + mode_index: usize, + manager: *WindowManager, +) void { + binding.* = .{ + .object = object, + .keysym = keysym, + .modifiers = modifiers, + .command = command, + .mode_index = mode_index, + .manager = manager, + }; + object.setListener(*Binding, handleEvent, binding); +} + +pub fn deinit(binding: *Binding) void { + binding.object.destroy(); + gpa.free(binding.command); +} + +fn handleEvent(_: *river.XkbBindingV1, event: river.XkbBindingV1.Event, binding: *Binding) void { + switch (event) { + .pressed => { + if (binding.lockedOut()) return; + binding.manager.fireBinding(binding.command); + }, + .released, .stop_repeat => {}, + } +} + +/// While the session is locked only bindings from the locked mode fire. +fn lockedOut(binding: *const Binding) bool { + if (!binding.manager.locked) return false; + const mode = binding.manager.modes.items[binding.mode_index]; + return !std.mem.eql(u8, mode.name, "locked"); +} + +pub fn parseModifiers(text: []const u8) error{InvalidModifier}!river.SeatV1.Modifiers { + var modifiers: river.SeatV1.Modifiers = .{}; + var parts = std.mem.splitScalar(u8, text, '+'); + while (parts.next()) |name| { + if (std.mem.eql(u8, name, "None")) continue; + inline for (.{ + .{ "Shift", "shift" }, + .{ "Control", "ctrl" }, + .{ "Alt", "mod1" }, + .{ "Mod1", "mod1" }, + .{ "Mod3", "mod3" }, + .{ "Super", "mod4" }, + .{ "Mod4", "mod4" }, + .{ "Mod5", "mod5" }, + }) |pair| { + if (std.mem.eql(u8, name, pair[0])) { + @field(modifiers, pair[1]) = true; + break; + } + } else return error.InvalidModifier; + } + return modifiers; +} + +pub fn parseKeysym(text: []const u8) error{ InvalidKeysym, OutOfMemory }!u32 { + const name = try gpa.dupeZ(u8, text); + defer gpa.free(name); + const keysym = xkb.Keysym.fromName(name, .case_insensitive); + if (keysym == .NoSymbol) return error.InvalidKeysym; + return @intFromEnum(keysym); +} blob - /dev/null blob + 16a418f2d0b9bacdee691ef4a0e352393f7d9da8 (mode 644) --- /dev/null +++ ponton/Command.zig @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const Command = @This(); + +const std = @import("std"); + +pub const max_arguments = 32; +pub const max_size = 4096; + +buffer: [max_size]u8 = undefined, +slices: [max_arguments][]const u8 = undefined, +count: usize = 0, + +pub const Cursor = struct { + command: *const Command, + index: usize = 0, + + pub fn next(cursor: *Cursor) ?[]const u8 { + if (cursor.index == cursor.command.count) return null; + const argument = cursor.command.slices[cursor.index]; + cursor.index += 1; + return argument; + } +}; + +pub fn arguments(command: *const Command) Cursor { + return .{ .command = command }; +} + +test "fromText splits arguments" { + const testing = std.testing; + var command: Command = undefined; + try command.fromText("map normal Super Q close"); + try testing.expectEqual(@as(usize, 5), command.count); + try testing.expectEqualStrings("map", command.slices[0]); + try testing.expectEqualStrings("close", command.slices[4]); +} + +/// Split text on spaces into an owned command. Used to run bound +/// commands, which may be shell spawns or window-manager commands. +pub fn fromText(command: *Command, text: []const u8) error{ CommandTooLong, TooManyArguments }!void { + command.* = .{}; + if (text.len > max_size) return error.CommandTooLong; + @memcpy(command.buffer[0..text.len], text); + var parts = std.mem.splitScalar(u8, command.buffer[0..text.len], ' '); + while (parts.next()) |part| { + if (part.len == 0) continue; + if (command.count == max_arguments) return error.TooManyArguments; + command.slices[command.count] = part; + command.count += 1; + } +} + +/// Parse an argc line followed by that many newline-terminated arguments. +/// Arguments may contain spaces. Newlines in arguments are not supported. +pub fn parse(command: *Command, reader: *std.Io.Reader) !void { + command.* = .{}; + const raw_count = (try reader.takeDelimiter('\n')) orelse return error.EndOfStream; + const total = std.fmt.parseInt(usize, raw_count, 10) catch return error.InvalidCount; + if (total == 0 or total > max_arguments) return error.InvalidCount; + + var used: usize = 0; + var index: usize = 0; + while (index < total) : (index += 1) { + const argument = (try reader.takeDelimiter('\n')) orelse return error.EndOfStream; + if (used + argument.len > max_size) return error.CommandTooLong; + @memcpy(command.buffer[used..][0..argument.len], argument); + command.slices[index] = command.buffer[used..][0..argument.len]; + used += argument.len; + } + command.count = total; +} blob - /dev/null blob + f22b502616844989375f29f44ea329d5b8863faf (mode 644) --- /dev/null +++ ponton/Input.zig @@ -0,0 +1,539 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const Input = @This(); + +const std = @import("std"); +const river = @import("wayland").client.river; +const xkb = @import("xkbcommon"); + +const PointerBinding = @import("PointerBinding.zig"); +const Rule = @import("Rule.zig"); + +const gpa = std.heap.c_allocator; +const log = std.log.scoped(.ponton); + +pub const Device = struct { + owner: *Input, + input: ?*river.InputDeviceV1 = null, + libinput: ?*river.LibinputDeviceV1 = null, + keyboard: ?*river.XkbKeyboardV1 = null, + kind: ?river.InputDeviceV1.Type = null, + name: ?[]const u8 = null, +}; + +input_manager: ?*river.InputManagerV1 = null, +libinput_config: ?*river.LibinputConfigV1 = null, +xkb_config: ?*river.XkbConfigV1 = null, +devices: std.ArrayListUnmanaged(*Device) = .empty, +keymap: ?*river.XkbKeymapV1 = null, + +pub fn deinit(input: *Input) void { + for (input.devices.items) |device| { + destroyDevice(device); + gpa.destroy(device); + } + if (input.keymap) |keymap| keymap.destroy(); + input.devices.deinit(gpa); +} + +pub fn setGlobals( + input: *Input, + input_manager: ?*river.InputManagerV1, + libinput_config: ?*river.LibinputConfigV1, + xkb_config: ?*river.XkbConfigV1, +) void { + input.input_manager = input_manager; + input.libinput_config = libinput_config; + input.xkb_config = xkb_config; + if (input_manager) |object| object.setListener(*Input, handleManagerEvent, input); + if (libinput_config) |object| object.setListener(*Input, handleConfigEvent, input); + if (xkb_config) |object| object.setListener(*Input, handleXkbConfigEvent, input); +} + +fn handleManagerEvent(_: *river.InputManagerV1, event: river.InputManagerV1.Event, input: *Input) void { + switch (event) { + .finished => {}, + .input_device => |created| input.addInputDevice(created.id), + } +} + +fn handleConfigEvent(_: *river.LibinputConfigV1, event: river.LibinputConfigV1.Event, input: *Input) void { + switch (event) { + .finished => {}, + .libinput_device => |created| input.addLibinputDevice(created.id), + } +} + +fn handleXkbConfigEvent(_: *river.XkbConfigV1, event: river.XkbConfigV1.Event, input: *Input) void { + switch (event) { + .finished => {}, + .xkb_keyboard => |created| input.addKeyboard(created.id), + } +} + +fn blank(input: *Input) *Device { + const device = gpa.create(Device) catch std.process.fatal("out of memory", .{}); + device.* = .{ .owner = input }; + input.devices.append(gpa, device) catch std.process.fatal("out of memory", .{}); + return device; +} + +fn addInputDevice(input: *Input, object: *river.InputDeviceV1) void { + const device = input.blank(); + device.input = object; + object.setListener(*Device, handleInputEvent, device); +} + +fn addLibinputDevice(input: *Input, object: *river.LibinputDeviceV1) void { + const device = input.blank(); + device.libinput = object; + object.setListener(*Device, handleLibinputEvent, device); +} + +fn addKeyboard(input: *Input, object: *river.XkbKeyboardV1) void { + const device = input.blank(); + device.keyboard = object; + object.setListener(*Device, handleKeyboardEvent, device); + if (input.keymap) |keymap| object.setKeymap(keymap); +} + +fn handleInputEvent(_: *river.InputDeviceV1, event: river.InputDeviceV1.Event, device: *Device) void { + switch (event) { + .removed => device.input = null, + .type => |typed| device.kind = typed.type, + .name => |named| { + if (device.name) |old| gpa.free(old); + device.name = gpa.dupe(u8, std.mem.sliceTo(named.name, 0)) catch null; + }, + .done => {}, + } +} + +fn handleLibinputEvent(_: *river.LibinputDeviceV1, event: river.LibinputDeviceV1.Event, device: *Device) void { + switch (event) { + .removed => device.libinput = null, + .input_device => |paired| merge(device, paired.device), + else => {}, + } +} + +fn handleKeyboardEvent(_: *river.XkbKeyboardV1, event: river.XkbKeyboardV1.Event, device: *Device) void { + switch (event) { + .removed => device.keyboard = null, + .input_device => |paired| merge(device, paired.device), + else => {}, + } +} + +fn handleResult(_: *river.LibinputResultV1, event: river.LibinputResultV1.Event, _: ?*anyopaque) void { + switch (event) { + .success => {}, + .unsupported => log.warn("input setting unsupported by device", .{}), + .invalid => log.warn("input setting invalid for device", .{}), + } +} + +/// Merge a libinput or keyboard placeholder into the device owning the +/// matching input object. Placeholders exist because the pairing event may +/// arrive before or after the input device event. +fn merge(device: *Device, input_object: ?*river.InputDeviceV1) void { + const target = input_object orelse return; + if (device.input == target) return; + for (device.owner.devices.items) |other| { + if (other == device or other.input != target) continue; + if (device.libinput) |libinput| { + other.libinput = libinput; + device.libinput = null; + } + if (device.keyboard) |keyboard| { + other.keyboard = keyboard; + device.keyboard = null; + } + return; + } + device.input = target; +} + +fn destroyDevice(device: *Device) void { + if (device.input) |object| object.destroy(); + if (device.libinput) |object| object.destroy(); + if (device.keyboard) |object| object.destroy(); + if (device.name) |name| gpa.free(name); +} + +pub fn listConfigs(input: *Input) error{OutOfMemory}![]const u8 { + var text: std.ArrayListUnmanaged(u8) = .empty; + errdefer text.deinit(gpa); + for (input.devices.items) |device| { + if (device.input == null or device.libinput == null) continue; + const name = device.name orelse "(unnamed)"; + const line = try std.fmt.allocPrint(gpa, "{s}\n", .{name}); + defer gpa.free(line); + try text.appendSlice(gpa, line); + } + return text.toOwnedSlice(gpa); +} + +pub fn list(input: *Input) error{OutOfMemory}![]const u8 { + var text: std.ArrayListUnmanaged(u8) = .empty; + errdefer text.deinit(gpa); + for (input.devices.items) |device| { + if (device.input == null) continue; + const name = device.name orelse "(unnamed)"; + const kind = if (device.kind) |kind| @tagName(kind) else "unknown"; + const line = try std.fmt.allocPrint(gpa, "{s} ({s})\n", .{ name, kind }); + defer gpa.free(line); + try text.appendSlice(gpa, line); + } + return text.toOwnedSlice(gpa); +} + +pub fn setRepeat(input: *Input, rate: i32, delay: i32) void { + for (input.devices.items) |device| { + const object = device.input orelse continue; + if (device.kind != .keyboard) continue; + object.setRepeatInfo(rate, delay); + } +} + +pub fn find(input: *Input, name: []const u8) ?*Device { + for (input.devices.items) |device| { + if (device.input == null) continue; + if (device.name) |device_name| { + if (std.mem.eql(u8, device_name, name)) return device; + } + } + return null; +} + +/// Match devices by glob over the device name, with an optional category +/// filter such as touchpad, pointer, keyboard or tablet. +pub fn matchAll( + input: *Input, + pattern: []const u8, + category: ?[]const u8, + matches: *std.ArrayListUnmanaged(*Device), +) void { + for (input.devices.items) |device| { + if (device.input == null) continue; + const name = device.name orelse continue; + Rule.validate(pattern) catch continue; + if (!Rule.match(name, pattern) and !std.mem.eql(u8, pattern, "*")) continue; + if (category) |wanted| { + if (!matchCategory(device, name, wanted)) continue; + } + matches.append(gpa, device) catch continue; + } +} + +fn matchCategory(device: *Device, name: []const u8, wanted: []const u8) bool { + if (std.mem.eql(u8, wanted, "touchpad")) { + return std.ascii.indexOfIgnoreCase(name, "touchpad") != null; + } + if (std.mem.eql(u8, wanted, "pointer") or std.mem.eql(u8, wanted, "mouse")) { + return device.kind == .pointer; + } + if (std.mem.eql(u8, wanted, "keyboard")) { + return device.kind == .keyboard; + } + if (std.mem.eql(u8, wanted, "touch")) { + return device.kind == .touch; + } + if (std.mem.eql(u8, wanted, "tablet")) { + return device.kind == .tablet; + } + return false; +} + +pub const Error = error{ + UnknownSetting, + InvalidValue, + NoLibinputDevice, + CannotReadFile, + CannotParseFile, + OutOfMemory, +}; + +/// Compile a keymap from xkb rule names and push it to all keyboards. +pub fn setLayout( + input: *Input, + rules: ?[]const u8, + model: ?[]const u8, + layout: []const u8, + variant: ?[]const u8, + options: ?[]const u8, +) Error!void { + const context = xkb.Context.new(.no_flags) orelse return error.OutOfMemory; + defer context.unref(); + + const owned_rules = dupeZOpt(rules) catch return error.OutOfMemory; + defer gpaFreeOpt(owned_rules); + const owned_model = dupeZOpt(model) catch return error.OutOfMemory; + defer gpaFreeOpt(owned_model); + const owned_layout = gpa.dupeZ(u8, layout) catch return error.OutOfMemory; + defer gpa.free(owned_layout); + const owned_variant = dupeZOpt(variant) catch return error.OutOfMemory; + defer gpaFreeOpt(owned_variant); + const owned_options = dupeZOpt(options) catch return error.OutOfMemory; + defer gpaFreeOpt(owned_options); + + const names = xkb.RuleNames{ + .rules = asSentinel(owned_rules), + .model = asSentinel(owned_model), + .layout = asSentinel(owned_layout), + .variant = asSentinel(owned_variant), + .options = asSentinel(owned_options), + }; + const keymap = xkb.Keymap.newFromNames(context, &names, .no_flags) orelse { + return error.InvalidValue; + }; + defer keymap.unref(); + try input.submitKeymap(keymap); +} + +/// Read a keymap file and push it to all keyboards. +pub fn setLayoutFile(input: *Input, path: []const u8) Error!void { + const io = std.Io.Threaded.global_single_threaded.io(); + var file = std.Io.Dir.cwd().openFile(io, path, .{}) catch return error.CannotReadFile; + defer file.close(io); + var stack: [4096]u8 = undefined; + var reader = file.reader(io, &stack); + const bytes = reader.interface.allocRemaining(gpa, .limited(1024 * 1024)) catch { + return error.CannotReadFile; + }; + defer gpa.free(bytes); + + const context = xkb.Context.new(.no_flags) orelse return error.OutOfMemory; + defer context.unref(); + const keymap = xkb.Keymap.newFromBuffer(context, bytes.ptr, bytes.len, .text_v1, .no_flags) orelse { + return error.CannotParseFile; + }; + defer keymap.unref(); + try input.submitKeymap(keymap); +} + +fn dupeZOpt(text: ?[]const u8) error{OutOfMemory}!?[:0]u8 { + const bytes = text orelse return null; + const owned: [:0]u8 = try gpa.dupeZ(u8, bytes); + return owned; +} + +fn gpaFreeOpt(text: ?[:0]u8) void { + if (text) |owned| gpa.free(owned); +} + +fn asSentinel(text: ?[:0]u8) ?[*:0]const u8 { + const owned = text orelse return null; + return owned; +} + +fn submitKeymap(input: *Input, keymap: *xkb.Keymap) Error!void { + const config = input.xkb_config orelse return error.InvalidValue; + const text = keymap.getAsString(.text_v1) orelse return error.OutOfMemory; + defer std.c.free(text); + const size = std.mem.len(text); + + const fd = std.c.memfd_create("river-keymap", std.c.MFD.CLOEXEC | std.c.MFD.ALLOW_SEALING); + if (fd < 0) return error.OutOfMemory; + errdefer _ = std.c.close(fd); + + var written: usize = 0; + while (written < size) { + const n = std.c.write(fd, text[written..size].ptr, size - written); + if (n <= 0) return error.OutOfMemory; + written += @intCast(n); + } + const seals: c_uint = std.c.F.SEAL_SHRINK | std.c.F.SEAL_GROW | std.c.F.SEAL_WRITE; + _ = std.c.fcntl(fd, std.c.F.ADD_SEALS, seals); + + const pending = gpa.create(PendingKeymap) catch return error.OutOfMemory; + pending.* = .{ .owner = input, .fd = fd }; + const object = config.createKeymap(fd, .text_v1) catch { + gpa.destroy(pending); + return error.OutOfMemory; + }; + pending.object = object; + object.setListener(*PendingKeymap, handleKeymapEvent, pending); +} + +const PendingKeymap = struct { + owner: *Input, + object: *river.XkbKeymapV1 = undefined, + fd: std.c.fd_t, +}; + +fn handleKeymapEvent(_: *river.XkbKeymapV1, event: river.XkbKeymapV1.Event, pending: *PendingKeymap) void { + defer { + _ = std.c.close(pending.fd); + gpa.destroy(pending); + } + switch (event) { + .success => pending.owner.adoptKeymap(pending.object), + .failure => |failure| { + log.err("keymap rejected: {s}", .{failure.error_msg}); + pending.object.destroy(); + }, + } +} + +fn adoptKeymap(input: *Input, object: *river.XkbKeymapV1) void { + if (input.keymap) |old| old.destroy(); + input.keymap = object; + for (input.devices.items) |device| { + const keyboard = device.keyboard orelse continue; + keyboard.setKeymap(object); + } +} + +pub fn configure(device: *Device, setting: []const u8, value: ?[]const u8) Error!void { + const libinput = device.libinput orelse return error.NoLibinputDevice; + if (std.mem.eql(u8, setting, "tap")) { + try setResult(libinput.setTap(try parseToggle(river.LibinputDeviceV1.TapState, value))); + } else if (std.mem.eql(u8, setting, "tap-button-map")) { + const map: river.LibinputDeviceV1.TapButtonMap = if (eql(value, "lrm")) + .lrm + else if (eql(value, "lmr")) + .lmr + else + return error.InvalidValue; + try setResult(libinput.setTapButtonMap(map)); + } else if (std.mem.eql(u8, setting, "drag")) { + try setResult(libinput.setDrag(try parseToggle(river.LibinputDeviceV1.DragState, value))); + } else if (std.mem.eql(u8, setting, "drag-lock")) { + const state: river.LibinputDeviceV1.DragLockState = if (eql(value, "disabled")) + .disabled + else if (eql(value, "timeout")) + .enabled_timeout + else if (eql(value, "sticky")) + .enabled_sticky + else + return error.InvalidValue; + try setResult(libinput.setDragLock(state)); + } else if (std.mem.eql(u8, setting, "three-finger-drag")) { + const state: river.LibinputDeviceV1.ThreeFingerDragState = if (eql(value, "enabled")) + .enabled_3fg + else if (eql(value, "disabled")) + .disabled + else + return error.InvalidValue; + try setResult(libinput.setThreeFingerDrag(state)); + } else if (std.mem.eql(u8, setting, "natural-scroll")) { + try setResult(libinput.setNaturalScroll(try parseToggle(river.LibinputDeviceV1.NaturalScrollState, value))); + } else if (std.mem.eql(u8, setting, "left-handed")) { + try setResult(libinput.setLeftHanded(try parseToggle(river.LibinputDeviceV1.LeftHandedState, value))); + } else if (std.mem.eql(u8, setting, "click-method")) { + const method: river.LibinputDeviceV1.ClickMethod = if (eql(value, "none")) + .none + else if (eql(value, "button-areas")) + .button_areas + else if (eql(value, "clickfinger")) + .clickfinger + else + return error.InvalidValue; + try setResult(libinput.setClickMethod(method)); + } else if (std.mem.eql(u8, setting, "clickfinger-button-map")) { + const map: river.LibinputDeviceV1.ClickfingerButtonMap = if (eql(value, "lrm")) + .lrm + else if (eql(value, "lmr")) + .lmr + else + return error.InvalidValue; + try setResult(libinput.setClickfingerButtonMap(map)); + } else if (std.mem.eql(u8, setting, "middle-emulation")) { + try setResult(libinput.setMiddleEmulation(try parseToggle(river.LibinputDeviceV1.MiddleEmulationState, value))); + } else if (std.mem.eql(u8, setting, "scroll-method")) { + const method: river.LibinputDeviceV1.ScrollMethod = if (eql(value, "none")) + .no_scroll + else if (eql(value, "two-finger")) + .two_finger + else if (eql(value, "edge")) + .edge + else if (eql(value, "button")) + .on_button_down + else + return error.InvalidValue; + try setResult(libinput.setScrollMethod(method)); + } else if (std.mem.eql(u8, setting, "scroll-button")) { + const text = value orelse return error.InvalidValue; + const button = PointerBinding.parseButton(text) catch return error.InvalidValue; + try setResult(libinput.setScrollButton(button)); + } else if (std.mem.eql(u8, setting, "scroll-button-lock")) { + try setResult(libinput.setScrollButtonLock(try parseToggle(river.LibinputDeviceV1.ScrollButtonLockState, value))); + } else if (std.mem.eql(u8, setting, "dwt")) { + try setResult(libinput.setDwt(try parseToggle(river.LibinputDeviceV1.DwtState, value))); + } else if (std.mem.eql(u8, setting, "dwtp")) { + try setResult(libinput.setDwtp(try parseToggle(river.LibinputDeviceV1.DwtpState, value))); + } else if (std.mem.eql(u8, setting, "send-events")) { + const mode: river.LibinputDeviceV1.SendEventsModes = if (eql(value, "enabled")) + .{} + else if (eql(value, "disabled")) + .{ .disabled = true } + else if (eql(value, "disabled-on-external-mouse")) + .{ .disabled_on_external_mouse = true } + else + return error.InvalidValue; + try setResult(libinput.setSendEvents(mode)); + } else if (std.mem.eql(u8, setting, "pointer-accel") or std.mem.eql(u8, setting, "accel-speed")) { + const text = value orelse return error.InvalidValue; + const speed = std.fmt.parseFloat(f64, text) catch return error.InvalidValue; + if (speed < -1.0 or speed > 1.0) return error.InvalidValue; + const Fn = @typeInfo(@TypeOf(river.LibinputDeviceV1.setAccelSpeed)).@"fn"; + const ArrayT = @typeInfo(Fn.params[1].type.?).pointer.child; + var storage: [1]f64 = .{speed}; + var array: ArrayT = undefined; + array.size = @sizeOf(f64); + array.alloc = @sizeOf(f64); + array.data = @ptrCast(&storage); + try setResult(libinput.setAccelSpeed(&array)); + } else if (std.mem.eql(u8, setting, "scroll-factor")) { + const input_object = device.input orelse return error.NoLibinputDevice; + const text = value orelse return error.InvalidValue; + const factor = std.fmt.parseFloat(f64, text) catch return error.InvalidValue; + if (factor < 0.0) return error.InvalidValue; + const Fn = @typeInfo(@TypeOf(river.InputDeviceV1.setScrollFactor)).@"fn"; + const FixedT = Fn.params[1].type.?; + const fixed: FixedT = @enumFromInt(@as(i32, @intFromFloat(factor * 256.0))); + input_object.setScrollFactor(fixed); + } else if (std.mem.eql(u8, setting, "accel-profile")) { + const profile: river.LibinputDeviceV1.AccelProfile = if (eql(value, "none")) + .none + else if (eql(value, "flat")) + .flat + else if (eql(value, "adaptive")) + .adaptive + else + return error.InvalidValue; + try setResult(libinput.setAccelProfile(profile)); + } else return error.UnknownSetting; +} + +fn eql(value: ?[]const u8, expected: []const u8) bool { + const text = value orelse return false; + if (std.mem.eql(u8, text, expected)) return true; + return dashed(text, expected); +} + +/// Accept dashes and underscores interchangeably, and enable alongside +/// enabled for boolean settings. +fn dashed(text: []const u8, expected: []const u8) bool { + if (text.len != expected.len) return false; + for (text, expected) |a, b| { + const left = if (a == '-') '_' else a; + const right = if (b == '-') '_' else b; + if (left != right) return false; + } + return true; +} + +fn parseToggle(comptime T: type, value: ?[]const u8) error{InvalidValue}!T { + const text = value orelse return error.InvalidValue; + if (eql(text, "enabled") or std.mem.eql(u8, text, "enable")) return .enabled; + if (eql(text, "disabled") or std.mem.eql(u8, text, "disable")) return .disabled; + return error.InvalidValue; +} + +fn setResult(result: anytype) Error!void { + const object = result catch return error.OutOfMemory; + object.setListener(?*anyopaque, handleResult, null); +} blob - /dev/null blob + 8fcbb87a5b6349e740682b054b594db916649adc (mode 644) --- /dev/null +++ ponton/Ipc.zig @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const Ipc = @This(); + +const std = @import("std"); +const Io = std.Io; + +const Command = @import("Command.zig"); +const WindowManager = @import("WindowManager.zig"); + +const socket_name = "ponton.sock"; +const command_size_max = 4096; + +io: Io, +server: Io.net.Server, +path: []const u8, + +pub fn init(ipc: *Ipc, io: Io, runtime_dir: []const u8) !void { + const gpa = std.heap.c_allocator; + const path = try std.fs.path.join(gpa, &.{ runtime_dir, socket_name }); + errdefer gpa.free(path); + + const address = try Io.net.UnixAddress.init(path); + const server = try address.listen(io, .{ .kernel_backlog = 8 }); + ipc.* = .{ + .io = io, + .server = server, + .path = path, + }; +} + +pub fn deinit(ipc: *Ipc) void { + ipc.server.deinit(ipc.io); + std.Io.Dir.deleteFileAbsolute(ipc.io, ipc.path) catch {}; + std.heap.c_allocator.free(ipc.path); +} + +pub fn handle(ipc: *const Ipc) std.posix.fd_t { + return ipc.server.socket.handle; +} + +pub fn accept(ipc: *Ipc, manager: *WindowManager) !void { + var client = ipc.server.accept(ipc.io) catch |err| switch (err) { + error.WouldBlock => return, + else => return err, + }; + defer client.close(ipc.io); + + var read_buffer: [command_size_max]u8 = undefined; + var reader = client.reader(ipc.io, &read_buffer); + var command: Command = undefined; + command.parse(&reader.interface) catch |err| switch (err) { + error.ReadFailed, error.EndOfStream => return, + else => return err, + }; + + var write_buffer: [128]u8 = undefined; + var writer = client.writer(ipc.io, &write_buffer); + if (manager.answerQuery(&command)) |response| { + defer std.heap.c_allocator.free(response); + try writer.interface.writeAll(response); + try writer.interface.flush(); + return; + } else |err| switch (err) { + error.UnknownCommand => {}, + error.OutOfMemory => { + try writer.interface.print("error: {s}\n", .{@errorName(err)}); + try writer.interface.flush(); + return; + }, + } + manager.queueCommand(&command) catch |err| { + try writer.interface.print("error: {s}\n", .{@errorName(err)}); + try writer.interface.flush(); + return; + }; + try writer.interface.writeAll("ok\n"); + try writer.interface.flush(); +} blob - /dev/null blob + 8f1c90c547338ae5f7080156274c8587aa649106 (mode 644) --- /dev/null +++ ponton/Layout.zig @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const std = @import("std"); + +pub const Location = enum { + top, + right, + bottom, + left, +}; + +pub const Attach = enum { + top, + bottom, +}; + +pub const Rect = struct { + x: i32, + y: i32, + width: i32, + height: i32, +}; + +pub const Placement = struct { + x: i32, + y: i32, + width: i32, + height: i32, +}; + +/// Split the output rectangle between a main area and a secondary area. +/// Windows divide their area evenly, the last window takes any remainder. +/// Pure and total: safe for any count, including zero. +pub fn compute( + placements: []Placement, + output: Rect, + main_count: u32, + ratio: f32, + location: Location, +) void { + const count = placements.len; + if (count == 0) return; + + const clamped_ratio = std.math.clamp(ratio, 0.1, 0.9); + const mains: usize = @min(@as(usize, @intCast(main_count)), count); + + const vertical = location == .left or location == .right; + const output_width = @as(f32, @floatFromInt(output.width)); + const output_height = @as(f32, @floatFromInt(output.height)); + + const main_rect, const secondary_rect = switch (location) { + .left => blk: { + const main_width: i32 = @intFromFloat(output_width * clamped_ratio); + break :blk .{ + Rect{ .x = output.x, .y = output.y, .width = main_width, .height = output.height }, + Rect{ + .x = output.x + main_width, + .y = output.y, + .width = output.width - main_width, + .height = output.height, + }, + }; + }, + .right => blk: { + const main_width: i32 = @intFromFloat(output_width * clamped_ratio); + break :blk .{ + Rect{ + .x = output.x + output.width - main_width, + .y = output.y, + .width = main_width, + .height = output.height, + }, + Rect{ .x = output.x, .y = output.y, .width = output.width - main_width, .height = output.height }, + }; + }, + .top => blk: { + const main_height: i32 = @intFromFloat(output_height * clamped_ratio); + break :blk .{ + Rect{ .x = output.x, .y = output.y, .width = output.width, .height = main_height }, + Rect{ + .x = output.x, + .y = output.y + main_height, + .width = output.width, + .height = output.height - main_height, + }, + }; + }, + .bottom => blk: { + const main_height: i32 = @intFromFloat(output_height * clamped_ratio); + break :blk .{ + Rect{ + .x = output.x, + .y = output.y + output.height - main_height, + .width = output.width, + .height = main_height, + }, + Rect{ .x = output.x, .y = output.y, .width = output.width, .height = output.height - main_height }, + }; + }, + }; + + split(placements[0..mains], main_rect, vertical); + split(placements[mains..], secondary_rect, vertical); +} + +fn split(placements: []Placement, area: Rect, vertical: bool) void { + if (placements.len == 0) return; + if (vertical) { + const height_each = @divFloor(area.height, @as(i32, @intCast(placements.len))); + for (placements[0 .. placements.len - 1], 0..) |*placement, i| { + placement.* = .{ + .x = area.x, + .y = area.y + @as(i32, @intCast(i)) * height_each, + .width = area.width, + .height = height_each, + }; + } + const last = &placements[placements.len - 1]; + last.* = .{ + .x = area.x, + .y = area.y + @as(i32, @intCast(placements.len - 1)) * height_each, + .width = area.width, + .height = area.height - height_each * @as(i32, @intCast(placements.len - 1)), + }; + } else { + const width_each = @divFloor(area.width, @as(i32, @intCast(placements.len))); + for (placements[0 .. placements.len - 1], 0..) |*placement, i| { + placement.* = .{ + .x = area.x + @as(i32, @intCast(i)) * width_each, + .y = area.y, + .width = width_each, + .height = area.height, + }; + } + const last = &placements[placements.len - 1]; + last.* = .{ + .x = area.x + @as(i32, @intCast(placements.len - 1)) * width_each, + .y = area.y, + .width = area.width - width_each * @as(i32, @intCast(placements.len - 1)), + .height = area.height, + }; + } +} + +test "single window fills output" { + var placements: [1]Placement = undefined; + compute(&placements, .{ .x = 0, .y = 0, .width = 1920, .height = 1080 }, 1, 0.6, .left); + try std.testing.expectEqualDeep(Placement{ .x = 0, .y = 0, .width = 1152, .height = 1080 }, placements[0]); +} + +test "no windows is a no-op" { + compute(&.{}, .{ .x = 0, .y = 0, .width = 1920, .height = 1080 }, 1, 0.6, .left); +} + +test "two windows split main and secondary" { + var placements: [2]Placement = undefined; + compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 800 }, 1, 0.6, .left); + try std.testing.expectEqualDeep(Placement{ .x = 0, .y = 0, .width = 600, .height = 800 }, placements[0]); + try std.testing.expectEqualDeep(Placement{ .x = 600, .y = 0, .width = 400, .height = 800 }, placements[1]); +} + +test "right location mirrors areas" { + var placements: [2]Placement = undefined; + compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 800 }, 1, 0.6, .right); + try std.testing.expectEqualDeep(Placement{ .x = 400, .y = 0, .width = 600, .height = 800 }, placements[0]); + try std.testing.expectEqualDeep(Placement{ .x = 0, .y = 0, .width = 400, .height = 800 }, placements[1]); +} + +test "main count divides main area evenly" { + var placements: [3]Placement = undefined; + compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 900 }, 2, 0.5, .left); + try std.testing.expectEqualDeep(Placement{ .x = 0, .y = 0, .width = 500, .height = 450 }, placements[0]); + try std.testing.expectEqualDeep(Placement{ .x = 0, .y = 450, .width = 500, .height = 450 }, placements[1]); + try std.testing.expectEqualDeep(Placement{ .x = 500, .y = 0, .width = 500, .height = 900 }, placements[2]); +} + +test "ratio clamps to sane bounds" { + var placements: [1]Placement = undefined; + compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 800 }, 1, 99.0, .left); + try std.testing.expectEqualDeep(Placement{ .x = 0, .y = 0, .width = 900, .height = 800 }, placements[0]); +} blob - /dev/null blob + 582b02950eeccc3b34208a770b6ec9c8e3b3551a (mode 644) --- /dev/null +++ ponton/Output.zig @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const Output = @This(); + +const river = @import("wayland").client.river; + +const Layout = @import("Layout.zig"); + +object: *river.OutputV1, +layer_output: ?*river.LayerShellOutputV1 = null, +wl_name: ?u32 = null, +x: i32 = 0, +y: i32 = 0, +width: i32 = 1, +height: i32 = 1, +removed: bool = false, +tags: u32 = 1, +previous_tags: u32 = 1, +attach_mode: ?Layout.Attach = null, +usable: Layout.Rect = .{ .x = 0, .y = 0, .width = 1, .height = 1 }, +usable_valid: bool = false, +main_count: u32 = 1, +main_ratio: f32 = 0.6, +main_location: Layout.Location = .left, + +pub fn init(output: *Output, object: *river.OutputV1) void { + output.* = .{ .object = object }; + object.setListener(*Output, handleEvent, output); +} + +pub fn bindLayerShell(output: *Output, layer_shell: *river.LayerShellV1) void { + if (output.layer_output != null) return; + const layer_output = layer_shell.getOutput(output.object) catch return; + output.layer_output = layer_output; + layer_output.setListener(*Output, handleLayerEvent, output); +} + +pub fn deinit(output: *Output) void { + if (output.layer_output) |layer_output| { + layer_output.destroy(); + output.layer_output = null; + } + output.object.destroy(); +} + +fn handleLayerEvent(_: *river.LayerShellOutputV1, event: river.LayerShellOutputV1.Event, output: *Output) void { + switch (event) { + .non_exclusive_area => |area| { + output.usable = .{ .x = area.x, .y = area.y, .width = area.width, .height = area.height }; + output.usable_valid = true; + }, + } +} + +fn handleEvent(_: *river.OutputV1, event: river.OutputV1.Event, output: *Output) void { + switch (event) { + .removed => output.removed = true, + .wl_output => |named| output.wl_name = named.name, + .position => |position| { + output.x = position.x; + output.y = position.y; + }, + .dimensions => |dimensions| { + output.width = dimensions.width; + output.height = dimensions.height; + }, + .capture_sessions => {}, + } +} blob - /dev/null blob + 73419ec37018a85182cfde5ac1752ba6f4b3a05a (mode 644) --- /dev/null +++ ponton/PointerBinding.zig @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const PointerBinding = @This(); + +const std = @import("std"); +const river = @import("wayland").client.river; + +const Seat = @import("Seat.zig"); +const WindowManager = @import("WindowManager.zig"); + +const gpa = std.heap.c_allocator; + +pub const Action = enum { + move, + resize, + command, +}; + +object: *river.PointerBindingV1, +button: u32, +modifiers: river.SeatV1.Modifiers, +action: Action, +command: [:0]const u8, +manager: *WindowManager, +seat: *Seat, +enabled: bool = false, + +pub fn init( + binding: *PointerBinding, + object: *river.PointerBindingV1, + button: u32, + modifiers: river.SeatV1.Modifiers, + action: Action, + command: [:0]const u8, + manager: *WindowManager, + seat: *Seat, +) void { + binding.* = .{ + .object = object, + .button = button, + .modifiers = modifiers, + .action = action, + .command = command, + .manager = manager, + .seat = seat, + }; + object.setListener(*PointerBinding, handleEvent, binding); +} + +pub fn deinit(binding: *PointerBinding) void { + binding.object.destroy(); + gpa.free(binding.command); +} + +fn handleEvent(_: *river.PointerBindingV1, event: river.PointerBindingV1.Event, binding: *PointerBinding) void { + switch (event) { + .pressed => { + if (binding.action == .command) { + binding.manager.fireBinding(binding.command); + } else { + binding.manager.requestPointerOp(binding.seat, binding); + } + }, + .released => {}, + } +} + +pub fn parseButton(text: []const u8) error{InvalidButton}!u32 { + inline for (.{ + .{ "BTN_LEFT", 0x110 }, + .{ "BTN_RIGHT", 0x111 }, + .{ "BTN_MIDDLE", 0x112 }, + .{ "BTN_SIDE", 0x113 }, + .{ "BTN_EXTRA", 0x114 }, + .{ "BTN_FORWARD", 0x115 }, + .{ "BTN_BACK", 0x116 }, + .{ "BTN_TASK", 0x117 }, + }) |pair| { + if (std.mem.eql(u8, text, pair[0])) return pair[1]; + } + return std.fmt.parseInt(u32, text, 10) catch return error.InvalidButton; +} blob - /dev/null blob + 3a6e26aada4cccd2ddd0a7a3941b71f6ddd011c5 (mode 644) --- /dev/null +++ ponton/Rule.zig @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const std = @import("std"); +const mem = std.mem; + +/// Validate a glob, returning error.InvalidGlob if it is empty, "**" or has a +/// '*' at any position other than the first and/or last byte. +pub fn validate(glob: []const u8) error{InvalidGlob}!void { + switch (glob.len) { + 0 => return error.InvalidGlob, + 1 => {}, + 2 => if (glob[0] == '*' and glob[1] == '*') return error.InvalidGlob, + else => if (mem.indexOfScalar(u8, glob[1 .. glob.len - 1], '*') != null) { + return error.InvalidGlob; + }, + } +} + +/// Return true if text is matched by glob. The glob must be valid, +/// see validate(). +pub fn match(text: []const u8, glob: []const u8) bool { + if (glob.len == 1) { + return glob[0] == '*' or mem.eql(u8, text, glob); + } + + const suffix_match = glob[0] == '*'; + const prefix_match = glob[glob.len - 1] == '*'; + + if (suffix_match and prefix_match) { + return mem.indexOf(u8, text, glob[1 .. glob.len - 1]) != null; + } else if (suffix_match) { + return mem.endsWith(u8, text, glob[1..]); + } else if (prefix_match) { + return mem.startsWith(u8, text, glob[0 .. glob.len - 1]); + } else { + return mem.eql(u8, text, glob); + } +} + +pub const Action = enum { + float, + fullscreen, + ssd, + csd, + tags, +}; + +pub const Rule = struct { + app_id_glob: []const u8, + title_glob: []const u8, + action: Action, + tags: u32 = 0, +}; + +test validate { + const testing = std.testing; + + try validate("*"); + try validate("a"); + try validate("*a"); + try validate("a*"); + try validate("*a*"); + try validate("ab"); + + try testing.expectError(error.InvalidGlob, validate("")); + try testing.expectError(error.InvalidGlob, validate("**")); + try testing.expectError(error.InvalidGlob, validate("***")); + try testing.expectError(error.InvalidGlob, validate("a*c")); + try testing.expectError(error.InvalidGlob, validate("ab*c*")); + try testing.expectError(error.InvalidGlob, validate("*ab*c")); + try testing.expectError(error.InvalidGlob, validate("**a")); + try testing.expectError(error.InvalidGlob, validate("abc**")); +} + +test match { + const testing = std.testing; + + try testing.expect(match("", "*")); + try testing.expect(match("a", "*")); + try testing.expect(match("a", "*a*")); + try testing.expect(match("a", "a*")); + try testing.expect(match("a", "*a")); + try testing.expect(match("a", "a")); + + try testing.expect(!match("a", "b")); + try testing.expect(!match("a", "*b*")); + try testing.expect(!match("a", "b*")); + try testing.expect(!match("a", "*b")); + + try testing.expect(match("ab", "*ab*")); + try testing.expect(match("ab", "*b")); + try testing.expect(match("ab", "a*")); + + try testing.expect(!match("ab", "b*")); + try testing.expect(!match("ab", "*a")); + try testing.expect(!match("ab", "ac")); + try testing.expect(!match("ab", "*ac*")); +} + +test "rule matches app id and title together" { + const testing = std.testing; + try testing.expect(match("float-term", "float*")); + try testing.expect(!match("other", "float*")); + try testing.expect(match("foo", "*")); +} blob - /dev/null blob + 9bc1479083d94e745e02c2460e076a27f2c10b34 (mode 644) --- /dev/null +++ ponton/Seat.zig @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const Seat = @This(); + +const std = @import("std"); +const river = @import("wayland").client.river; + +const Binding = @import("Binding.zig"); +const Output = @import("Output.zig"); +const PointerBinding = @import("PointerBinding.zig"); +const Window = @import("Window.zig"); + +const gpa = std.heap.c_allocator; + +pub const PointerOp = struct { + action: PointerBinding.Action, + window: *Window, + start_x: i32, + start_y: i32, + start_width: i32, + start_height: i32, + pending_delta: ?struct { dx: i32, dy: i32 } = null, + released: bool = false, +}; + +object: *river.SeatV1, +layer_seat: ?*river.LayerShellSeatV1 = null, +focused_output: ?*Output = null, +hovered_window: ?*river.WindowV1 = null, +last_focused: ?*river.WindowV1 = null, +focused_window: ?*river.WindowV1 = null, +removed: bool = false, +mode_index: usize = 0, +bindings: std.ArrayListUnmanaged(*Binding) = .empty, +pointer_bindings: std.ArrayListUnmanaged(*PointerBinding) = .empty, +op: ?PointerOp = null, +pending_op: ?*PointerBinding = null, + +pub fn init(seat: *Seat, object: *river.SeatV1) void { + seat.* = .{ .object = object }; + object.setListener(*Seat, handleEvent, seat); +} + +pub fn bindLayerShell(seat: *Seat, layer_shell: *river.LayerShellV1) void { + if (seat.layer_seat != null) return; + const layer_seat = layer_shell.getSeat(seat.object) catch return; + seat.layer_seat = layer_seat; + layer_seat.setListener(*Seat, handleLayerSeatEvent, seat); +} + +fn handleLayerSeatEvent(_: *river.LayerShellSeatV1, event: river.LayerShellSeatV1.Event, seat: *Seat) void { + _ = seat; + switch (event) { + .focus_exclusive, .focus_non_exclusive, .focus_none => {}, + } +} + +pub fn deinit(seat: *Seat) void { + if (seat.layer_seat) |layer_seat| { + layer_seat.destroy(); + seat.layer_seat = null; + } + for (seat.bindings.items) |binding| { + binding.deinit(); + gpa.destroy(binding); + } + for (seat.pointer_bindings.items) |binding| { + binding.deinit(); + gpa.destroy(binding); + } + seat.bindings.deinit(gpa); + seat.pointer_bindings.deinit(gpa); + seat.object.destroy(); +} + +pub fn focus(seat: *Seat, window: *river.WindowV1) void { + seat.focused_window = window; + seat.object.focusWindow(window); +} + +fn handleEvent(_: *river.SeatV1, event: river.SeatV1.Event, seat: *Seat) void { + switch (event) { + .removed => seat.removed = true, + .pointer_enter => |entered| seat.hovered_window = entered.window, + .pointer_leave => seat.hovered_window = null, + .op_delta => |delta| { + if (seat.op) |*op| op.pending_delta = .{ .dx = delta.dx, .dy = delta.dy }; + }, + .op_release => { + if (seat.op) |*op| op.released = true; + }, + .window_interaction, + .shell_surface_interaction, + .pointer_position, + .op_delta_touch, + .op_release_touch, + .op_cancel_touch, + .wl_seat, + => {}, + } +} blob - /dev/null blob + e74508734ecfb3a8dc8616f58ea0a7b13114044f (mode 644) --- /dev/null +++ ponton/Spawn.zig @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const std = @import("std"); + +const c = std.c; +const gpa = std.heap.c_allocator; + +/// Run command with `/bin/sh -c` in a detached grandchild process. +pub fn run(command: []const u8) void { + const owned = gpa.dupeZ(u8, command) catch return; + defer gpa.free(owned); + + const shell: [*:0]const u8 = "/bin/sh"; + const child_args = [_:null]?[*:0]const u8{ shell, "-c", owned, null }; + + const pid: c.pid_t = forkPid() orelse return; + if (pid == 0) { + _ = c.setsid(); + + const grandchild: c.pid_t = forkPid() orelse c._exit(1); + if (grandchild == 0) { + _ = c.execve(shell, &child_args, c.environ); + c._exit(1); + } + c._exit(0); + } + + var status: c_int = 0; + while (true) { + switch (c.errno(c.waitpid(pid, &status, 0))) { + .SUCCESS => return, + .INTR => continue, + else => return, + } + } +} + +fn forkPid() ?c.pid_t { + const rc = c.fork(); + switch (c.errno(rc)) { + .SUCCESS => {}, + else => return null, + } + return @intCast(rc); +} blob - /dev/null blob + ca96ede9b537ba09cda065acbc1f48a96c687156 (mode 644) --- /dev/null +++ ponton/Window.zig @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const Window = @This(); + +const std = @import("std"); +const river = @import("wayland").client.river; + +const Output = @import("Output.zig"); + +object: ?*river.WindowV1, +node: ?*river.NodeV1 = null, +closed: bool = false, +tags: u32 = 1, +placed: bool = false, +output: ?*Output = null, +float: bool = false, +fullscreen: bool = false, +focused: bool = false, +app_id: ?[]const u8 = null, +title: ?[]const u8 = null, +width: i32 = 800, +height: i32 = 600, +x: i32 = 0, +y: i32 = 0, + +pub fn init(window: *Window, object: *river.WindowV1) void { + window.* = .{ .object = object }; + object.setListener(*Window, handleEvent, window); +} + +pub fn deinit(window: *Window) void { + if (window.node) |node| { + node.destroy(); + window.node = null; + } + if (window.object) |object| { + object.destroy(); + window.object = null; + } + if (window.app_id) |app_id| std.heap.c_allocator.free(app_id); + if (window.title) |title| std.heap.c_allocator.free(title); + window.app_id = null; + window.title = null; +} + +pub fn configure(window: *Window, width: i32, height: i32) void { + const object = window.object orelse return; + window.width = @max(width, 1); + window.height = @max(height, 1); + object.proposeDimensions(window.width, window.height); +} + +pub fn render(window: *Window, border_width: i32, border_color: [4]u32) void { + const object = window.object orelse return; + const node = window.node orelse return; + node.setPosition(window.x, window.y); + object.setBorders(.{ .top = true, .bottom = true, .left = true, .right = true }, border_width, border_color[0], border_color[1], border_color[2], border_color[3]); + object.show(); + node.placeTop(); +} + +fn handleEvent(_: *river.WindowV1, event: river.WindowV1.Event, window: *Window) void { + switch (event) { + .closed => { + window.closed = true; + if (window.node) |node| { + node.destroy(); + window.node = null; + } + }, + .dimensions => |dimensions| { + window.width = dimensions.width; + window.height = dimensions.height; + }, + .app_id => |app_id| { + if (window.app_id) |old| std.heap.c_allocator.free(old); + window.app_id = if (app_id.app_id) |id| + std.heap.c_allocator.dupe(u8, std.mem.sliceTo(id, 0)) catch null + else + null; + }, + .title => |title| { + if (window.title) |old| std.heap.c_allocator.free(old); + window.title = if (title.title) |name| + std.heap.c_allocator.dupe(u8, std.mem.sliceTo(name, 0)) catch null + else + null; + }, + .dimensions_hint, + .parent, + .decoration_hint, + .pointer_move_requested, + .pointer_resize_requested, + .show_window_menu_requested, + .maximize_requested, + .unmaximize_requested, + .fullscreen_requested, + .exit_fullscreen_requested, + .minimize_requested, + .unreliable_pid, + .presentation_hint, + .identifier, + .capture_sessions, + .touch_move_requested, + .touch_resize_requested, + => {}, + } +} blob - /dev/null blob + 7a03dc69da3b3b0f3cc710e088e1a5d59700081a (mode 644) --- /dev/null +++ ponton/WindowManager.zig @@ -0,0 +1,1742 @@ +// SPDX-FileCopyrightText: © 2026 The River Developers +// SPDX-License-Identifier: GPL-3.0-only + +const WindowManager = @This(); + +const std = @import("std"); +const wl = @import("wayland").client.wl; +const river = @import("wayland").client.river; + +const Binding = @import("Binding.zig"); +const Command = @import("Command.zig"); +const Input = @import("Input.zig"); +const Layout = @import("Layout.zig"); +const Output = @import("Output.zig"); +const PointerBinding = @import("PointerBinding.zig"); +const Rule = @import("Rule.zig"); +const Seat = @import("Seat.zig"); +const Spawn = @import("Spawn.zig"); +const Window = @import("Window.zig"); + +const gpa = std.heap.c_allocator; +const log = std.log.scoped(.ponton); + +const max_pending = 64; + +const BindingSpec = struct { + keysym: u32, + modifiers: river.SeatV1.Modifiers, + command: [:0]const u8, +}; + +const PointerSpec = struct { + button: u32, + modifiers: river.SeatV1.Modifiers, + action: PointerBinding.Action, + command: [:0]const u8, +}; + +const Mode = struct { + name: []const u8, + specs: std.ArrayListUnmanaged(BindingSpec) = .empty, + pointer_specs: std.ArrayListUnmanaged(PointerSpec) = .empty, +}; + +pub const WlOutput = struct { + global_name: u32, + object: *wl.Output, + connector: ?[]const u8 = null, +}; + +windows: std.ArrayListUnmanaged(*Window) = .empty, +outputs: std.ArrayListUnmanaged(*Output) = .empty, +seats: std.ArrayListUnmanaged(*Seat) = .empty, +wl_outputs: ?*std.ArrayListUnmanaged(*WlOutput) = null, +input: Input = .{}, +modes: std.ArrayListUnmanaged(Mode) = .empty, +rules: std.ArrayListUnmanaged(Rule.Rule) = .empty, +pending: std.ArrayListUnmanaged(Command) = .empty, +object: *river.WindowManagerV1, +default_attach: Layout.Attach = .top, +spawn_tagmask: ?u32 = null, +locked: bool = false, +border_width: i32 = 2, +border_focused: [4]u32 = expandHex(0x93a1a1ff), +border_unfocused: [4]u32 = expandHex(0x586e75ff), +focus_follows_cursor: bool = false, +cursor_warp: bool = false, +cursor_theme_name: ?[]const u8 = null, +cursor_theme_size: u32 = 24, +xkb: ?*river.XkbBindingsV1 = null, +layer_shell: ?*river.LayerShellV1 = null, +layer_default_set: bool = false, + +pub fn init( + manager: *WindowManager, + object: *river.WindowManagerV1, + xkb: ?*river.XkbBindingsV1, + layer_shell: ?*river.LayerShellV1, +) void { + manager.* = .{ .object = object, .xkb = xkb, .layer_shell = layer_shell }; + object.setListener(*WindowManager, handleEvent, manager); + _ = manager.ensureMode("normal"); +} + +pub fn deinit(manager: *WindowManager) void { + for (manager.windows.items) |window| { + window.deinit(); + gpa.destroy(window); + } + for (manager.outputs.items) |output| { + output.deinit(); + gpa.destroy(output); + } + for (manager.seats.items) |seat| { + seat.deinit(); + gpa.destroy(seat); + } + if (manager.cursor_theme_name) |name| gpa.free(name); + for (manager.modes.items) |*mode| { + for (mode.specs.items) |spec| gpa.free(spec.command); + for (mode.pointer_specs.items) |spec| gpa.free(spec.command); + mode.specs.deinit(gpa); + mode.pointer_specs.deinit(gpa); + gpa.free(mode.name); + } + for (manager.rules.items) |rule| { + gpa.free(rule.app_id_glob); + gpa.free(rule.title_glob); + } + manager.input.deinit(); + manager.windows.deinit(gpa); + manager.outputs.deinit(gpa); + manager.seats.deinit(gpa); + manager.modes.deinit(gpa); + manager.rules.deinit(gpa); + manager.pending.deinit(gpa); +} + +/// Queue a command for the next manage sequence. Protocol state may only +/// change inside manage_start, so commands never execute inline. +/// Unknown commands fail here so callers get an error synchronously +/// instead of a late warning with an earlier ok reply. +pub fn queueCommand(manager: *WindowManager, command: *const Command) error{ QueueFull, UnknownCommand }!void { + var arguments = command.arguments(); + const name = arguments.next() orelse return error.UnknownCommand; + if (!isKnownCommand(name)) return error.UnknownCommand; + if (manager.pending.items.len == max_pending) return error.QueueFull; + manager.pending.append(gpa, command.*) catch return error.QueueFull; + manager.object.manageDirty(); +} + +fn isKnownCommand(name: []const u8) bool { + for ([_][]const u8{ + "exit", "close", "focus-view", + "set-focused-tags", "toggle-focused-tags", "set-view-tags", + "toggle-view-tags", "focus-output", "send-to-output", + "declare-mode", "enter-mode", "map", + "map-pointer", "unmap", "unmap-pointer", + "main-ratio", "main-count", "main-location", + "zoom", "spawn", "swap", + "move", "resize", "snap", + "toggle-float", "toggle-fullscreen", "rule-add", + "rule-del", "set-repeat", "input", + "keyboard-layout", "keyboard-layout-file", "attach-mode", + "default-attach-mode", "output-attach-mode", "spawn-tagmask", + "focus-previous-tags", "send-to-previous-tags", "border-width", + "border-color-focused", "border-color-unfocused", "focus-follows-cursor", + "set-cursor-warp", "xcursor-theme", + }) |known| { + if (std.mem.eql(u8, name, known)) return true; + } + return false; +} + +test "all commands validate" { + const testing = std.testing; + const known = [_][]const u8{ + "exit", "close", "focus-view", "set-focused-tags", "toggle-focused-tags", + "set-view-tags", "toggle-view-tags", "focus-output", "send-to-output", "declare-mode", + "enter-mode", "map", "map-pointer", "unmap", "unmap-pointer", + "main-ratio", "main-count", "main-location", "zoom", "spawn", + "swap", "move", "resize", "snap", "toggle-float", + "toggle-fullscreen", "rule-add", "rule-del", "set-repeat", "input", + "keyboard-layout", "keyboard-layout-file", "attach-mode", "default-attach-mode", "output-attach-mode", + "spawn-tagmask", "focus-previous-tags", "send-to-previous-tags", "border-width", "border-color-focused", + "border-color-unfocused", "focus-follows-cursor", "set-cursor-warp", "xcursor-theme", + }; + for (known) |name| try testing.expect(isKnownCommand(name)); + try testing.expect(!isKnownCommand("frobnicate")); + try testing.expect(!isKnownCommand("")); +} + +/// Answer a read-only query inline. Queries never touch protocol state, +/// so they run outside manage sequences and reply immediately. +pub fn answerQuery(manager: *WindowManager, command: *const Command) error{ OutOfMemory, UnknownCommand }![]const u8 { + var arguments = command.arguments(); + const name = arguments.next() orelse return error.UnknownCommand; + if (arguments.next() != null) return error.UnknownCommand; + if (std.mem.eql(u8, name, "list-inputs")) return manager.input.list(); + if (std.mem.eql(u8, name, "list-input-configs")) return manager.input.listConfigs(); + if (std.mem.eql(u8, name, "list-rules")) return manager.listRules(); + return error.UnknownCommand; +} + +fn execute(manager: *WindowManager, command: *const Command) Error!void { + var arguments = command.arguments(); + const name = arguments.next() orelse return error.UnknownCommand; + + if (std.mem.eql(u8, name, "exit")) { + manager.object.exitSession(); + return; + } + if (std.mem.eql(u8, name, "close")) { + if (manager.seats.items.len == 0) return; + const window = manager.seats.items[0].focused_window orelse return; + window.close(); + return; + } + if (std.mem.eql(u8, name, "focus-view")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + if (std.mem.eql(u8, raw, "next")) { + manager.cycleFocus(true); + } else if (std.mem.eql(u8, raw, "previous")) { + manager.cycleFocus(false); + } else { + manager.focusDirection(raw) catch return error.InvalidArgument; + } + return; + } + if (std.mem.eql(u8, name, "attach-mode")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + manager.default_attach = parseAttach(raw) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "default-attach-mode")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + manager.default_attach = parseAttach(raw) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "output-attach-mode")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const output = manager.focusedOutput() orelse return; + output.attach_mode = parseAttach(raw) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "spawn-tagmask")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + manager.spawn_tagmask = std.fmt.parseInt(u32, raw, 10) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "focus-previous-tags")) { + if (arguments.next() != null) return error.InvalidArgument; + const output = manager.focusedOutput() orelse return; + const current = output.tags; + output.tags = output.previous_tags; + output.previous_tags = current; + return; + } + if (std.mem.eql(u8, name, "send-to-previous-tags")) { + if (arguments.next() != null) return error.InvalidArgument; + const output = manager.focusedOutput() orelse return; + const window = manager.focusedWindow() orelse return; + window.tags = output.previous_tags; + return; + } + if (std.mem.eql(u8, name, "set-focused-tags")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const mask = std.fmt.parseInt(u32, raw, 10) catch return error.InvalidArgument; + const output = manager.focusedOutput() orelse return; + output.previous_tags = output.tags; + output.tags = mask; + return; + } + if (std.mem.eql(u8, name, "toggle-focused-tags")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const mask = std.fmt.parseInt(u32, raw, 10) catch return error.InvalidArgument; + const output = manager.focusedOutput() orelse return; + output.previous_tags = output.tags; + output.tags ^= mask; + return; + } + if (std.mem.eql(u8, name, "toggle-view-tags")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const mask = std.fmt.parseInt(u32, raw, 10) catch return error.InvalidArgument; + const window = manager.focusedWindow() orelse return; + window.tags ^= mask; + return; + } + if (std.mem.eql(u8, name, "focus-output")) { + const target = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const output = manager.resolveOutput(target) orelse return error.InvalidArgument; + if (manager.seats.items.len == 0) return; + manager.seats.items[0].focused_output = output; + return; + } + if (std.mem.eql(u8, name, "send-to-output")) { + var current_tags = false; + var target = arguments.next() orelse return error.InvalidArgument; + if (std.mem.eql(u8, target, "-current-tags")) { + current_tags = true; + target = arguments.next() orelse return error.InvalidArgument; + } + if (arguments.next() != null) return error.InvalidArgument; + const output = manager.resolveOutput(target) orelse return error.InvalidArgument; + const window = manager.focusedWindow() orelse return; + window.output = output; + if (current_tags) window.tags = output.tags; + return; + } + if (std.mem.eql(u8, name, "set-view-tags")) { + const raw = arguments.next() orelse return error.InvalidArgument; + const mask = std.fmt.parseInt(u32, raw, 10) catch return error.InvalidArgument; + if (manager.seats.items.len == 0) return; + const seat = manager.seats.items[0]; + const window = seat.focused_window orelse return; + const target = manager.findWindow(window) orelse return; + target.tags = mask; + return; + } + if (std.mem.eql(u8, name, "declare-mode")) { + const mode_name = arguments.next() orelse return error.InvalidArgument; + _ = manager.ensureMode(mode_name); + return; + } + if (std.mem.eql(u8, name, "enter-mode")) { + const mode_name = arguments.next() orelse return error.InvalidArgument; + const index = manager.modeIndex(mode_name) orelse return error.InvalidArgument; + for (manager.seats.items) |seat| manager.setSeatMode(seat, index); + return; + } + if (std.mem.eql(u8, name, "map")) { + try manager.defineBinding(&arguments); + return; + } + if (std.mem.eql(u8, name, "map-pointer")) { + try manager.definePointerBinding(&arguments); + return; + } + if (std.mem.eql(u8, name, "unmap")) { + const mode_name = arguments.next() orelse return error.InvalidArgument; + const modifiers_raw = arguments.next() orelse return error.InvalidArgument; + const keysym_raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const modifiers = Binding.parseModifiers(modifiers_raw) catch return error.InvalidModifier; + const keysym = Binding.parseKeysym(keysym_raw) catch return error.InvalidArgument; + manager.removeBinding(mode_name, keysym, modifiers); + return; + } + if (std.mem.eql(u8, name, "unmap-pointer")) { + const mode_name = arguments.next() orelse return error.InvalidArgument; + const modifiers_raw = arguments.next() orelse return error.InvalidArgument; + const button_raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const modifiers = Binding.parseModifiers(modifiers_raw) catch return error.InvalidModifier; + const button = PointerBinding.parseButton(button_raw) catch return error.InvalidButton; + manager.removePointerBinding(mode_name, button, modifiers); + return; + } + if (std.mem.eql(u8, name, "main-ratio")) { + const raw = arguments.next() orelse return error.InvalidArgument; + manager.adjustRatio(raw) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "main-count")) { + const raw = arguments.next() orelse return error.InvalidArgument; + manager.adjustCount(raw) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "main-location")) { + const raw = arguments.next() orelse return error.InvalidArgument; + manager.setLocation(raw) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "zoom")) { + manager.zoom(); + return; + } + if (std.mem.eql(u8, name, "spawn")) { + const shell_command = try manager.joinCommand(&arguments); + defer gpa.free(shell_command); + if (shell_command.len == 0) return error.InvalidArgument; + Spawn.run(shell_command); + return; + } + if (std.mem.eql(u8, name, "swap")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const forward = if (std.mem.eql(u8, raw, "next")) + true + else if (std.mem.eql(u8, raw, "previous")) + false + else + return error.InvalidArgument; + manager.swap(forward); + return; + } + if (std.mem.eql(u8, name, "move")) { + const direction = arguments.next() orelse return error.InvalidArgument; + const delta_raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const delta = std.fmt.parseInt(i32, delta_raw, 10) catch return error.InvalidArgument; + manager.moveFocused(direction, delta) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "resize")) { + const axis = arguments.next() orelse return error.InvalidArgument; + const delta_raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const delta = std.fmt.parseInt(i32, delta_raw, 10) catch return error.InvalidArgument; + manager.resizeFocused(axis, delta) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "border-width")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const width = std.fmt.parseInt(i32, raw, 10) catch return error.InvalidArgument; + if (width < 0) return error.InvalidArgument; + manager.border_width = width; + return; + } + if (std.mem.eql(u8, name, "border-color-focused")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + manager.border_focused = parseRgba(raw) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "border-color-unfocused")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + manager.border_unfocused = parseRgba(raw) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "focus-follows-cursor")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + if (std.mem.eql(u8, raw, "always") or std.mem.eql(u8, raw, "normal")) { + manager.focus_follows_cursor = true; + } else if (std.mem.eql(u8, raw, "disabled")) { + manager.focus_follows_cursor = false; + } else return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "set-cursor-warp")) { + const raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + if (std.mem.eql(u8, raw, "on-focus")) { + manager.cursor_warp = true; + } else if (std.mem.eql(u8, raw, "disabled")) { + manager.cursor_warp = false; + } else return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "xcursor-theme")) { + const theme = arguments.next() orelse return error.InvalidArgument; + const size_raw = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + const size = std.fmt.parseInt(u32, size_raw, 10) catch return error.InvalidArgument; + if (manager.cursor_theme_name) |old| gpa.free(old); + manager.cursor_theme_name = gpa.dupe(u8, theme) catch return error.OutOfMemory; + manager.cursor_theme_size = size; + for (manager.seats.items) |seat| manager.applyCursorTheme(seat); + return; + } + if (std.mem.eql(u8, name, "snap")) { + const direction = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + manager.snapFocused(direction) catch return error.InvalidArgument; + return; + } + if (std.mem.eql(u8, name, "set-repeat")) { + const rate_raw = arguments.next() orelse return error.InvalidArgument; + const delay_raw = arguments.next() orelse return error.InvalidArgument; + const rate = std.fmt.parseInt(i32, rate_raw, 10) catch return error.InvalidArgument; + const delay = std.fmt.parseInt(i32, delay_raw, 10) catch return error.InvalidArgument; + if (rate < 0 or delay < 0) return error.InvalidArgument; + manager.input.setRepeat(rate, delay); + return; + } + if (std.mem.eql(u8, name, "keyboard-layout")) { + var rules: ?[]const u8 = null; + var model: ?[]const u8 = null; + var variant: ?[]const u8 = null; + var options: ?[]const u8 = null; + var layout: ?[]const u8 = null; + while (arguments.next()) |argument| { + if (std.mem.eql(u8, argument, "-rules")) { + rules = arguments.next() orelse return error.InvalidArgument; + } else if (std.mem.eql(u8, argument, "-model")) { + model = arguments.next() orelse return error.InvalidArgument; + } else if (std.mem.eql(u8, argument, "-variant")) { + variant = arguments.next() orelse return error.InvalidArgument; + } else if (std.mem.eql(u8, argument, "-options")) { + options = arguments.next() orelse return error.InvalidArgument; + } else if (layout == null) { + layout = argument; + } else return error.InvalidArgument; + } + const chosen = layout orelse return error.InvalidArgument; + Input.setLayout(&manager.input, rules, model, chosen, variant, options) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => return error.InvalidArgument, + }; + return; + } + if (std.mem.eql(u8, name, "keyboard-layout-file")) { + const path = arguments.next() orelse return error.InvalidArgument; + if (arguments.next() != null) return error.InvalidArgument; + Input.setLayoutFile(&manager.input, path) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => return error.InvalidArgument, + }; + return; + } + if (std.mem.eql(u8, name, "input")) { + const pattern = arguments.next() orelse return error.InvalidArgument; + var setting = arguments.next() orelse return error.InvalidArgument; + var value = arguments.next(); + var extra = arguments.next(); + var category: ?[]const u8 = null; + if (extra != null) { + category = setting; + setting = value orelse return error.InvalidArgument; + value = extra; + extra = arguments.next(); + } + if (extra != null) return error.InvalidArgument; + var matches: std.ArrayListUnmanaged(*Input.Device) = .empty; + defer matches.deinit(gpa); + manager.input.matchAll(pattern, category, &matches); + if (matches.items.len == 0) return error.InvalidArgument; + for (matches.items) |device| { + Input.configure(device, setting, value) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => return error.InvalidArgument, + }; + } + return; + } + if (std.mem.eql(u8, name, "toggle-float")) { + if (manager.seats.items.len == 0) return; + const focused = manager.seats.items[0].focused_window orelse return; + const window = manager.findWindow(focused) orelse return; + window.float = !window.float; + return; + } + if (std.mem.eql(u8, name, "toggle-fullscreen")) { + if (manager.seats.items.len == 0) return; + const focused = manager.seats.items[0].focused_window orelse return; + const window = manager.findWindow(focused) orelse return; + window.fullscreen = !window.fullscreen; + if (window.object) |object| { + if (window.fullscreen) { + const target = window.output orelse manager.focusedOutput(); + if (target) |output| object.fullscreen(output.object); + object.informFullscreen(); + } else { + object.exitFullscreen(); + object.informNotFullscreen(); + } + } + return; + } + if (std.mem.eql(u8, name, "rule-add")) { + try manager.defineRule(&arguments, false); + return; + } + if (std.mem.eql(u8, name, "rule-del")) { + try manager.defineRule(&arguments, true); + return; + } + return error.UnknownCommand; +} + +/// rule-add [-app-id glob] [-title glob] [value] +/// rule-del [-app-id glob] [-title glob] +fn defineRule(manager: *WindowManager, arguments: *Command.Cursor, remove: bool) Error!void { + var app_id_glob: []const u8 = "*"; + var title_glob: []const u8 = "*"; + var action_raw: ?[]const u8 = null; + var value_raw: ?[]const u8 = null; + while (arguments.next()) |argument| { + if (std.mem.eql(u8, argument, "-app-id")) { + app_id_glob = arguments.next() orelse return error.InvalidArgument; + } else if (std.mem.eql(u8, argument, "-title")) { + title_glob = arguments.next() orelse return error.InvalidArgument; + } else if (action_raw == null) { + action_raw = argument; + } else if (value_raw == null) { + value_raw = argument; + } else return error.InvalidArgument; + } + const action_name = action_raw orelse return error.InvalidArgument; + const action: Rule.Action = if (std.mem.eql(u8, action_name, "float")) + .float + else if (std.mem.eql(u8, action_name, "fullscreen")) + .fullscreen + else if (std.mem.eql(u8, action_name, "ssd")) + .ssd + else if (std.mem.eql(u8, action_name, "csd")) + .csd + else if (std.mem.eql(u8, action_name, "tags")) + .tags + else + return error.InvalidArgument; + + Rule.validate(app_id_glob) catch return error.InvalidGlob; + Rule.validate(title_glob) catch return error.InvalidGlob; + + if (remove) { + var index: usize = 0; + while (index < manager.rules.items.len) { + const rule = manager.rules.items[index]; + if (rule.action == action and + std.mem.eql(u8, rule.app_id_glob, app_id_glob) and + std.mem.eql(u8, rule.title_glob, title_glob)) + { + _ = manager.rules.swapRemove(index); + gpa.free(rule.app_id_glob); + gpa.free(rule.title_glob); + return; + } + index += 1; + } + return; + } + + var tags: u32 = 0; + if (action == .tags) { + const raw = value_raw orelse return error.InvalidArgument; + tags = std.fmt.parseInt(u32, raw, 10) catch return error.InvalidArgument; + } else if (value_raw != null) return error.InvalidArgument; + + const owned_app_id = gpa.dupe(u8, app_id_glob) catch return error.OutOfMemory; + errdefer gpa.free(owned_app_id); + const owned_title = gpa.dupe(u8, title_glob) catch return error.OutOfMemory; + errdefer gpa.free(owned_title); + manager.rules.append(gpa, .{ + .app_id_glob = owned_app_id, + .title_glob = owned_title, + .action = action, + .tags = tags, + }) catch return error.OutOfMemory; +} + +const Error = error{ + UnknownCommand, + InvalidArgument, + InvalidModifier, + InvalidKeysym, + InvalidButton, + InvalidGlob, + OutOfMemory, +}; + +/// map +fn defineBinding(manager: *WindowManager, arguments: *Command.Cursor) Error!void { + const mode_name = arguments.next() orelse return error.InvalidArgument; + const modifiers_raw = arguments.next() orelse return error.InvalidArgument; + const keysym_raw = arguments.next() orelse return error.InvalidArgument; + + const modifiers = Binding.parseModifiers(modifiers_raw) catch return error.InvalidModifier; + const keysym = Binding.parseKeysym(keysym_raw) catch |err| switch (err) { + error.InvalidKeysym => return error.InvalidKeysym, + error.OutOfMemory => return error.OutOfMemory, + }; + + var length: usize = 0; + var rest = arguments.*; + while (rest.next()) |argument| length += argument.len + 1; + if (length == 0) return error.InvalidArgument; + + const command = gpa.allocSentinel(u8, length - 1, 0) catch return error.OutOfMemory; + errdefer gpa.free(command); + var offset: usize = 0; + while (arguments.next()) |argument| { + if (offset != 0) { + command[offset] = ' '; + offset += 1; + } + @memcpy(command[offset..][0..argument.len], argument); + offset += argument.len; + } + + const mode = manager.ensureMode(mode_name); + for (mode.specs.items) |*spec| { + if (spec.keysym == keysym and std.meta.eql(spec.modifiers, modifiers)) { + gpa.free(spec.command); + spec.* = .{ .keysym = keysym, .modifiers = modifiers, .command = command }; + manager.refreshBindings(mode); + return; + } + } + mode.specs.append(gpa, .{ + .keysym = keysym, + .modifiers = modifiers, + .command = command, + }) catch { + gpa.free(command); + return error.OutOfMemory; + }; + manager.refreshBindings(mode); +} + +fn removeBinding(manager: *WindowManager, mode_name: []const u8, keysym: u32, modifiers: river.SeatV1.Modifiers) void { + const index = manager.modeIndex(mode_name) orelse return; + const mode = &manager.modes.items[index]; + for (mode.specs.items, 0..) |spec, spec_index| { + if (spec.keysym != keysym or !std.meta.eql(spec.modifiers, modifiers)) continue; + _ = mode.specs.swapRemove(spec_index); + gpa.free(spec.command); + break; + } + for (manager.seats.items) |seat| { + if (!std.mem.eql(u8, manager.modes.items[seat.mode_index].name, mode.name)) continue; + var binding_index: usize = 0; + while (binding_index < seat.bindings.items.len) { + const binding = seat.bindings.items[binding_index]; + if (binding.keysym != keysym or !std.meta.eql(binding.modifiers, modifiers)) { + binding_index += 1; + continue; + } + _ = seat.bindings.swapRemove(binding_index); + binding.deinit(); + gpa.destroy(binding); + } + } +} + +fn removePointerBinding(manager: *WindowManager, mode_name: []const u8, button: u32, modifiers: river.SeatV1.Modifiers) void { + const index = manager.modeIndex(mode_name) orelse return; + const mode = &manager.modes.items[index]; + for (mode.pointer_specs.items, 0..) |spec, spec_index| { + if (spec.button != button or !std.meta.eql(spec.modifiers, modifiers)) continue; + _ = mode.pointer_specs.swapRemove(spec_index); + gpa.free(spec.command); + break; + } + for (manager.seats.items) |seat| { + if (!std.mem.eql(u8, manager.modes.items[seat.mode_index].name, mode.name)) continue; + var binding_index: usize = 0; + while (binding_index < seat.pointer_bindings.items.len) { + const binding = seat.pointer_bindings.items[binding_index]; + if (binding.button != button or !std.meta.eql(binding.modifiers, modifiers)) { + binding_index += 1; + continue; + } + _ = seat.pointer_bindings.swapRemove(binding_index); + binding.deinit(); + gpa.destroy(binding); + } + } +} + +/// map-pointer