Compare commits
65 Commits
main
...
track-old-
Author | SHA1 | Date | |
---|---|---|---|
8488cd9d97
|
|||
46f77f30dc | |||
8490558b8b | |||
14f63f3099
|
|||
543697847f | |||
c1fc15dbc6
|
|||
6abcc68a19 | |||
ab879e245c | |||
9f8b689f8a | |||
e575485f0d | |||
4fba7505f3 | |||
5ca829bd5a | |||
a2a5e8f463 | |||
33a69405c4
|
|||
5080f07724
|
|||
d66decb7c4
|
|||
eab34c7c03
|
|||
dbe2cb72f8
|
|||
1b5dd21ee6 | |||
3529463569 | |||
2061ae2c4c
|
|||
fd55f51ba1 | |||
26f599b56b | |||
fbb9cc0f76 | |||
55974987b6 | |||
f82b2f5816 | |||
066baa5753 | |||
db7de8151c | |||
f5d37f9b4d | |||
93863b132e | |||
85a1673a9e | |||
2cc1d1cef3 | |||
f27bbf03f1 | |||
99ef96a389 | |||
ccd676e5a9 | |||
a7411ef2a6 | |||
1f5bf1d972 | |||
14a5609dae
|
|||
4232d6b99f | |||
ec16f1c375 | |||
a80e0f7322 | |||
0997fde28e | |||
ae7f4b8fcb | |||
2e09b66963 | |||
ffb24267b8
|
|||
de3035563c | |||
28a14c6794 | |||
e2f3cd8252 | |||
f9201ae7cd | |||
16c938111d | |||
68dda1a48a
|
|||
fac47ffb5d
|
|||
8da69699e9 | |||
c5b1d1de4e | |||
4d44ca6d5d | |||
7fdba05b82 | |||
958f8798b6 | |||
045ee7bd25 | |||
033cad47bf | |||
680cb8ef69 | |||
5d1fc034bc | |||
c75d32c88b | |||
b35a40b9df | |||
ba6023e38a | |||
74baf7225a |
@ -28,32 +28,35 @@ sources:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_deps: |
|
- install_deps: |
|
||||||
cd wayland
|
cd wayland
|
||||||
git checkout 1.22.0
|
git checkout 1.23.0
|
||||||
meson setup build -Ddocumentation=false -Dtests=false --prefix /usr
|
meson setup build -Ddocumentation=false -Dtests=false --prefix /usr
|
||||||
sudo ninja -C build install
|
sudo ninja -C build install
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
cd wlroots
|
cd wlroots
|
||||||
git checkout 0.17.2
|
git checkout 0.18.0
|
||||||
meson setup build --auto-features=enabled -Drenderers=gles2 -Dexamples=false \
|
meson setup build --auto-features=enabled -Drenderers=gles2 \
|
||||||
-Dwerror=false -Db_ndebug=false -Dxcb-errors=disabled --prefix /usr
|
-Dcolor-management=disabled -Dlibliftoff=disabled \
|
||||||
|
-Dexamples=false -Dwerror=false -Db_ndebug=false \
|
||||||
|
-Dxcb-errors=disabled --prefix /usr
|
||||||
sudo ninja -C build/ install
|
sudo ninja -C build/ install
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
wget -nv https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz
|
# Eat Github's resources rather than the Zig Software Foundation's resources!
|
||||||
# Remove a lot of useless lines from tar output.
|
wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz
|
||||||
tar -xvf zig-linux-x86_64-0.11.0.tar.xz 1>/dev/null
|
tar xf zig-linux-x86_64-0.13.0.tar.xz
|
||||||
sudo mv zig-linux-x86_64-0.11.0/zig /usr/bin/
|
sudo mv zig-linux-x86_64-0.13.0/zig /usr/bin/
|
||||||
sudo mv zig-linux-x86_64-0.11.0/lib /usr/lib/zig
|
sudo mv zig-linux-x86_64-0.13.0/lib /usr/lib/zig
|
||||||
- build: |
|
- build: |
|
||||||
cd river
|
cd river
|
||||||
zig build
|
zig build --summary all
|
||||||
- build_xwayland: |
|
- build_xwayland: |
|
||||||
cd river
|
cd river
|
||||||
zig build -Dxwayland
|
zig build --summary all -Dxwayland
|
||||||
- fmt: |
|
- fmt: |
|
||||||
cd river
|
cd river
|
||||||
zig fmt --check river/
|
zig fmt --check river/
|
||||||
zig fmt --check riverctl/
|
zig fmt --check riverctl/
|
||||||
zig fmt --check rivertile/
|
zig fmt --check rivertile/
|
||||||
zig fmt --check build.zig
|
zig fmt --check build.zig
|
||||||
|
zig fmt --check build.zig.zon
|
||||||
|
@ -26,32 +26,35 @@ sources:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_deps: |
|
- install_deps: |
|
||||||
cd wayland
|
cd wayland
|
||||||
git checkout 1.22.0
|
git checkout 1.23.0
|
||||||
meson setup build -Ddocumentation=false -Dtests=false --prefix /usr
|
meson setup build -Ddocumentation=false -Dtests=false --prefix /usr
|
||||||
sudo ninja -C build install
|
sudo ninja -C build install
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
cd wlroots
|
cd wlroots
|
||||||
git checkout 0.17.2
|
git checkout 0.18.0
|
||||||
meson setup build --auto-features=enabled -Drenderers=gles2 -Dexamples=false \
|
meson setup build --auto-features=enabled -Drenderers=gles2 \
|
||||||
-Dwerror=false -Db_ndebug=false --prefix /usr
|
-Dcolor-management=disabled -Dlibliftoff=disabled \
|
||||||
|
-Dexamples=false -Dwerror=false -Db_ndebug=false \
|
||||||
|
-Dxcb-errors=disabled --prefix /usr
|
||||||
sudo ninja -C build/ install
|
sudo ninja -C build/ install
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
wget -nv https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz
|
# Eat Github's resources rather than the Zig Software Foundation's resources!
|
||||||
# Remove a lot of useless lines from tar output.
|
wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz
|
||||||
tar -xvf zig-linux-x86_64-0.11.0.tar.xz 1>/dev/null
|
tar xf zig-linux-x86_64-0.13.0.tar.xz
|
||||||
sudo mv zig-linux-x86_64-0.11.0/zig /usr/bin/
|
sudo mv zig-linux-x86_64-0.13.0/zig /usr/bin/
|
||||||
sudo mv zig-linux-x86_64-0.11.0/lib /usr/lib/zig
|
sudo mv zig-linux-x86_64-0.13.0/lib /usr/lib/zig
|
||||||
- build: |
|
- build: |
|
||||||
cd river
|
cd river
|
||||||
zig build
|
zig build --summary all
|
||||||
- build_xwayland: |
|
- build_xwayland: |
|
||||||
cd river
|
cd river
|
||||||
zig build -Dxwayland
|
zig build --summary all -Dxwayland
|
||||||
- fmt: |
|
- fmt: |
|
||||||
cd river
|
cd river
|
||||||
zig fmt --check river/
|
zig fmt --check river/
|
||||||
zig fmt --check riverctl/
|
zig fmt --check riverctl/
|
||||||
zig fmt --check rivertile/
|
zig fmt --check rivertile/
|
||||||
zig fmt --check build.zig
|
zig fmt --check build.zig
|
||||||
|
zig fmt --check build.zig.zon
|
||||||
|
@ -18,6 +18,7 @@ packages:
|
|||||||
- x11/xcb-util-renderutil
|
- x11/xcb-util-renderutil
|
||||||
- x11/xcb-util-wm
|
- x11/xcb-util-wm
|
||||||
- x11-servers/xwayland
|
- x11-servers/xwayland
|
||||||
|
- security/ca_root_nss
|
||||||
- sysutils/seatd
|
- sysutils/seatd
|
||||||
- sysutils/libdisplay-info
|
- sysutils/libdisplay-info
|
||||||
- gmake
|
- gmake
|
||||||
@ -30,29 +31,31 @@ sources:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_deps: |
|
- install_deps: |
|
||||||
cd wayland
|
cd wayland
|
||||||
git checkout 1.22.0
|
git checkout 1.23.0
|
||||||
meson setup build -Ddocumentation=false -Dtests=false --prefix /usr
|
meson setup build -Ddocumentation=false -Dtests=false --prefix /usr
|
||||||
sudo ninja -C build install
|
sudo ninja -C build install
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
cd wlroots
|
cd wlroots
|
||||||
git checkout 0.17.2
|
git checkout 0.18.0
|
||||||
meson setup build --auto-features=enabled -Drenderers=gles2 -Dexamples=false \
|
meson setup build --auto-features=enabled -Drenderers=gles2 \
|
||||||
-Dwerror=false -Db_ndebug=false --prefix /usr
|
-Dcolor-management=disabled -Dlibliftoff=disabled \
|
||||||
|
-Dexamples=false -Dwerror=false -Db_ndebug=false \
|
||||||
|
-Dxcb-errors=disabled --prefix /usr
|
||||||
sudo ninja -C build/ install
|
sudo ninja -C build/ install
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
wget -nv https://ziglang.org/download/0.11.0/zig-freebsd-x86_64-0.11.0.tar.xz
|
# Eat Github's resources rather than the Zig Software Foundation's resources!
|
||||||
# Remove a lot of useless lines from tar output.
|
wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.13.0/zig-freebsd-x86_64-0.13.0.tar.xz
|
||||||
tar -xvf zig-freebsd-x86_64-0.11.0.tar.xz 1>/dev/null
|
tar xf zig-freebsd-x86_64-0.13.0.tar.xz
|
||||||
sudo mv zig-freebsd-x86_64-0.11.0/zig /usr/bin/
|
sudo mv zig-freebsd-x86_64-0.13.0/zig /usr/bin/
|
||||||
sudo mv zig-freebsd-x86_64-0.11.0/lib /usr/lib/zig
|
sudo mv zig-freebsd-x86_64-0.13.0/lib /usr/lib/zig
|
||||||
- build: |
|
- build: |
|
||||||
cd river
|
cd river
|
||||||
zig build
|
zig build --summary all
|
||||||
- build_xwayland: |
|
- build_xwayland: |
|
||||||
cd river
|
cd river
|
||||||
zig build -Dxwayland
|
zig build --summary all -Dxwayland
|
||||||
- fmt: |
|
- fmt: |
|
||||||
cd river
|
cd river
|
||||||
zig fmt --check river/
|
zig fmt --check river/
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,4 @@
|
|||||||
|
.zig-cache/
|
||||||
zig-cache/
|
zig-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
|
deps/
|
||||||
|
12
.gitmodules
vendored
12
.gitmodules
vendored
@ -1,12 +0,0 @@
|
|||||||
[submodule "deps/zig-wayland"]
|
|
||||||
path = deps/zig-wayland
|
|
||||||
url = https://codeberg.org/ifreund/zig-wayland
|
|
||||||
[submodule "deps/zig-pixman"]
|
|
||||||
path = deps/zig-pixman
|
|
||||||
url = https://codeberg.org/ifreund/zig-pixman
|
|
||||||
[submodule "deps/zig-xkbcommon"]
|
|
||||||
path = deps/zig-xkbcommon
|
|
||||||
url = https://codeberg.org/ifreund/zig-xkbcommon
|
|
||||||
[submodule "deps/zig-wlroots"]
|
|
||||||
path = deps/zig-wlroots
|
|
||||||
url = https://codeberg.org/ifreund/zig-wlroots
|
|
39
PACKAGING.md
39
PACKAGING.md
@ -26,6 +26,45 @@ and is only compatible with that release and any patch releases. At the time
|
|||||||
of writing for example river is compatible with Zig 0.9.0 and 0.9.1 but
|
of writing for example river is compatible with Zig 0.9.0 and 0.9.1 but
|
||||||
not Zig 0.8.0 or 0.10.0.
|
not Zig 0.8.0 or 0.10.0.
|
||||||
|
|
||||||
|
## Zig Package Manager
|
||||||
|
|
||||||
|
River uses the built-in Zig package manager for its (few) Zig dependencies.
|
||||||
|
By default, running `zig build` will fetch river's Zig dependencies from the
|
||||||
|
internet and store them in the global zig cache before building river. Since
|
||||||
|
accessing the internet is forbidden or at least frowned upon by most distro
|
||||||
|
packaging infrastructure, there are ways to fetch the Zig dependencies in a
|
||||||
|
separate step before building river:
|
||||||
|
|
||||||
|
1. Fetch step with internet access:
|
||||||
|
|
||||||
|
For each package in the `build.zig.zon` manifest file run the following command
|
||||||
|
with the tarball URL in the `build.zig.zon`:
|
||||||
|
|
||||||
|
```
|
||||||
|
zig fetch --global-cache-dir /tmp/foobar $URL
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will download and unpack the tarball, hash the contents of the
|
||||||
|
tarball, and store the contents in the `/tmp/foobar/p/$HASH` directory. This
|
||||||
|
hash should match the corresponding hash field in the `build.zig.zon`.
|
||||||
|
|
||||||
|
2. Build step with no internet access:
|
||||||
|
|
||||||
|
The `--system` flag for `zig build` takes a path to an arbitrary directory in
|
||||||
|
which zig packages stored in subdirectories matching their hash can be found.
|
||||||
|
|
||||||
|
```
|
||||||
|
zig build --system /tmp/foobar/p/ ...
|
||||||
|
```
|
||||||
|
|
||||||
|
This flag will disable all internet access and error if a package is not found
|
||||||
|
in the provided directory.
|
||||||
|
|
||||||
|
It is also possible for distros to distribute Zig package manager packages as
|
||||||
|
distro packages, although there are still some rough edges as the support for
|
||||||
|
this is not yet mature. See this patchset for Chimera Linux for an example of
|
||||||
|
how this can work: https://github.com/chimera-linux/cports/pull/1395
|
||||||
|
|
||||||
## Build options
|
## Build options
|
||||||
|
|
||||||
River is built using the Zig build system. To see all available build
|
River is built using the Zig build system. To see all available build
|
||||||
|
19
README.md
19
README.md
@ -7,7 +7,7 @@
|
|||||||
River is a dynamic tiling Wayland compositor with flexible runtime
|
River is a dynamic tiling Wayland compositor with flexible runtime
|
||||||
configuration.
|
configuration.
|
||||||
|
|
||||||
Install from your [package manager](https://repology.org/project/river/versions) —
|
Check [packaging status](https://repology.org/project/river-compositor/versions) —
|
||||||
Join us at [#river](https://web.libera.chat/?channels=#river) on irc.libera.chat —
|
Join us at [#river](https://web.libera.chat/?channels=#river) on irc.libera.chat —
|
||||||
Read our man pages, [wiki](https://codeberg.org/river/wiki), and
|
Read our man pages, [wiki](https://codeberg.org/river/wiki), and
|
||||||
[Code of Conduct](CODE_OF_CONDUCT.md)
|
[Code of Conduct](CODE_OF_CONDUCT.md)
|
||||||
@ -51,21 +51,16 @@ commands to set up the user's configuration.
|
|||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
On cloning the repository, you must init and update the submodules as well
|
Note: If you are packaging river for distribution, see [PACKAGING.md](PACKAGING.md).
|
||||||
with e.g.
|
|
||||||
|
|
||||||
```
|
|
||||||
git submodule update --init
|
|
||||||
```
|
|
||||||
|
|
||||||
To compile river first ensure that you have the following dependencies
|
To compile river first ensure that you have the following dependencies
|
||||||
installed. The "development" versions are required if applicable to your
|
installed. The "development" versions are required if applicable to your
|
||||||
distribution.
|
distribution.
|
||||||
|
|
||||||
- [zig](https://ziglang.org/download/) 0.11
|
- [zig](https://ziglang.org/download/) 0.13
|
||||||
- wayland
|
- wayland
|
||||||
- wayland-protocols
|
- wayland-protocols
|
||||||
- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.17.2
|
- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.18
|
||||||
- xkbcommon
|
- xkbcommon
|
||||||
- libevdev
|
- libevdev
|
||||||
- pixman
|
- pixman
|
||||||
@ -76,10 +71,8 @@ Then run, for example:
|
|||||||
```
|
```
|
||||||
zig build -Doptimize=ReleaseSafe --prefix ~/.local install
|
zig build -Doptimize=ReleaseSafe --prefix ~/.local install
|
||||||
```
|
```
|
||||||
To enable experimental Xwayland support pass the `-Dxwayland` option as well.
|
To enable Xwayland support pass the `-Dxwayland` option as well.
|
||||||
|
Run `zig build -h` to see a list of all options.
|
||||||
If you are packaging river for distribution, see also
|
|
||||||
[PACKAGING.md](PACKAGING.md).
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
129
build.zig
129
build.zig
@ -4,7 +4,7 @@ const Build = std.Build;
|
|||||||
const fs = std.fs;
|
const fs = std.fs;
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
|
|
||||||
const Scanner = @import("deps/zig-wayland/build.zig").Scanner;
|
const Scanner = @import("zig-wayland").Scanner;
|
||||||
|
|
||||||
/// While a river release is in development, this string should contain the version in development
|
/// While a river release is in development, this string should contain the version in development
|
||||||
/// with the "-dev" suffix.
|
/// with the "-dev" suffix.
|
||||||
@ -18,6 +18,7 @@ pub fn build(b: *Build) !void {
|
|||||||
|
|
||||||
const strip = b.option(bool, "strip", "Omit debug information") orelse false;
|
const strip = b.option(bool, "strip", "Omit debug information") orelse false;
|
||||||
const pie = b.option(bool, "pie", "Build a Position Independent Executable") orelse false;
|
const pie = b.option(bool, "pie", "Build a Position Independent Executable") orelse false;
|
||||||
|
const llvm = !(b.option(bool, "no-llvm", "(expirimental) Use non-LLVM x86 Zig backend") orelse false);
|
||||||
|
|
||||||
const omit_frame_pointer = switch (optimize) {
|
const omit_frame_pointer = switch (optimize) {
|
||||||
.Debug, .ReleaseSafe => false,
|
.Debug, .ReleaseSafe => false,
|
||||||
@ -64,7 +65,7 @@ pub fn build(b: *Build) !void {
|
|||||||
if (mem.endsWith(u8, version, "-dev")) {
|
if (mem.endsWith(u8, version, "-dev")) {
|
||||||
var ret: u8 = undefined;
|
var ret: u8 = undefined;
|
||||||
|
|
||||||
const git_describe_long = b.execAllowFail(
|
const git_describe_long = b.runAllowFail(
|
||||||
&.{ "git", "-C", b.build_root.path orelse ".", "describe", "--long" },
|
&.{ "git", "-C", b.build_root.path orelse ".", "describe", "--long" },
|
||||||
&ret,
|
&ret,
|
||||||
.Inherit,
|
.Inherit,
|
||||||
@ -91,18 +92,19 @@ pub fn build(b: *Build) !void {
|
|||||||
const scanner = Scanner.create(b, .{});
|
const scanner = Scanner.create(b, .{});
|
||||||
|
|
||||||
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
|
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
|
||||||
scanner.addSystemProtocol("staging/ext-session-lock/ext-session-lock-v1.xml");
|
scanner.addSystemProtocol("stable/tablet/tablet-v2.xml");
|
||||||
scanner.addSystemProtocol("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml");
|
|
||||||
scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml");
|
|
||||||
scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml");
|
|
||||||
scanner.addSystemProtocol("unstable/tablet/tablet-unstable-v2.xml");
|
|
||||||
scanner.addSystemProtocol("staging/cursor-shape/cursor-shape-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/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.addCustomProtocol("protocol/river-control-unstable-v1.xml");
|
scanner.addCustomProtocol(b.path("protocol/river-control-unstable-v1.xml"));
|
||||||
scanner.addCustomProtocol("protocol/river-status-unstable-v1.xml");
|
scanner.addCustomProtocol(b.path("protocol/river-status-unstable-v1.xml"));
|
||||||
scanner.addCustomProtocol("protocol/river-layout-v3.xml");
|
scanner.addCustomProtocol(b.path("protocol/river-layout-v3.xml"));
|
||||||
scanner.addCustomProtocol("protocol/wlr-layer-shell-unstable-v1.xml");
|
scanner.addCustomProtocol(b.path("protocol/wlr-layer-shell-unstable-v1.xml"));
|
||||||
scanner.addCustomProtocol("protocol/wlr-output-power-management-unstable-v1.xml");
|
scanner.addCustomProtocol(b.path("protocol/wlr-output-power-management-unstable-v1.xml"));
|
||||||
|
|
||||||
// Some of these versions may be out of date with what wlroots implements.
|
// 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.
|
// This is not a problem in practice though as long as river successfully compiles.
|
||||||
@ -123,6 +125,7 @@ pub fn build(b: *Build) !void {
|
|||||||
scanner.generate("zxdg_decoration_manager_v1", 1);
|
scanner.generate("zxdg_decoration_manager_v1", 1);
|
||||||
scanner.generate("ext_session_lock_manager_v1", 1);
|
scanner.generate("ext_session_lock_manager_v1", 1);
|
||||||
scanner.generate("wp_cursor_shape_manager_v1", 1);
|
scanner.generate("wp_cursor_shape_manager_v1", 1);
|
||||||
|
scanner.generate("wp_tearing_control_manager_v1", 1);
|
||||||
|
|
||||||
scanner.generate("zriver_control_v1", 1);
|
scanner.generate("zriver_control_v1", 1);
|
||||||
scanner.generate("zriver_status_manager_v1", 4);
|
scanner.generate("zriver_status_manager_v1", 4);
|
||||||
@ -131,63 +134,59 @@ pub fn build(b: *Build) !void {
|
|||||||
scanner.generate("zwlr_layer_shell_v1", 4);
|
scanner.generate("zwlr_layer_shell_v1", 4);
|
||||||
scanner.generate("zwlr_output_power_manager_v1", 1);
|
scanner.generate("zwlr_output_power_manager_v1", 1);
|
||||||
|
|
||||||
const wayland = b.createModule(.{ .source_file = scanner.result });
|
const wayland = b.createModule(.{ .root_source_file = scanner.result });
|
||||||
const xkbcommon = b.createModule(.{
|
|
||||||
.source_file = .{ .path = "deps/zig-xkbcommon/src/xkbcommon.zig" },
|
|
||||||
});
|
|
||||||
const pixman = b.createModule(.{
|
|
||||||
.source_file = .{ .path = "deps/zig-pixman/pixman.zig" },
|
|
||||||
});
|
|
||||||
const wlroots = b.createModule(.{
|
|
||||||
.source_file = .{ .path = "deps/zig-wlroots/src/wlroots.zig" },
|
|
||||||
.dependencies = &.{
|
|
||||||
.{ .name = "wayland", .module = wayland },
|
|
||||||
.{ .name = "xkbcommon", .module = xkbcommon },
|
|
||||||
.{ .name = "pixman", .module = pixman },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const flags = b.createModule(.{ .source_file = .{ .path = "common/flags.zig" } });
|
const xkbcommon = b.dependency("zig-xkbcommon", .{}).module("xkbcommon");
|
||||||
const globber = b.createModule(.{ .source_file = .{ .path = "common/globber.zig" } });
|
const pixman = b.dependency("zig-pixman", .{}).module("pixman");
|
||||||
|
|
||||||
|
const wlroots = b.dependency("zig-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.18", .{});
|
||||||
|
|
||||||
|
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 river = b.addExecutable(.{
|
const river = b.addExecutable(.{
|
||||||
.name = "river",
|
.name = "river",
|
||||||
.root_source_file = .{ .path = "river/main.zig" },
|
.root_source_file = b.path("river/main.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.strip = strip,
|
||||||
|
.use_llvm = llvm,
|
||||||
|
.use_lld = llvm,
|
||||||
});
|
});
|
||||||
river.addOptions("build_options", options);
|
river.root_module.addOptions("build_options", options);
|
||||||
|
|
||||||
river.linkLibC();
|
river.linkLibC();
|
||||||
river.linkSystemLibrary("libevdev");
|
river.linkSystemLibrary("libevdev");
|
||||||
river.linkSystemLibrary("libinput");
|
river.linkSystemLibrary("libinput");
|
||||||
|
|
||||||
river.addModule("wayland", wayland);
|
|
||||||
river.linkSystemLibrary("wayland-server");
|
river.linkSystemLibrary("wayland-server");
|
||||||
|
river.linkSystemLibrary("wlroots-0.18");
|
||||||
river.addModule("xkbcommon", xkbcommon);
|
|
||||||
river.linkSystemLibrary("xkbcommon");
|
river.linkSystemLibrary("xkbcommon");
|
||||||
|
|
||||||
river.addModule("pixman", pixman);
|
|
||||||
river.linkSystemLibrary("pixman-1");
|
river.linkSystemLibrary("pixman-1");
|
||||||
|
|
||||||
river.addModule("wlroots", wlroots);
|
river.root_module.addImport("wayland", wayland);
|
||||||
river.linkSystemLibrary("wlroots");
|
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.addModule("flags", flags);
|
|
||||||
river.addModule("globber", globber);
|
|
||||||
river.addCSourceFile(.{
|
river.addCSourceFile(.{
|
||||||
.file = .{ .path = "river/wlroots_log_wrapper.c" },
|
.file = b.path("river/wlroots_log_wrapper.c"),
|
||||||
.flags = &.{ "-std=c99", "-O2" },
|
.flags = &.{ "-std=c99", "-O2" },
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: remove when zig issue #131 is implemented
|
|
||||||
scanner.addCSource(river);
|
|
||||||
|
|
||||||
river.strip = strip;
|
|
||||||
river.pie = pie;
|
river.pie = pie;
|
||||||
river.omit_frame_pointer = omit_frame_pointer;
|
river.root_module.omit_frame_pointer = omit_frame_pointer;
|
||||||
|
|
||||||
b.installArtifact(river);
|
b.installArtifact(river);
|
||||||
}
|
}
|
||||||
@ -195,22 +194,22 @@ pub fn build(b: *Build) !void {
|
|||||||
{
|
{
|
||||||
const riverctl = b.addExecutable(.{
|
const riverctl = b.addExecutable(.{
|
||||||
.name = "riverctl",
|
.name = "riverctl",
|
||||||
.root_source_file = .{ .path = "riverctl/main.zig" },
|
.root_source_file = b.path("riverctl/main.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.strip = strip,
|
||||||
|
.use_llvm = llvm,
|
||||||
|
.use_lld = llvm,
|
||||||
});
|
});
|
||||||
riverctl.addOptions("build_options", options);
|
riverctl.root_module.addOptions("build_options", options);
|
||||||
|
|
||||||
riverctl.addModule("flags", flags);
|
riverctl.root_module.addImport("flags", flags);
|
||||||
riverctl.addModule("wayland", wayland);
|
riverctl.root_module.addImport("wayland", wayland);
|
||||||
riverctl.linkLibC();
|
riverctl.linkLibC();
|
||||||
riverctl.linkSystemLibrary("wayland-client");
|
riverctl.linkSystemLibrary("wayland-client");
|
||||||
|
|
||||||
scanner.addCSource(riverctl);
|
|
||||||
|
|
||||||
riverctl.strip = strip;
|
|
||||||
riverctl.pie = pie;
|
riverctl.pie = pie;
|
||||||
riverctl.omit_frame_pointer = omit_frame_pointer;
|
riverctl.root_module.omit_frame_pointer = omit_frame_pointer;
|
||||||
|
|
||||||
b.installArtifact(riverctl);
|
b.installArtifact(riverctl);
|
||||||
}
|
}
|
||||||
@ -218,22 +217,22 @@ pub fn build(b: *Build) !void {
|
|||||||
{
|
{
|
||||||
const rivertile = b.addExecutable(.{
|
const rivertile = b.addExecutable(.{
|
||||||
.name = "rivertile",
|
.name = "rivertile",
|
||||||
.root_source_file = .{ .path = "rivertile/main.zig" },
|
.root_source_file = b.path("rivertile/main.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.strip = strip,
|
||||||
|
.use_llvm = llvm,
|
||||||
|
.use_lld = llvm,
|
||||||
});
|
});
|
||||||
rivertile.addOptions("build_options", options);
|
rivertile.root_module.addOptions("build_options", options);
|
||||||
|
|
||||||
rivertile.addModule("flags", flags);
|
rivertile.root_module.addImport("flags", flags);
|
||||||
rivertile.addModule("wayland", wayland);
|
rivertile.root_module.addImport("wayland", wayland);
|
||||||
rivertile.linkLibC();
|
rivertile.linkLibC();
|
||||||
rivertile.linkSystemLibrary("wayland-client");
|
rivertile.linkSystemLibrary("wayland-client");
|
||||||
|
|
||||||
scanner.addCSource(rivertile);
|
|
||||||
|
|
||||||
rivertile.strip = strip;
|
|
||||||
rivertile.pie = pie;
|
rivertile.pie = pie;
|
||||||
rivertile.omit_frame_pointer = omit_frame_pointer;
|
rivertile.root_module.omit_frame_pointer = omit_frame_pointer;
|
||||||
|
|
||||||
b.installArtifact(rivertile);
|
b.installArtifact(rivertile);
|
||||||
}
|
}
|
||||||
@ -261,7 +260,7 @@ pub fn build(b: *Build) !void {
|
|||||||
// Even passing a buffer to std.Build.Step.Run appears to be racy and occasionally deadlocks.
|
// 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" });
|
const scdoc = b.addSystemCommand(&.{ "/bin/sh", "-c", "scdoc < doc/" ++ page ++ ".1.scd" });
|
||||||
// This makes the caching work for the Workaround, and the extra argument is ignored by /bin/sh.
|
// This makes the caching work for the Workaround, and the extra argument is ignored by /bin/sh.
|
||||||
scdoc.addFileArg(.{ .path = "doc/" ++ page ++ ".1.scd" });
|
scdoc.addFileArg(b.path("doc/" ++ page ++ ".1.scd"));
|
||||||
|
|
||||||
const stdout = scdoc.captureStdOut();
|
const stdout = scdoc.captureStdOut();
|
||||||
b.getInstallStep().dependOn(&b.addInstallFile(stdout, "share/man/man1/" ++ page ++ ".1").step);
|
b.getInstallStep().dependOn(&b.addInstallFile(stdout, "share/man/man1/" ++ page ++ ".1").step);
|
||||||
@ -282,7 +281,7 @@ pub fn build(b: *Build) !void {
|
|||||||
|
|
||||||
{
|
{
|
||||||
const globber_test = b.addTest(.{
|
const globber_test = b.addTest(.{
|
||||||
.root_source_file = .{ .path = "common/globber.zig" },
|
.root_source_file = b.path("common/globber.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
23
build.zig.zon
Normal file
23
build.zig.zon
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
.{
|
||||||
|
.name = "river",
|
||||||
|
.version = "0.4.0-dev",
|
||||||
|
.paths = .{""},
|
||||||
|
.dependencies = .{
|
||||||
|
.@"zig-pixman" = .{
|
||||||
|
.url = "https://codeberg.org/ifreund/zig-pixman/archive/v0.2.0.tar.gz",
|
||||||
|
.hash = "12209db20ce873af176138b76632931def33a10539387cba745db72933c43d274d56",
|
||||||
|
},
|
||||||
|
.@"zig-wayland" = .{
|
||||||
|
.url = "https://codeberg.org/ifreund/zig-wayland/archive/bd8afd256fb6beed7d72e3580b00f33dea7155a1.tar.gz",
|
||||||
|
.hash = "1220218a0e5c2cd63a2311417f4d3f2411dd17d75815f67c704ee657bd846ecbc3e0",
|
||||||
|
},
|
||||||
|
.@"zig-wlroots" = .{
|
||||||
|
.url = "https://codeberg.org/ifreund/zig-wlroots/archive/afbbbbe5579c750feed8de12b073fa50b0651137.tar.gz",
|
||||||
|
.hash = "122060ddef836b7872cb2088764a8bd2fb2e9254327673e8176b7f7a621ec897484f",
|
||||||
|
},
|
||||||
|
.@"zig-xkbcommon" = .{
|
||||||
|
.url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.2.0.tar.gz",
|
||||||
|
.hash = "1220c90b2228d65fd8427a837d31b0add83e9fade1dcfa539bb56fd06f1f8461605f",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
@ -18,7 +18,7 @@ const std = @import("std");
|
|||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
|
|
||||||
pub const Flag = struct {
|
pub const Flag = struct {
|
||||||
name: []const u8,
|
name: [:0]const u8,
|
||||||
kind: enum { boolean, arg },
|
kind: enum { boolean, arg },
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ pub fn parser(comptime Arg: type, comptime flags: []const Flag) type {
|
|||||||
|
|
||||||
pub const Flags = flags_type: {
|
pub const Flags = flags_type: {
|
||||||
var fields: []const std.builtin.Type.StructField = &.{};
|
var fields: []const std.builtin.Type.StructField = &.{};
|
||||||
inline for (flags) |flag| {
|
for (flags) |flag| {
|
||||||
const field: std.builtin.Type.StructField = switch (flag.kind) {
|
const field: std.builtin.Type.StructField = switch (flag.kind) {
|
||||||
.boolean => .{
|
.boolean => .{
|
||||||
.name = flag.name,
|
.name = flag.name,
|
||||||
@ -57,7 +57,7 @@ pub fn parser(comptime Arg: type, comptime flags: []const Flag) type {
|
|||||||
fields = fields ++ [_]std.builtin.Type.StructField{field};
|
fields = fields ++ [_]std.builtin.Type.StructField{field};
|
||||||
}
|
}
|
||||||
break :flags_type @Type(.{ .Struct = .{
|
break :flags_type @Type(.{ .Struct = .{
|
||||||
.layout = .Auto,
|
.layout = .auto,
|
||||||
.fields = fields,
|
.fields = fields,
|
||||||
.decls = &.{},
|
.decls = &.{},
|
||||||
.is_tuple = false,
|
.is_tuple = false,
|
||||||
|
@ -18,12 +18,11 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
|
|
||||||
/// Validate a glob, returning error.InvalidGlob if it is empty, "**" or has a
|
/// Validate a glob, returning error.InvalidGlob if is "**" or has a '*'
|
||||||
/// '*' at any position other than the first and/or last byte.
|
/// at any position other than the first and/or last byte.
|
||||||
pub fn validate(glob: []const u8) error{InvalidGlob}!void {
|
pub fn validate(glob: []const u8) error{InvalidGlob}!void {
|
||||||
switch (glob.len) {
|
switch (glob.len) {
|
||||||
0 => return error.InvalidGlob,
|
0, 1 => {},
|
||||||
1 => {},
|
|
||||||
2 => if (glob[0] == '*' and glob[1] == '*') return error.InvalidGlob,
|
2 => if (glob[0] == '*' and glob[1] == '*') return error.InvalidGlob,
|
||||||
else => if (mem.indexOfScalar(u8, glob[1 .. glob.len - 1], '*') != null) {
|
else => if (mem.indexOfScalar(u8, glob[1 .. glob.len - 1], '*') != null) {
|
||||||
return error.InvalidGlob;
|
return error.InvalidGlob;
|
||||||
@ -34,6 +33,7 @@ pub fn validate(glob: []const u8) error{InvalidGlob}!void {
|
|||||||
test validate {
|
test validate {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
try validate("");
|
||||||
try validate("*");
|
try validate("*");
|
||||||
try validate("a");
|
try validate("a");
|
||||||
try validate("*a");
|
try validate("*a");
|
||||||
@ -48,7 +48,6 @@ test validate {
|
|||||||
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("***"));
|
try testing.expectError(error.InvalidGlob, validate("***"));
|
||||||
try testing.expectError(error.InvalidGlob, validate("a*c"));
|
try testing.expectError(error.InvalidGlob, validate("a*c"));
|
||||||
@ -67,7 +66,9 @@ pub fn match(s: []const u8, glob: []const u8) bool {
|
|||||||
validate(glob) catch unreachable;
|
validate(glob) catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glob.len == 1) {
|
if (glob.len == 0) {
|
||||||
|
return s.len == 0;
|
||||||
|
} else if (glob.len == 1) {
|
||||||
return glob[0] == '*' or mem.eql(u8, s, glob);
|
return glob[0] == '*' or mem.eql(u8, s, glob);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,6 +90,9 @@ test match {
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
try testing.expect(match("", "*"));
|
try testing.expect(match("", "*"));
|
||||||
|
try testing.expect(match("", ""));
|
||||||
|
try testing.expect(!match("a", ""));
|
||||||
|
try testing.expect(!match("", "a"));
|
||||||
|
|
||||||
try testing.expect(match("a", "*"));
|
try testing.expect(match("a", "*"));
|
||||||
try testing.expect(match("a", "*a*"));
|
try testing.expect(match("a", "*a*"));
|
||||||
@ -165,8 +169,10 @@ pub fn order(a: []const u8, b: []const u8) std.math.Order {
|
|||||||
return .lt;
|
return .lt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const count_a = @as(u2, @intFromBool(a[0] == '*')) + @intFromBool(a[a.len - 1] == '*');
|
const count_a = if (a.len != 0) @as(u2, @intFromBool(a[0] == '*')) +
|
||||||
const count_b = @as(u2, @intFromBool(b[0] == '*')) + @intFromBool(b[b.len - 1] == '*');
|
@intFromBool(a[a.len - 1] == '*') else 0;
|
||||||
|
const count_b = if (b.len != 0) @as(u2, @intFromBool(b[0] == '*')) +
|
||||||
|
@intFromBool(b[b.len - 1] == '*') else 0;
|
||||||
|
|
||||||
if (count_a == 0 and count_b == 0) {
|
if (count_a == 0 and count_b == 0) {
|
||||||
return .eq;
|
return .eq;
|
||||||
@ -182,6 +188,7 @@ test order {
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const Order = std.math.Order;
|
const Order = std.math.Order;
|
||||||
|
|
||||||
|
try testing.expectEqual(Order.eq, order("", ""));
|
||||||
try testing.expectEqual(Order.eq, 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"));
|
||||||
@ -204,6 +211,7 @@ test order {
|
|||||||
"bababab",
|
"bababab",
|
||||||
"b",
|
"b",
|
||||||
"a",
|
"a",
|
||||||
|
"",
|
||||||
};
|
};
|
||||||
|
|
||||||
for (descending, 0..) |a, i| {
|
for (descending, 0..) |a, i| {
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
function __riverctl_completion ()
|
function __riverctl_completion ()
|
||||||
{
|
{
|
||||||
local rule_actions="float no-float ssd csd tags output position dimensions fullscreen no-fullscreen"
|
local rule_actions="float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen warp no-warp"
|
||||||
if [ "${COMP_CWORD}" -eq 1 ]
|
if [ "${COMP_CWORD}" -eq 1 ]
|
||||||
then
|
then
|
||||||
OPTS=" \
|
OPTS=" \
|
||||||
keyboard-group-create \
|
|
||||||
keyboard-group-destroy \
|
|
||||||
keyboard-group-add \
|
|
||||||
keyboard-group-remove \
|
|
||||||
keyboard-layout \
|
keyboard-layout \
|
||||||
keyboard-layout-file \
|
keyboard-layout-file \
|
||||||
close \
|
close \
|
||||||
@ -99,6 +95,7 @@ function __riverctl_completion ()
|
|||||||
tap-button-map \
|
tap-button-map \
|
||||||
scroll-method \
|
scroll-method \
|
||||||
scroll-button \
|
scroll-button \
|
||||||
|
scroll-button-lock \
|
||||||
map-to-output"
|
map-to-output"
|
||||||
COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[3]}"))
|
COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[3]}"))
|
||||||
elif [ "${COMP_WORDS[1]}" == "hide-cursor" ]
|
elif [ "${COMP_WORDS[1]}" == "hide-cursor" ]
|
||||||
@ -117,7 +114,7 @@ function __riverctl_completion ()
|
|||||||
"events") OPTS="enabled disabled disabled-on-external-mouse" ;;
|
"events") OPTS="enabled disabled disabled-on-external-mouse" ;;
|
||||||
"accel-profile") OPTS="none flat adaptive" ;;
|
"accel-profile") OPTS="none flat adaptive" ;;
|
||||||
"click-method") OPTS="none button-areas clickfinger" ;;
|
"click-method") OPTS="none button-areas clickfinger" ;;
|
||||||
"drag"|"drag-lock"|"disable-while-typing"|"middle-emulation"|"left-handed"|"tap") OPTS="enabled disabled" ;;
|
"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" ;;
|
"tap-button-map") OPTS="left-right-middle left-middle-right" ;;
|
||||||
"scroll-method") OPTS="none two-finger edge button" ;;
|
"scroll-method") OPTS="none two-finger edge button" ;;
|
||||||
*) return ;;
|
*) return ;;
|
||||||
|
@ -72,11 +72,6 @@ complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'hide-cursor'
|
|||||||
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-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 '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 'xcursor-theme' -d 'Set the xcursor theme'
|
||||||
# Keyboardgroups
|
|
||||||
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-create' -d 'Create a keyboard group'
|
|
||||||
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-destroy' -d 'Destroy a keyboard group'
|
|
||||||
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-add' -d 'Add a keyboard to a keyboard group'
|
|
||||||
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-remove' -d 'Remove a keyboard from a keyboard group'
|
|
||||||
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' -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.'
|
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-layout-file' -d 'Set the keyboard layout from a file.'
|
||||||
|
|
||||||
@ -91,10 +86,10 @@ complete -c riverctl -n '__fish_seen_subcommand_from default-attach-mode'
|
|||||||
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 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 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 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'
|
complete -c riverctl -n '__fish_seen_subcommand_from list-rules' -n '__fish_riverctl_complete_arg 2' -a 'float ssd tags output position dimensions fullscreen warp'
|
||||||
|
|
||||||
# Options and subcommands for 'rule-add' and 'rule-del'
|
# 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
|
set -l rule_actions float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen warp no-warp
|
||||||
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 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 '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"
|
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"
|
||||||
@ -119,10 +114,11 @@ complete -c riverctl -n '__fish_seen_subcommand_from input; and __fish_riverctl_
|
|||||||
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 '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-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' -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'
|
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'
|
# 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' -a 'enabled disabled'
|
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 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 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 click-method' -a 'none button-areas clickfinger'
|
||||||
|
@ -62,11 +62,6 @@ _riverctl_commands()
|
|||||||
'set-repeat:Set the keyboard repeat rate and repeat delay'
|
'set-repeat:Set the keyboard repeat rate and repeat delay'
|
||||||
'set-cursor-warp:Set the cursor warp mode.'
|
'set-cursor-warp:Set the cursor warp mode.'
|
||||||
'xcursor-theme:Set the xcursor theme'
|
'xcursor-theme:Set the xcursor theme'
|
||||||
# Keyboard groups
|
|
||||||
'keyboard-group-create:Create a keyboard group'
|
|
||||||
'keyboard-group-destroy:Destroy a keyboard group'
|
|
||||||
'keyboard-group-add:Add a keyboard to a keyboard group'
|
|
||||||
'keyboard-group-remove:Remove a keyboard from a keyboard group'
|
|
||||||
'keyboard-layout:Set the keyboard layout'
|
'keyboard-layout:Set the keyboard layout'
|
||||||
'keyboard-layout-file:Set the keyboard layout from a file'
|
'keyboard-layout-file:Set the keyboard layout from a file'
|
||||||
# Input
|
# Input
|
||||||
@ -126,6 +121,7 @@ _riverctl()
|
|||||||
'tap-button-map:Configure the button mapping for tapping'
|
'tap-button-map:Configure the button mapping for tapping'
|
||||||
'scroll-method:Set the scroll method'
|
'scroll-method:Set the scroll method'
|
||||||
'scroll-button:Set the scroll button'
|
'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'
|
'map-to-output:Map to a given output'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -135,7 +131,7 @@ _riverctl()
|
|||||||
case "$line[2]" in
|
case "$line[2]" in
|
||||||
events) _alternative 'input-cmds:args:(enabled disabled disabled-on-external-mouse)' ;;
|
events) _alternative 'input-cmds:args:(enabled disabled disabled-on-external-mouse)' ;;
|
||||||
accel-profile) _alternative 'input-cmds:args:(none flat adaptive)' ;;
|
accel-profile) _alternative 'input-cmds:args:(none flat adaptive)' ;;
|
||||||
click-method) _alternative 'input-cmds:args:(none button-area clickfinger)' ;;
|
click-method) _alternative 'input-cmds:args:(none button-areas clickfinger)' ;;
|
||||||
drag) _alternative 'input-cmds:args:(enabled disabled)' ;;
|
drag) _alternative 'input-cmds:args:(enabled disabled)' ;;
|
||||||
drag-lock) _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-typing) _alternative 'input-cmds:args:(enabled disabled)' ;;
|
||||||
@ -144,6 +140,7 @@ _riverctl()
|
|||||||
natural-scroll) _alternative 'input-cmds:args:(enabled disabled)' ;;
|
natural-scroll) _alternative 'input-cmds:args:(enabled disabled)' ;;
|
||||||
left-handed) _alternative 'input-cmds:args:(enabled disabled)' ;;
|
left-handed) _alternative 'input-cmds:args:(enabled disabled)' ;;
|
||||||
tap) _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)' ;;
|
tap-button-map) _alternative 'input-cmds:args:(left-right-middle left-middle-right)' ;;
|
||||||
scroll-method) _alternative 'input-cmds:args:(none two-finger edge button)' ;;
|
scroll-method) _alternative 'input-cmds:args:(none two-finger edge button)' ;;
|
||||||
*) return 0 ;;
|
*) return 0 ;;
|
||||||
@ -205,9 +202,9 @@ _riverctl()
|
|||||||
# In case of a new rule added in river, we just need
|
# In case of a new rule added in river, we just need
|
||||||
# to add it to the third option between '()',
|
# to add it to the third option between '()',
|
||||||
# i.e (float no-float <new-option>)
|
# i.e (float no-float <new-option>)
|
||||||
_arguments '1: :(-app-id -title)' '2: : ' ':: :(float no-float ssd csd tags output position dimensions fullscreen no-fullscreen)'
|
_arguments '1: :(-app-id -title)' '2: : ' ':: :(float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen warp no-warp)'
|
||||||
;;
|
;;
|
||||||
list-rules) _alternative 'arguments:args:(float ssd tags output position dimensions fullscreen)' ;;
|
list-rules) _alternative 'arguments:args:(float ssd tags output position dimensions fullscreen warp)' ;;
|
||||||
*) return 0 ;;
|
*) return 0 ;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
1
deps/zig-pixman
vendored
1
deps/zig-pixman
vendored
Submodule deps/zig-pixman deleted from 70bff91bee
1
deps/zig-wayland
vendored
1
deps/zig-wayland
vendored
Submodule deps/zig-wayland deleted from 73fed09330
1
deps/zig-wlroots
vendored
1
deps/zig-wlroots
vendored
Submodule deps/zig-wlroots deleted from a579f9f7da
1
deps/zig-xkbcommon
vendored
1
deps/zig-xkbcommon
vendored
Submodule deps/zig-xkbcommon deleted from 7e09b38937
@ -298,20 +298,33 @@ matches everything while _\*\*_ and the empty string are invalid.
|
|||||||
with make: _HP Inc._, model: _HP 22w_, and serial: _CNC93720WF_, the
|
with make: _HP Inc._, model: _HP 22w_, and serial: _CNC93720WF_, the
|
||||||
identifier would be: _HP Inc. HP 22w CNC93720WF_. If the make, model, or
|
identifier would be: _HP Inc. HP 22w CNC93720WF_. If the make, model, or
|
||||||
serial is unknown, the word "Unknown" is used instead.
|
serial is unknown, the word "Unknown" is used instead.
|
||||||
- *position*: Set the initial position of the view, clamping to the
|
- *position*: Set the initial position of the view, clamping to the bounds
|
||||||
bounds of the output. Requires x and y coordinates of the view as
|
of the output. Requires x and y coordinates of the view as arguments, both
|
||||||
arguments, both of which must be non-negative. Applies only to new views.
|
of which must be non-negative. Applies only to new views.
|
||||||
|
- *relative-position*: Set the position of the view relative to
|
||||||
|
something. Requires the anchor and the x and y coordinates of the
|
||||||
|
view. The coordinates are either positive or negative numbers that are
|
||||||
|
relative to the anchor. Applies only to new views.
|
||||||
- *dimensions*: Set the initial dimensions of the view, clamping to the
|
- *dimensions*: Set the initial dimensions of the view, clamping to the
|
||||||
constraints of the view. Requires width and height of the view as
|
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.
|
arguments, both of which must be non-negative. Applies only to new views.
|
||||||
- *fullscreen*: Make the view fullscreen. 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
|
- *no-fullscreen*: Don't make the view fullscreen. Applies only to
|
||||||
new views.
|
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.
|
||||||
|
- *warp*: Always warp the cursor when switching to this view, regardless of
|
||||||
|
the _set-cursor-warp_ setting. Applies to new and existing views.
|
||||||
|
- *no-warp*: Never warp the cursor when switching to this view, regardless
|
||||||
|
of the _set-cursor-warp_ setting. Applies to new and existing views.
|
||||||
|
|
||||||
Both *float* and *no-float* rules are added to the same list,
|
Both *float* and *no-float* rules are added to the same list,
|
||||||
which means that adding a *no-float* rule with the same arguments
|
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
|
as a *float* rule will overwrite it. The same holds for *ssd* and
|
||||||
*csd*, *fullscreen* and *no-fullscreen* rules.
|
*csd*, *fullscreen* and *no-fullscreen*, *tearing* and
|
||||||
|
*no-tearing*, *warp* and *no-warp* rules.
|
||||||
|
|
||||||
If multiple rules in a list match a given view the most specific
|
If multiple rules in a list match a given view the most specific
|
||||||
rule will be applied. For example with the following rules
|
rule will be applied. For example with the following rules
|
||||||
@ -339,7 +352,7 @@ matches everything while _\*\*_ and the empty string are invalid.
|
|||||||
*rule-del* [*-app-id* _glob_|*-title* _glob_] _action_
|
*rule-del* [*-app-id* _glob_|*-title* _glob_] _action_
|
||||||
Delete a rule created using *rule-add* with the given arguments.
|
Delete a rule created using *rule-add* with the given arguments.
|
||||||
|
|
||||||
*list-rules* *float*|*ssd*|*tags*|*position*|*dimensions*|*fullscreen*
|
*list-rules* *float*|*ssd*|*tags*|*position*|*dimensions*|*fullscreen*|*warp*
|
||||||
Print the specified rule list. The output is ordered from most specific
|
Print the specified rule list. The output is ordered from most specific
|
||||||
to least specific, the same order in which views are checked against
|
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
|
when searching for a match. Only the first matching rule in the list
|
||||||
@ -364,6 +377,10 @@ matches everything while _\*\*_ and the empty string are invalid.
|
|||||||
Set the attach mode of the currently focused output, overriding the value of
|
Set the attach mode of the currently focused output, overriding the value of
|
||||||
default-attach-mode if any.
|
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_
|
*background-color* _0xRRGGBB_|_0xRRGGBBAA_
|
||||||
Set the background color.
|
Set the background color.
|
||||||
|
|
||||||
@ -413,7 +430,8 @@ matches everything while _\*\*_ and the empty string are invalid.
|
|||||||
|
|
||||||
*set-repeat* _rate_ _delay_
|
*set-repeat* _rate_ _delay_
|
||||||
Set the keyboard repeat rate to _rate_ key repeats per second and
|
Set the keyboard repeat rate to _rate_ key repeats per second and
|
||||||
repeat delay to _delay_ milliseconds.
|
repeat delay to _delay_ milliseconds. The default is a rate of 25
|
||||||
|
repeats per second and a delay of 600ms.
|
||||||
|
|
||||||
*xcursor-theme* _theme_name_ [_size_]
|
*xcursor-theme* _theme_name_ [_size_]
|
||||||
Set the xcursor theme to _theme_name_ and optionally set the _size_.
|
Set the xcursor theme to _theme_name_ and optionally set the _size_.
|
||||||
@ -444,29 +462,10 @@ matches everything while _\*\*_ and the empty string are invalid.
|
|||||||
following URL:
|
following URL:
|
||||||
https://xkbcommon.org/doc/current/keymap-text-format-v1.html
|
https://xkbcommon.org/doc/current/keymap-text-format-v1.html
|
||||||
|
|
||||||
*keyboard-group-create* _group_name_
|
|
||||||
Create a keyboard group. A keyboard group collects multiple keyboards in
|
|
||||||
a single logical keyboard. This means that all state, like the active
|
|
||||||
modifiers, is shared between the keyboards in a group.
|
|
||||||
|
|
||||||
*keyboard-group-destroy* _group_name_
|
|
||||||
Destroy the keyboard group with the given name. All attached keyboards
|
|
||||||
will be released, making them act as separate devices again.
|
|
||||||
|
|
||||||
*keyboard-group-add* _group_name_ _input_device_name_
|
|
||||||
Add a keyboard to a keyboard group, identified by the keyboard's
|
|
||||||
input device name. Any currently connected and future keyboards with
|
|
||||||
the given name will be added to the group. Simple globbing patterns are
|
|
||||||
supported, see the rules section for further information on globs.
|
|
||||||
|
|
||||||
*keyboard-group-remove* _group_name_ _input_device_name_
|
|
||||||
Remove a keyboard from a keyboard group, identified by the keyboard's
|
|
||||||
input device name.
|
|
||||||
|
|
||||||
The _input_ command can be used to create a configuration rule for an input
|
The _input_ command can be used to create a configuration rule for an input
|
||||||
device identified by its _name_.
|
device identified by its _name_.
|
||||||
The _name_ of an input device consists of its type, its numerical vendor id,
|
The _name_ of an input device consists of its type, its decimal vendor id,
|
||||||
its numerical product id and finally its self-advertised name, separated by -.
|
its decimal product id and finally its self-advertised name, separated by -.
|
||||||
Simple globbing patterns are supported, see the rules section for further
|
Simple globbing patterns are supported, see the rules section for further
|
||||||
information on globs.
|
information on globs.
|
||||||
|
|
||||||
@ -536,6 +535,11 @@ However note that not every input device supports every property.
|
|||||||
Set the scroll button of an input device. _button_ is the name of a Linux
|
Set the scroll button of an input device. _button_ is the name of a Linux
|
||||||
input event code.
|
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*
|
*input* _name_ *map-to-output* _output_|*disabled*
|
||||||
Maps the input to a given output. This is valid even if the output isn't
|
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
|
currently active and will lead to the device being mapped once it is
|
||||||
|
@ -58,9 +58,15 @@ pub const HideCursorWhenTypingMode = enum {
|
|||||||
enabled,
|
enabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Anchor = enum {
|
||||||
|
absolute,
|
||||||
|
mouse,
|
||||||
|
};
|
||||||
|
|
||||||
pub const Position = struct {
|
pub const Position = struct {
|
||||||
x: u31,
|
anchor: Anchor,
|
||||||
y: u31,
|
x: i31,
|
||||||
|
y: i31,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Dimensions = struct {
|
pub const Dimensions = struct {
|
||||||
@ -68,6 +74,9 @@ pub const Dimensions = struct {
|
|||||||
height: u31,
|
height: u31,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Whether to allow tearing page flips when fullscreen if a view requests it.
|
||||||
|
allow_tearing: bool = false,
|
||||||
|
|
||||||
/// Color of background in RGBA with premultiplied alpha (alpha should only affect nested sessions)
|
/// Color of background in RGBA with premultiplied alpha (alpha should only affect nested sessions)
|
||||||
background_color: [4]f32 = [_]f32{ 0.0, 0.16862745, 0.21176471, 1.0 }, // Solarized base03
|
background_color: [4]f32 = [_]f32{ 0.0, 0.16862745, 0.21176471, 1.0 }, // Solarized base03
|
||||||
|
|
||||||
@ -98,6 +107,8 @@ rules: struct {
|
|||||||
position: RuleList(Position) = .{},
|
position: RuleList(Position) = .{},
|
||||||
dimensions: RuleList(Dimensions) = .{},
|
dimensions: RuleList(Dimensions) = .{},
|
||||||
fullscreen: RuleList(bool) = .{},
|
fullscreen: RuleList(bool) = .{},
|
||||||
|
tearing: RuleList(bool) = .{},
|
||||||
|
warp: RuleList(bool) = .{},
|
||||||
} = .{},
|
} = .{},
|
||||||
|
|
||||||
/// The selected focus_follows_cursor mode
|
/// The selected focus_follows_cursor mode
|
||||||
@ -182,6 +193,7 @@ pub fn deinit(config: *Config) void {
|
|||||||
config.rules.position.deinit();
|
config.rules.position.deinit();
|
||||||
config.rules.dimensions.deinit();
|
config.rules.dimensions.deinit();
|
||||||
config.rules.fullscreen.deinit();
|
config.rules.fullscreen.deinit();
|
||||||
|
config.rules.warp.deinit();
|
||||||
|
|
||||||
util.gpa.free(config.default_layout_namespace);
|
util.gpa.free(config.default_layout_namespace);
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ pub fn init(control: *Control) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
|
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
|
||||||
const control = @fieldParentPtr(Control, "server_destroy", listener);
|
const control: *Control = @fieldParentPtr("server_destroy", listener);
|
||||||
control.global.destroy();
|
control.global.destroy();
|
||||||
control.args_map.deinit();
|
control.args_map.deinit();
|
||||||
}
|
}
|
||||||
|
169
river/Cursor.zig
169
river/Cursor.zig
@ -19,7 +19,7 @@ const Cursor = @This();
|
|||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const os = std.os;
|
const posix = std.posix;
|
||||||
const math = std.math;
|
const math = std.math;
|
||||||
const wlr = @import("wlroots");
|
const wlr = @import("wlroots");
|
||||||
const wayland = @import("wayland");
|
const wayland = @import("wayland");
|
||||||
@ -108,6 +108,19 @@ const LayoutPoint = struct {
|
|||||||
ly: f64,
|
ly: f64,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Image = union(enum) {
|
||||||
|
/// No cursor image
|
||||||
|
none,
|
||||||
|
/// Name of the current Xcursor shape
|
||||||
|
xcursor: [*:0]const u8,
|
||||||
|
/// Cursor surface configured by a client
|
||||||
|
client: struct {
|
||||||
|
surface: *wlr.Surface,
|
||||||
|
hotspot_x: i32,
|
||||||
|
hotspot_y: i32,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const log = std.log.scoped(.cursor);
|
const log = std.log.scoped(.cursor);
|
||||||
|
|
||||||
/// Current cursor mode as well as any state needed to implement that mode
|
/// Current cursor mode as well as any state needed to implement that mode
|
||||||
@ -124,9 +137,8 @@ wlr_cursor: *wlr.Cursor,
|
|||||||
|
|
||||||
/// Xcursor manager for the currently configured Xcursor theme.
|
/// Xcursor manager for the currently configured Xcursor theme.
|
||||||
xcursor_manager: *wlr.XcursorManager,
|
xcursor_manager: *wlr.XcursorManager,
|
||||||
/// Name of the current Xcursor shape, or null if a client has configured a
|
image: Image = .none,
|
||||||
/// surface to be used as the cursor shape instead.
|
image_surface_destroy: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleImageSurfaceDestroy),
|
||||||
xcursor_name: ?[*:0]const u8 = null,
|
|
||||||
|
|
||||||
/// Number of distinct buttons currently pressed
|
/// Number of distinct buttons currently pressed
|
||||||
pressed_count: u32 = 0,
|
pressed_count: u32 = 0,
|
||||||
@ -286,24 +298,46 @@ pub fn setTheme(cursor: *Cursor, theme: ?[*:0]const u8, _size: ?u32) !void {
|
|||||||
cursor.xcursor_manager.destroy();
|
cursor.xcursor_manager.destroy();
|
||||||
cursor.xcursor_manager = xcursor_manager;
|
cursor.xcursor_manager = xcursor_manager;
|
||||||
|
|
||||||
if (cursor.xcursor_name) |name| {
|
switch (cursor.image) {
|
||||||
cursor.setXcursor(name);
|
.none, .client => {},
|
||||||
|
.xcursor => |name| cursor.wlr_cursor.setXcursor(xcursor_manager, name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setXcursor(cursor: *Cursor, name: [*:0]const u8) void {
|
pub fn setImage(cursor: *Cursor, image: Image) void {
|
||||||
cursor.wlr_cursor.setXcursor(cursor.xcursor_manager, name);
|
switch (cursor.image) {
|
||||||
cursor.xcursor_name = name;
|
.none, .xcursor => {},
|
||||||
|
.client => {
|
||||||
|
cursor.image_surface_destroy.link.remove();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cursor.image = image;
|
||||||
|
switch (cursor.image) {
|
||||||
|
.none => cursor.wlr_cursor.unsetImage(),
|
||||||
|
.xcursor => |name| cursor.wlr_cursor.setXcursor(cursor.xcursor_manager, name),
|
||||||
|
.client => |client| {
|
||||||
|
cursor.wlr_cursor.setSurface(client.surface, client.hotspot_x, client.hotspot_y);
|
||||||
|
client.surface.events.destroy.add(&cursor.image_surface_destroy);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleImageSurfaceDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
||||||
|
const cursor: *Cursor = @fieldParentPtr("image_surface_destroy", listener);
|
||||||
|
// wlroots calls wlr_cursor_unset_image() automatically
|
||||||
|
// when the cursor surface is destroyed.
|
||||||
|
cursor.image = .none;
|
||||||
|
cursor.image_surface_destroy.link.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clearFocus(cursor: *Cursor) void {
|
fn clearFocus(cursor: *Cursor) void {
|
||||||
cursor.setXcursor("default");
|
cursor.setImage(.{ .xcursor = "default" });
|
||||||
cursor.seat.wlr_seat.pointerNotifyClearFocus();
|
cursor.seat.wlr_seat.pointerNotifyClearFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Axis event is a scroll wheel or similiar
|
/// Axis event is a scroll wheel or similiar
|
||||||
fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Pointer.event.Axis) void {
|
fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Pointer.event.Axis) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "axis", listener);
|
const cursor: *Cursor = @fieldParentPtr("axis", listener);
|
||||||
const device: *InputDevice = @ptrFromInt(event.device.data);
|
const device: *InputDevice = @ptrFromInt(event.device.data);
|
||||||
|
|
||||||
cursor.seat.handleActivity();
|
cursor.seat.handleActivity();
|
||||||
@ -324,11 +358,12 @@ fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Point
|
|||||||
math.maxInt(i32) / 2,
|
math.maxInt(i32) / 2,
|
||||||
)),
|
)),
|
||||||
event.source,
|
event.source,
|
||||||
|
event.relative_direction,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.Pointer.event.Button) void {
|
fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.Pointer.event.Button) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "button", listener);
|
const cursor: *Cursor = @fieldParentPtr("button", listener);
|
||||||
|
|
||||||
cursor.seat.handleActivity();
|
cursor.seat.handleActivity();
|
||||||
cursor.unhide();
|
cursor.unhide();
|
||||||
@ -432,7 +467,7 @@ fn handlePinchBegin(
|
|||||||
listener: *wl.Listener(*wlr.Pointer.event.PinchBegin),
|
listener: *wl.Listener(*wlr.Pointer.event.PinchBegin),
|
||||||
event: *wlr.Pointer.event.PinchBegin,
|
event: *wlr.Pointer.event.PinchBegin,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "pinch_begin", listener);
|
const cursor: *Cursor = @fieldParentPtr("pinch_begin", listener);
|
||||||
server.input_manager.pointer_gestures.sendPinchBegin(
|
server.input_manager.pointer_gestures.sendPinchBegin(
|
||||||
cursor.seat.wlr_seat,
|
cursor.seat.wlr_seat,
|
||||||
event.time_msec,
|
event.time_msec,
|
||||||
@ -444,7 +479,7 @@ fn handlePinchUpdate(
|
|||||||
listener: *wl.Listener(*wlr.Pointer.event.PinchUpdate),
|
listener: *wl.Listener(*wlr.Pointer.event.PinchUpdate),
|
||||||
event: *wlr.Pointer.event.PinchUpdate,
|
event: *wlr.Pointer.event.PinchUpdate,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "pinch_update", listener);
|
const cursor: *Cursor = @fieldParentPtr("pinch_update", listener);
|
||||||
server.input_manager.pointer_gestures.sendPinchUpdate(
|
server.input_manager.pointer_gestures.sendPinchUpdate(
|
||||||
cursor.seat.wlr_seat,
|
cursor.seat.wlr_seat,
|
||||||
event.time_msec,
|
event.time_msec,
|
||||||
@ -459,7 +494,7 @@ fn handlePinchEnd(
|
|||||||
listener: *wl.Listener(*wlr.Pointer.event.PinchEnd),
|
listener: *wl.Listener(*wlr.Pointer.event.PinchEnd),
|
||||||
event: *wlr.Pointer.event.PinchEnd,
|
event: *wlr.Pointer.event.PinchEnd,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "pinch_end", listener);
|
const cursor: *Cursor = @fieldParentPtr("pinch_end", listener);
|
||||||
server.input_manager.pointer_gestures.sendPinchEnd(
|
server.input_manager.pointer_gestures.sendPinchEnd(
|
||||||
cursor.seat.wlr_seat,
|
cursor.seat.wlr_seat,
|
||||||
event.time_msec,
|
event.time_msec,
|
||||||
@ -471,7 +506,7 @@ fn handleSwipeBegin(
|
|||||||
listener: *wl.Listener(*wlr.Pointer.event.SwipeBegin),
|
listener: *wl.Listener(*wlr.Pointer.event.SwipeBegin),
|
||||||
event: *wlr.Pointer.event.SwipeBegin,
|
event: *wlr.Pointer.event.SwipeBegin,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "swipe_begin", listener);
|
const cursor: *Cursor = @fieldParentPtr("swipe_begin", listener);
|
||||||
server.input_manager.pointer_gestures.sendSwipeBegin(
|
server.input_manager.pointer_gestures.sendSwipeBegin(
|
||||||
cursor.seat.wlr_seat,
|
cursor.seat.wlr_seat,
|
||||||
event.time_msec,
|
event.time_msec,
|
||||||
@ -483,7 +518,7 @@ fn handleSwipeUpdate(
|
|||||||
listener: *wl.Listener(*wlr.Pointer.event.SwipeUpdate),
|
listener: *wl.Listener(*wlr.Pointer.event.SwipeUpdate),
|
||||||
event: *wlr.Pointer.event.SwipeUpdate,
|
event: *wlr.Pointer.event.SwipeUpdate,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "swipe_update", listener);
|
const cursor: *Cursor = @fieldParentPtr("swipe_update", listener);
|
||||||
server.input_manager.pointer_gestures.sendSwipeUpdate(
|
server.input_manager.pointer_gestures.sendSwipeUpdate(
|
||||||
cursor.seat.wlr_seat,
|
cursor.seat.wlr_seat,
|
||||||
event.time_msec,
|
event.time_msec,
|
||||||
@ -496,7 +531,7 @@ fn handleSwipeEnd(
|
|||||||
listener: *wl.Listener(*wlr.Pointer.event.SwipeEnd),
|
listener: *wl.Listener(*wlr.Pointer.event.SwipeEnd),
|
||||||
event: *wlr.Pointer.event.SwipeEnd,
|
event: *wlr.Pointer.event.SwipeEnd,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "swipe_end", listener);
|
const cursor: *Cursor = @fieldParentPtr("swipe_end", listener);
|
||||||
server.input_manager.pointer_gestures.sendSwipeEnd(
|
server.input_manager.pointer_gestures.sendSwipeEnd(
|
||||||
cursor.seat.wlr_seat,
|
cursor.seat.wlr_seat,
|
||||||
event.time_msec,
|
event.time_msec,
|
||||||
@ -508,7 +543,7 @@ fn handleTouchDown(
|
|||||||
listener: *wl.Listener(*wlr.Touch.event.Down),
|
listener: *wl.Listener(*wlr.Touch.event.Down),
|
||||||
event: *wlr.Touch.event.Down,
|
event: *wlr.Touch.event.Down,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "touch_down", listener);
|
const cursor: *Cursor = @fieldParentPtr("touch_down", listener);
|
||||||
|
|
||||||
cursor.seat.handleActivity();
|
cursor.seat.handleActivity();
|
||||||
|
|
||||||
@ -544,7 +579,7 @@ fn handleTouchMotion(
|
|||||||
listener: *wl.Listener(*wlr.Touch.event.Motion),
|
listener: *wl.Listener(*wlr.Touch.event.Motion),
|
||||||
event: *wlr.Touch.event.Motion,
|
event: *wlr.Touch.event.Motion,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "touch_motion", listener);
|
const cursor: *Cursor = @fieldParentPtr("touch_motion", listener);
|
||||||
|
|
||||||
cursor.seat.handleActivity();
|
cursor.seat.handleActivity();
|
||||||
|
|
||||||
@ -563,12 +598,12 @@ fn handleTouchUp(
|
|||||||
listener: *wl.Listener(*wlr.Touch.event.Up),
|
listener: *wl.Listener(*wlr.Touch.event.Up),
|
||||||
event: *wlr.Touch.event.Up,
|
event: *wlr.Touch.event.Up,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "touch_up", listener);
|
const cursor: *Cursor = @fieldParentPtr("touch_up", listener);
|
||||||
|
|
||||||
cursor.seat.handleActivity();
|
cursor.seat.handleActivity();
|
||||||
|
|
||||||
if (cursor.touch_points.remove(event.touch_id)) {
|
if (cursor.touch_points.remove(event.touch_id)) {
|
||||||
cursor.seat.wlr_seat.touchNotifyUp(event.time_msec, event.touch_id);
|
_ = cursor.seat.wlr_seat.touchNotifyUp(event.time_msec, event.touch_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -576,43 +611,20 @@ fn handleTouchCancel(
|
|||||||
listener: *wl.Listener(*wlr.Touch.event.Cancel),
|
listener: *wl.Listener(*wlr.Touch.event.Cancel),
|
||||||
_: *wlr.Touch.event.Cancel,
|
_: *wlr.Touch.event.Cancel,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "touch_cancel", listener);
|
const cursor: *Cursor = @fieldParentPtr("touch_cancel", listener);
|
||||||
|
|
||||||
cursor.seat.handleActivity();
|
cursor.seat.handleActivity();
|
||||||
|
|
||||||
cursor.touch_points.clearRetainingCapacity();
|
cursor.touch_points.clearRetainingCapacity();
|
||||||
|
|
||||||
// We can't call touchNotifyCancel() from inside the loop over touch points as it also loops
|
const wlr_seat = cursor.seat.wlr_seat;
|
||||||
// over touch points and may destroy multiple touch points in a single call.
|
while (wlr_seat.touch_state.touch_points.first()) |touch_point| {
|
||||||
//
|
wlr_seat.touchNotifyCancel(touch_point.client);
|
||||||
// What we should do here is `while (touch_points.first()) |point| cancel()` but since the
|
|
||||||
// surface may be null we can't rely on the fact tha all touch points will be destroyed
|
|
||||||
// and risk an infinite loop if the surface of any wlr_touch_point is null.
|
|
||||||
//
|
|
||||||
// This is all really silly and totally unnecessary since all touchNotifyCancel() does with
|
|
||||||
// the surface argument is obtain a seat client and touch_point.seat_client is never null.
|
|
||||||
// TODO(wlroots) clean this up after the wlroots MR fixing this is merged:
|
|
||||||
// https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4613
|
|
||||||
|
|
||||||
// The upper bound of 32 comes from an implementation detail of libinput which uses
|
|
||||||
// a 32-bit integer as a map to keep track of touch points.
|
|
||||||
var surfaces: std.BoundedArray(*wlr.Surface, 32) = .{};
|
|
||||||
{
|
|
||||||
var it = cursor.seat.wlr_seat.touch_state.touch_points.iterator(.forward);
|
|
||||||
while (it.next()) |touch_point| {
|
|
||||||
if (touch_point.surface) |surface| {
|
|
||||||
surfaces.append(surface) catch break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (surfaces.slice()) |surface| {
|
|
||||||
cursor.seat.wlr_seat.touchNotifyCancel(surface);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleTouchFrame(listener: *wl.Listener(void)) void {
|
fn handleTouchFrame(listener: *wl.Listener(void)) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "touch_frame", listener);
|
const cursor: *Cursor = @fieldParentPtr("touch_frame", listener);
|
||||||
|
|
||||||
cursor.seat.handleActivity();
|
cursor.seat.handleActivity();
|
||||||
|
|
||||||
@ -624,7 +636,7 @@ fn handleTabletToolAxis(
|
|||||||
event: *wlr.Tablet.event.Axis,
|
event: *wlr.Tablet.event.Axis,
|
||||||
) void {
|
) void {
|
||||||
const device: *InputDevice = @ptrFromInt(event.device.data);
|
const device: *InputDevice = @ptrFromInt(event.device.data);
|
||||||
const tablet = @fieldParentPtr(Tablet, "device", device);
|
const tablet: *Tablet = @fieldParentPtr("device", device);
|
||||||
|
|
||||||
device.seat.handleActivity();
|
device.seat.handleActivity();
|
||||||
|
|
||||||
@ -638,7 +650,7 @@ fn handleTabletToolProximity(
|
|||||||
event: *wlr.Tablet.event.Proximity,
|
event: *wlr.Tablet.event.Proximity,
|
||||||
) void {
|
) void {
|
||||||
const device: *InputDevice = @ptrFromInt(event.device.data);
|
const device: *InputDevice = @ptrFromInt(event.device.data);
|
||||||
const tablet = @fieldParentPtr(Tablet, "device", device);
|
const tablet: *Tablet = @fieldParentPtr("device", device);
|
||||||
|
|
||||||
device.seat.handleActivity();
|
device.seat.handleActivity();
|
||||||
|
|
||||||
@ -652,7 +664,7 @@ fn handleTabletToolTip(
|
|||||||
event: *wlr.Tablet.event.Tip,
|
event: *wlr.Tablet.event.Tip,
|
||||||
) void {
|
) void {
|
||||||
const device: *InputDevice = @ptrFromInt(event.device.data);
|
const device: *InputDevice = @ptrFromInt(event.device.data);
|
||||||
const tablet = @fieldParentPtr(Tablet, "device", device);
|
const tablet: *Tablet = @fieldParentPtr("device", device);
|
||||||
|
|
||||||
device.seat.handleActivity();
|
device.seat.handleActivity();
|
||||||
|
|
||||||
@ -666,7 +678,7 @@ fn handleTabletToolButton(
|
|||||||
event: *wlr.Tablet.event.Button,
|
event: *wlr.Tablet.event.Button,
|
||||||
) void {
|
) void {
|
||||||
const device: *InputDevice = @ptrFromInt(event.device.data);
|
const device: *InputDevice = @ptrFromInt(event.device.data);
|
||||||
const tablet = @fieldParentPtr(Tablet, "device", device);
|
const tablet: *Tablet = @fieldParentPtr("device", device);
|
||||||
|
|
||||||
device.seat.handleActivity();
|
device.seat.handleActivity();
|
||||||
|
|
||||||
@ -706,7 +718,7 @@ fn handlePointerMapping(cursor: *Cursor, event: *wlr.Pointer.event.Button, view:
|
|||||||
/// events together. For instance, two axis events may happen at the same
|
/// events together. For instance, two axis events may happen at the same
|
||||||
/// time, in which case a frame event won't be sent in between.
|
/// time, in which case a frame event won't be sent in between.
|
||||||
fn handleFrame(listener: *wl.Listener(*wlr.Cursor), _: *wlr.Cursor) void {
|
fn handleFrame(listener: *wl.Listener(*wlr.Cursor), _: *wlr.Cursor) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "frame", listener);
|
const cursor: *Cursor = @fieldParentPtr("frame", listener);
|
||||||
cursor.seat.wlr_seat.pointerNotifyFrame();
|
cursor.seat.wlr_seat.pointerNotifyFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,7 +732,7 @@ fn handleMotionAbsolute(
|
|||||||
listener: *wl.Listener(*wlr.Pointer.event.MotionAbsolute),
|
listener: *wl.Listener(*wlr.Pointer.event.MotionAbsolute),
|
||||||
event: *wlr.Pointer.event.MotionAbsolute,
|
event: *wlr.Pointer.event.MotionAbsolute,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "motion_absolute", listener);
|
const cursor: *Cursor = @fieldParentPtr("motion_absolute", listener);
|
||||||
|
|
||||||
cursor.seat.handleActivity();
|
cursor.seat.handleActivity();
|
||||||
|
|
||||||
@ -739,7 +751,7 @@ fn handleMotion(
|
|||||||
listener: *wl.Listener(*wlr.Pointer.event.Motion),
|
listener: *wl.Listener(*wlr.Pointer.event.Motion),
|
||||||
event: *wlr.Pointer.event.Motion,
|
event: *wlr.Pointer.event.Motion,
|
||||||
) void {
|
) void {
|
||||||
const cursor = @fieldParentPtr(Cursor, "motion", listener);
|
const cursor: *Cursor = @fieldParentPtr("motion", listener);
|
||||||
|
|
||||||
cursor.seat.handleActivity();
|
cursor.seat.handleActivity();
|
||||||
|
|
||||||
@ -751,7 +763,7 @@ fn handleRequestSetCursor(
|
|||||||
event: *wlr.Seat.event.RequestSetCursor,
|
event: *wlr.Seat.event.RequestSetCursor,
|
||||||
) void {
|
) void {
|
||||||
// This event is rasied by the seat when a client provides a cursor image
|
// This event is rasied by the seat when a client provides a cursor image
|
||||||
const cursor = @fieldParentPtr(Cursor, "request_set_cursor", listener);
|
const cursor: *Cursor = @fieldParentPtr("request_set_cursor", listener);
|
||||||
const focused_client = cursor.seat.wlr_seat.pointer_state.focused_client;
|
const focused_client = cursor.seat.wlr_seat.pointer_state.focused_client;
|
||||||
|
|
||||||
// This can be sent by any client, so we check to make sure this one is
|
// This can be sent by any client, so we check to make sure this one is
|
||||||
@ -762,8 +774,15 @@ fn handleRequestSetCursor(
|
|||||||
// on the output that it's currently on and continue to do so as the
|
// on the output that it's currently on and continue to do so as the
|
||||||
// cursor moves between outputs.
|
// cursor moves between outputs.
|
||||||
log.debug("focused client set cursor", .{});
|
log.debug("focused client set cursor", .{});
|
||||||
cursor.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y);
|
if (event.surface) |surface| {
|
||||||
cursor.xcursor_name = null;
|
cursor.setImage(.{ .client = .{
|
||||||
|
.surface = surface,
|
||||||
|
.hotspot_x = event.hotspot_x,
|
||||||
|
.hotspot_y = event.hotspot_y,
|
||||||
|
} });
|
||||||
|
} else {
|
||||||
|
cursor.setImage(.none);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -779,8 +798,6 @@ pub fn hide(cursor: *Cursor) void {
|
|||||||
|
|
||||||
cursor.hidden = true;
|
cursor.hidden = true;
|
||||||
cursor.wlr_cursor.unsetImage();
|
cursor.wlr_cursor.unsetImage();
|
||||||
cursor.xcursor_name = null;
|
|
||||||
cursor.seat.wlr_seat.pointerNotifyClearFocus();
|
|
||||||
cursor.hide_cursor_timer.timerUpdate(0) catch {
|
cursor.hide_cursor_timer.timerUpdate(0) catch {
|
||||||
log.err("failed to update cursor hide timeout", .{});
|
log.err("failed to update cursor hide timeout", .{});
|
||||||
};
|
};
|
||||||
@ -792,6 +809,7 @@ pub fn unhide(cursor: *Cursor) void {
|
|||||||
};
|
};
|
||||||
if (!cursor.hidden) return;
|
if (!cursor.hidden) return;
|
||||||
cursor.hidden = false;
|
cursor.hidden = false;
|
||||||
|
cursor.setImage(cursor.image);
|
||||||
cursor.updateState();
|
cursor.updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -890,7 +908,7 @@ fn computeEdges(cursor: *const Cursor, view: *const View) wlr.Edges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enterMode(cursor: *Cursor, mode: Mode, view: *View, xcursor_name: [*:0]const u8) void {
|
fn enterMode(cursor: *Cursor, mode: Mode, view: *View, xcursor: [*:0]const u8) void {
|
||||||
assert(cursor.mode == .passthrough or cursor.mode == .down);
|
assert(cursor.mode == .passthrough or cursor.mode == .down);
|
||||||
assert(mode == .move or mode == .resize);
|
assert(mode == .move or mode == .resize);
|
||||||
|
|
||||||
@ -906,7 +924,7 @@ fn enterMode(cursor: *Cursor, mode: Mode, view: *View, xcursor_name: [*:0]const
|
|||||||
}
|
}
|
||||||
|
|
||||||
cursor.seat.wlr_seat.pointerNotifyClearFocus();
|
cursor.seat.wlr_seat.pointerNotifyClearFocus();
|
||||||
cursor.setXcursor(xcursor_name);
|
cursor.setImage(.{ .xcursor = xcursor });
|
||||||
|
|
||||||
server.root.applyPending();
|
server.root.applyPending();
|
||||||
}
|
}
|
||||||
@ -1111,10 +1129,15 @@ pub fn updateState(cursor: *Cursor) void {
|
|||||||
.passthrough => {
|
.passthrough => {
|
||||||
cursor.updateFocusFollowsCursorTarget();
|
cursor.updateFocusFollowsCursorTarget();
|
||||||
if (!cursor.hidden) {
|
if (!cursor.hidden) {
|
||||||
var now: os.timespec = undefined;
|
var now: posix.timespec = undefined;
|
||||||
os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
posix.clock_gettime(posix.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
||||||
const msec: u32 = @intCast(now.tv_sec * std.time.ms_per_s +
|
// 2^32-1 milliseconds is ~50 days, which is a realistic uptime.
|
||||||
@divTrunc(now.tv_nsec, std.time.ns_per_ms));
|
// This means that we must wrap if the monotonic time is greater than
|
||||||
|
// 2^32-1 milliseconds and hope that clients don't get too confused.
|
||||||
|
const msec: u32 = @intCast(@rem(
|
||||||
|
now.tv_sec *% std.time.ms_per_s +% @divTrunc(now.tv_nsec, std.time.ns_per_ms),
|
||||||
|
math.maxInt(u32),
|
||||||
|
));
|
||||||
cursor.passthrough(msec);
|
cursor.passthrough(msec);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1200,10 +1223,18 @@ fn warp(cursor: *Cursor) void {
|
|||||||
|
|
||||||
const focused_output = cursor.seat.focused_output orelse return;
|
const focused_output = cursor.seat.focused_output orelse return;
|
||||||
|
|
||||||
|
var mode = server.config.warp_cursor;
|
||||||
|
if (cursor.seat.focused == .view) {
|
||||||
|
const view = cursor.seat.focused.view;
|
||||||
|
if (server.config.rules.warp.match(view)) |w| {
|
||||||
|
mode = if (w) .@"on-focus-change" else .disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Warp pointer to center of the focused view/output (In layout coordinates) if enabled.
|
// Warp pointer to center of the focused view/output (In layout coordinates) if enabled.
|
||||||
var output_layout_box: wlr.Box = undefined;
|
var output_layout_box: wlr.Box = undefined;
|
||||||
server.root.output_layout.getBox(focused_output.wlr_output, &output_layout_box);
|
server.root.output_layout.getBox(focused_output.wlr_output, &output_layout_box);
|
||||||
const target_box = switch (server.config.warp_cursor) {
|
const target_box = switch (mode) {
|
||||||
.disabled => return,
|
.disabled => return,
|
||||||
.@"on-output-change" => output_layout_box,
|
.@"on-output-change" => output_layout_box,
|
||||||
.@"on-focus-change" => switch (cursor.seat.focused) {
|
.@"on-focus-change" => switch (cursor.seat.focused) {
|
||||||
|
@ -71,7 +71,7 @@ pub fn updatePosition(drag_icon: *DragIcon, cursor: *Cursor) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void {
|
fn handleDestroy(listener: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void {
|
||||||
const drag_icon = @fieldParentPtr(DragIcon, "destroy", listener);
|
const drag_icon: *DragIcon = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
drag_icon.destroy.link.remove();
|
drag_icon.destroy.link.remove();
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ foreign_close: wl.Listener(*wlr.ForeignToplevelHandleV1) =
|
|||||||
wl.Listener(*wlr.ForeignToplevelHandleV1).init(handleForeignClose),
|
wl.Listener(*wlr.ForeignToplevelHandleV1).init(handleForeignClose),
|
||||||
|
|
||||||
pub fn map(handle: *ForeignToplevelHandle) void {
|
pub fn map(handle: *ForeignToplevelHandle) void {
|
||||||
const view = @fieldParentPtr(View, "foreign_toplevel_handle", handle);
|
const view: *View = @fieldParentPtr("foreign_toplevel_handle", handle);
|
||||||
|
|
||||||
assert(handle.wlr_handle == null);
|
assert(handle.wlr_handle == null);
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ pub fn unmap(handle: *ForeignToplevelHandle) void {
|
|||||||
|
|
||||||
/// Must be called just before the view's inflight state is made current.
|
/// Must be called just before the view's inflight state is made current.
|
||||||
pub fn update(handle: *ForeignToplevelHandle) void {
|
pub fn update(handle: *ForeignToplevelHandle) void {
|
||||||
const view = @fieldParentPtr(View, "foreign_toplevel_handle", handle);
|
const view: *View = @fieldParentPtr("foreign_toplevel_handle", handle);
|
||||||
|
|
||||||
const wlr_handle = handle.wlr_handle orelse return;
|
const wlr_handle = handle.wlr_handle orelse return;
|
||||||
|
|
||||||
@ -87,8 +87,8 @@ fn handleForeignActivate(
|
|||||||
listener: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Activated),
|
listener: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Activated),
|
||||||
event: *wlr.ForeignToplevelHandleV1.event.Activated,
|
event: *wlr.ForeignToplevelHandleV1.event.Activated,
|
||||||
) void {
|
) void {
|
||||||
const handle = @fieldParentPtr(ForeignToplevelHandle, "foreign_activate", listener);
|
const handle: *ForeignToplevelHandle = @fieldParentPtr("foreign_activate", listener);
|
||||||
const view = @fieldParentPtr(View, "foreign_toplevel_handle", handle);
|
const view: *View = @fieldParentPtr("foreign_toplevel_handle", handle);
|
||||||
const seat: *Seat = @ptrFromInt(event.seat.data);
|
const seat: *Seat = @ptrFromInt(event.seat.data);
|
||||||
|
|
||||||
seat.focus(view);
|
seat.focus(view);
|
||||||
@ -99,8 +99,8 @@ fn handleForeignFullscreen(
|
|||||||
listener: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen),
|
listener: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen),
|
||||||
event: *wlr.ForeignToplevelHandleV1.event.Fullscreen,
|
event: *wlr.ForeignToplevelHandleV1.event.Fullscreen,
|
||||||
) void {
|
) void {
|
||||||
const handle = @fieldParentPtr(ForeignToplevelHandle, "foreign_fullscreen", listener);
|
const handle: *ForeignToplevelHandle = @fieldParentPtr("foreign_fullscreen", listener);
|
||||||
const view = @fieldParentPtr(View, "foreign_toplevel_handle", handle);
|
const view: *View = @fieldParentPtr("foreign_toplevel_handle", handle);
|
||||||
|
|
||||||
view.pending.fullscreen = event.fullscreen;
|
view.pending.fullscreen = event.fullscreen;
|
||||||
server.root.applyPending();
|
server.root.applyPending();
|
||||||
@ -110,8 +110,8 @@ fn handleForeignClose(
|
|||||||
listener: *wl.Listener(*wlr.ForeignToplevelHandleV1),
|
listener: *wl.Listener(*wlr.ForeignToplevelHandleV1),
|
||||||
_: *wlr.ForeignToplevelHandleV1,
|
_: *wlr.ForeignToplevelHandleV1,
|
||||||
) void {
|
) void {
|
||||||
const handle = @fieldParentPtr(ForeignToplevelHandle, "foreign_close", listener);
|
const handle: *ForeignToplevelHandle = @fieldParentPtr("foreign_close", listener);
|
||||||
const view = @fieldParentPtr(View, "foreign_toplevel_handle", handle);
|
const view: *View = @fieldParentPtr("foreign_toplevel_handle", handle);
|
||||||
|
|
||||||
view.close();
|
view.close();
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ pub fn checkActive(inhibit_manager: *IdleInhibitManager) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleNewIdleInhibitor(listener: *wl.Listener(*wlr.IdleInhibitorV1), inhibitor: *wlr.IdleInhibitorV1) void {
|
fn handleNewIdleInhibitor(listener: *wl.Listener(*wlr.IdleInhibitorV1), inhibitor: *wlr.IdleInhibitorV1) void {
|
||||||
const inhibit_manager = @fieldParentPtr(IdleInhibitManager, "new_idle_inhibitor", listener);
|
const inhibit_manager: *IdleInhibitManager = @fieldParentPtr("new_idle_inhibitor", listener);
|
||||||
const inhibitor_node = util.gpa.create(std.TailQueue(IdleInhibitor).Node) catch return;
|
const inhibitor_node = util.gpa.create(std.TailQueue(IdleInhibitor).Node) catch return;
|
||||||
inhibitor_node.data.init(inhibitor, inhibit_manager) catch {
|
inhibitor_node.data.init(inhibitor, inhibit_manager) catch {
|
||||||
util.gpa.destroy(inhibitor_node);
|
util.gpa.destroy(inhibitor_node);
|
||||||
|
@ -45,11 +45,11 @@ pub fn init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
fn handleDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
||||||
const inhibitor = @fieldParentPtr(IdleInhibitor, "destroy", listener);
|
const inhibitor: *IdleInhibitor = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
inhibitor.destroy.link.remove();
|
inhibitor.destroy.link.remove();
|
||||||
|
|
||||||
const node = @fieldParentPtr(std.TailQueue(IdleInhibitor).Node, "data", inhibitor);
|
const node: *std.TailQueue(IdleInhibitor).Node = @fieldParentPtr("data", inhibitor);
|
||||||
server.idle_inhibit_manager.inhibitors.remove(node);
|
server.idle_inhibit_manager.inhibitors.remove(node);
|
||||||
|
|
||||||
inhibitor.inhibit_manager.checkActive();
|
inhibitor.inhibit_manager.checkActive();
|
||||||
|
@ -215,6 +215,18 @@ pub const ScrollButton = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const ScrollButtonLock = enum {
|
||||||
|
enabled,
|
||||||
|
disabled,
|
||||||
|
|
||||||
|
fn apply(scroll_button_lock: ScrollButtonLock, device: *c.libinput_device) void {
|
||||||
|
_ = c.libinput_device_config_scroll_set_button_lock(device, switch (scroll_button_lock) {
|
||||||
|
.enabled => c.LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED,
|
||||||
|
.disabled => c.LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const MapToOutput = struct {
|
pub const MapToOutput = struct {
|
||||||
output_name: ?[]const u8,
|
output_name: ?[]const u8,
|
||||||
|
|
||||||
@ -232,7 +244,7 @@ pub const MapToOutput = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
switch (device.wlr_device.type) {
|
switch (device.wlr_device.type) {
|
||||||
.pointer, .touch, .tablet_tool => {
|
.pointer, .touch, .tablet => {
|
||||||
log.debug("mapping input '{s}' -> '{s}'", .{
|
log.debug("mapping input '{s}' -> '{s}'", .{
|
||||||
device.identifier,
|
device.identifier,
|
||||||
if (wlr_output) |o| o.name else "<no output>",
|
if (wlr_output) |o| o.name else "<no output>",
|
||||||
@ -240,14 +252,14 @@ pub const MapToOutput = struct {
|
|||||||
|
|
||||||
device.seat.cursor.wlr_cursor.mapInputToOutput(device.wlr_device, wlr_output);
|
device.seat.cursor.wlr_cursor.mapInputToOutput(device.wlr_device, wlr_output);
|
||||||
|
|
||||||
if (device.wlr_device.type == .tablet_tool) {
|
if (device.wlr_device.type == .tablet) {
|
||||||
const tablet = @fieldParentPtr(Tablet, "device", device);
|
const tablet: *Tablet = @fieldParentPtr("device", device);
|
||||||
tablet.output_mapping = wlr_output;
|
tablet.output_mapping = wlr_output;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// These devices do not support being mapped to outputs.
|
// These devices do not support being mapped to outputs.
|
||||||
.keyboard, .tablet_pad, .switch_device => {},
|
.keyboard, .tablet_pad, .@"switch" => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -279,6 +291,7 @@ tap: ?TapState = null,
|
|||||||
@"pointer-accel": ?PointerAccel = null,
|
@"pointer-accel": ?PointerAccel = null,
|
||||||
@"scroll-method": ?ScrollMethod = null,
|
@"scroll-method": ?ScrollMethod = null,
|
||||||
@"scroll-button": ?ScrollButton = null,
|
@"scroll-button": ?ScrollButton = null,
|
||||||
|
@"scroll-button-lock": ?ScrollButtonLock = null,
|
||||||
@"map-to-output": ?MapToOutput = null,
|
@"map-to-output": ?MapToOutput = null,
|
||||||
|
|
||||||
pub fn deinit(config: *InputConfig) void {
|
pub fn deinit(config: *InputConfig) void {
|
||||||
|
@ -24,6 +24,7 @@ const wl = @import("wayland").server.wl;
|
|||||||
|
|
||||||
const globber = @import("globber");
|
const globber = @import("globber");
|
||||||
|
|
||||||
|
const c = @import("c.zig");
|
||||||
const server = &@import("main.zig").server;
|
const server = &@import("main.zig").server;
|
||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
|
|
||||||
@ -52,19 +53,21 @@ config: struct {
|
|||||||
link: wl.list.Link,
|
link: wl.list.Link,
|
||||||
|
|
||||||
pub fn init(device: *InputDevice, seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
pub fn init(device: *InputDevice, seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
||||||
const device_type: []const u8 = switch (wlr_device.type) {
|
var vendor: c_uint = 0;
|
||||||
.switch_device => "switch",
|
var product: c_uint = 0;
|
||||||
.tablet_tool => "tablet",
|
|
||||||
else => @tagName(wlr_device.type),
|
if (wlr_device.getLibinputDevice()) |d| {
|
||||||
};
|
vendor = c.libinput_device_get_id_vendor(@ptrCast(d));
|
||||||
|
product = c.libinput_device_get_id_product(@ptrCast(d));
|
||||||
|
}
|
||||||
|
|
||||||
const identifier = try std.fmt.allocPrint(
|
const identifier = try std.fmt.allocPrint(
|
||||||
util.gpa,
|
util.gpa,
|
||||||
"{s}-{}-{}-{s}",
|
"{s}-{}-{}-{s}",
|
||||||
.{
|
.{
|
||||||
device_type,
|
@tagName(wlr_device.type),
|
||||||
wlr_device.vendor,
|
vendor,
|
||||||
wlr_device.product,
|
product,
|
||||||
mem.trim(u8, mem.sliceTo(wlr_device.name orelse "unknown", 0), &ascii.whitespace),
|
mem.trim(u8, mem.sliceTo(wlr_device.name orelse "unknown", 0), &ascii.whitespace),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -125,13 +128,13 @@ fn isKeyboardGroup(wlr_device: *wlr.InputDevice) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice) void {
|
fn handleDestroy(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice) void {
|
||||||
const device = @fieldParentPtr(InputDevice, "destroy", listener);
|
const device: *InputDevice = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
log.debug("removed input device: {s}", .{device.identifier});
|
log.debug("removed input device: {s}", .{device.identifier});
|
||||||
|
|
||||||
switch (device.wlr_device.type) {
|
switch (device.wlr_device.type) {
|
||||||
.keyboard => {
|
.keyboard => {
|
||||||
const keyboard = @fieldParentPtr(Keyboard, "device", device);
|
const keyboard: *Keyboard = @fieldParentPtr("device", device);
|
||||||
keyboard.deinit();
|
keyboard.deinit();
|
||||||
util.gpa.destroy(keyboard);
|
util.gpa.destroy(keyboard);
|
||||||
},
|
},
|
||||||
@ -139,12 +142,12 @@ fn handleDestroy(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice)
|
|||||||
device.deinit();
|
device.deinit();
|
||||||
util.gpa.destroy(device);
|
util.gpa.destroy(device);
|
||||||
},
|
},
|
||||||
.tablet_tool => {
|
.tablet => {
|
||||||
const tablet = @fieldParentPtr(Tablet, "device", device);
|
const tablet: *Tablet = @fieldParentPtr("device", device);
|
||||||
tablet.destroy();
|
tablet.destroy();
|
||||||
},
|
},
|
||||||
.switch_device => {
|
.@"switch" => {
|
||||||
const switch_device = @fieldParentPtr(Switch, "device", device);
|
const switch_device: *Switch = @fieldParentPtr("device", device);
|
||||||
switch_device.deinit();
|
switch_device.deinit();
|
||||||
util.gpa.destroy(switch_device);
|
util.gpa.destroy(switch_device);
|
||||||
},
|
},
|
||||||
|
@ -158,7 +158,7 @@ pub fn reconfigureDevices(input_manager: *InputManager) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleNewInput(listener: *wl.Listener(*wlr.InputDevice), wlr_device: *wlr.InputDevice) void {
|
fn handleNewInput(listener: *wl.Listener(*wlr.InputDevice), wlr_device: *wlr.InputDevice) void {
|
||||||
const input_manager = @fieldParentPtr(InputManager, "new_input", listener);
|
const input_manager: *InputManager = @fieldParentPtr("new_input", listener);
|
||||||
|
|
||||||
input_manager.defaultSeat().addDevice(wlr_device);
|
input_manager.defaultSeat().addDevice(wlr_device);
|
||||||
}
|
}
|
||||||
@ -167,7 +167,7 @@ fn handleNewVirtualPointer(
|
|||||||
listener: *wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer),
|
listener: *wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer),
|
||||||
event: *wlr.VirtualPointerManagerV1.event.NewPointer,
|
event: *wlr.VirtualPointerManagerV1.event.NewPointer,
|
||||||
) void {
|
) void {
|
||||||
const input_manager = @fieldParentPtr(InputManager, "new_virtual_pointer", listener);
|
const input_manager: *InputManager = @fieldParentPtr("new_virtual_pointer", listener);
|
||||||
|
|
||||||
// TODO Support multiple seats and don't ignore
|
// TODO Support multiple seats and don't ignore
|
||||||
if (event.suggested_seat != null) {
|
if (event.suggested_seat != null) {
|
||||||
|
188
river/InputPopup.zig
Normal file
188
river/InputPopup.zig
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// This file is part of river, a dynamic tiling wayland compositor.
|
||||||
|
//
|
||||||
|
// Copyright 2024 The River Developers
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const InputPopup = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const wlr = @import("wlroots");
|
||||||
|
const wl = @import("wayland").server.wl;
|
||||||
|
|
||||||
|
const server = &@import("main.zig").server;
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
|
const InputRelay = @import("InputRelay.zig");
|
||||||
|
const SceneNodeData = @import("SceneNodeData.zig");
|
||||||
|
|
||||||
|
link: wl.list.Link,
|
||||||
|
input_relay: *InputRelay,
|
||||||
|
|
||||||
|
wlr_popup: *wlr.InputPopupSurfaceV2,
|
||||||
|
surface_tree: *wlr.SceneTree,
|
||||||
|
|
||||||
|
destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy),
|
||||||
|
map: wl.Listener(void) = wl.Listener(void).init(handleMap),
|
||||||
|
unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap),
|
||||||
|
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
|
||||||
|
|
||||||
|
pub fn create(wlr_popup: *wlr.InputPopupSurfaceV2, input_relay: *InputRelay) !void {
|
||||||
|
const input_popup = try util.gpa.create(InputPopup);
|
||||||
|
errdefer util.gpa.destroy(input_popup);
|
||||||
|
|
||||||
|
input_popup.* = .{
|
||||||
|
.link = undefined,
|
||||||
|
.input_relay = input_relay,
|
||||||
|
.wlr_popup = wlr_popup,
|
||||||
|
.surface_tree = try server.root.hidden.tree.createSceneSubsurfaceTree(wlr_popup.surface),
|
||||||
|
};
|
||||||
|
|
||||||
|
input_relay.input_popups.append(input_popup);
|
||||||
|
|
||||||
|
input_popup.wlr_popup.events.destroy.add(&input_popup.destroy);
|
||||||
|
input_popup.wlr_popup.surface.events.map.add(&input_popup.map);
|
||||||
|
input_popup.wlr_popup.surface.events.unmap.add(&input_popup.unmap);
|
||||||
|
input_popup.wlr_popup.surface.events.commit.add(&input_popup.commit);
|
||||||
|
|
||||||
|
input_popup.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleDestroy(listener: *wl.Listener(void)) void {
|
||||||
|
const input_popup: *InputPopup = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
|
input_popup.destroy.link.remove();
|
||||||
|
input_popup.map.link.remove();
|
||||||
|
input_popup.unmap.link.remove();
|
||||||
|
input_popup.commit.link.remove();
|
||||||
|
|
||||||
|
input_popup.link.remove();
|
||||||
|
|
||||||
|
util.gpa.destroy(input_popup);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleMap(listener: *wl.Listener(void)) void {
|
||||||
|
const input_popup: *InputPopup = @fieldParentPtr("map", listener);
|
||||||
|
|
||||||
|
input_popup.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleUnmap(listener: *wl.Listener(void)) void {
|
||||||
|
const input_popup: *InputPopup = @fieldParentPtr("unmap", listener);
|
||||||
|
|
||||||
|
input_popup.surface_tree.node.reparent(server.root.hidden.tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
||||||
|
const input_popup: *InputPopup = @fieldParentPtr("commit", listener);
|
||||||
|
|
||||||
|
input_popup.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(input_popup: *InputPopup) void {
|
||||||
|
const text_input = input_popup.input_relay.text_input orelse {
|
||||||
|
input_popup.surface_tree.node.reparent(server.root.hidden.tree);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!input_popup.wlr_popup.surface.mapped) return;
|
||||||
|
|
||||||
|
// This seems like it could be null if the focused surface is destroyed
|
||||||
|
const focused_surface = text_input.wlr_text_input.focused_surface orelse return;
|
||||||
|
|
||||||
|
// Focus should never be sent to subsurfaces
|
||||||
|
assert(focused_surface.getRootSurface() == focused_surface);
|
||||||
|
|
||||||
|
const focused = SceneNodeData.fromSurface(focused_surface) orelse return;
|
||||||
|
|
||||||
|
const output = switch (focused.data) {
|
||||||
|
.view => |view| view.current.output orelse return,
|
||||||
|
.layer_surface => |layer_surface| layer_surface.output,
|
||||||
|
.lock_surface => |lock_surface| lock_surface.getOutput(),
|
||||||
|
// Xwayland doesn't use the text-input protocol
|
||||||
|
.override_redirect => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
const popup_tree = switch (focused.data) {
|
||||||
|
.view => |view| view.popup_tree,
|
||||||
|
.layer_surface => |layer_surface| layer_surface.popup_tree,
|
||||||
|
.lock_surface => |lock_surface| lock_surface.getOutput().layers.popups,
|
||||||
|
// Xwayland doesn't use the text-input protocol
|
||||||
|
.override_redirect => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
input_popup.surface_tree.node.reparent(popup_tree);
|
||||||
|
|
||||||
|
if (!text_input.wlr_text_input.current.features.cursor_rectangle) {
|
||||||
|
// If the text-input client does not inform us where in the surface
|
||||||
|
// the active text input is there's not much we can do. Placing the
|
||||||
|
// popup at the top left corner of the window is nice and simple
|
||||||
|
// while not looking terrible.
|
||||||
|
input_popup.surface_tree.node.setPosition(0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var focused_x: c_int = undefined;
|
||||||
|
var focused_y: c_int = undefined;
|
||||||
|
_ = focused.node.coords(&focused_x, &focused_y);
|
||||||
|
|
||||||
|
var output_box: wlr.Box = undefined;
|
||||||
|
server.root.output_layout.getBox(output.wlr_output, &output_box);
|
||||||
|
|
||||||
|
// Relative to the surface with the active text input
|
||||||
|
var cursor_box = text_input.wlr_text_input.current.cursor_rectangle;
|
||||||
|
|
||||||
|
// Adjust to be relative to the output
|
||||||
|
cursor_box.x += focused_x - output_box.x;
|
||||||
|
cursor_box.y += focused_y - output_box.y;
|
||||||
|
|
||||||
|
// Choose popup x/y relative to the output:
|
||||||
|
|
||||||
|
// Align the left edge of the popup with the left edge of the cursor.
|
||||||
|
// If the popup wouldn't fit on the output instead align the right edge
|
||||||
|
// of the popup with the right edge of the cursor.
|
||||||
|
const popup_x = blk: {
|
||||||
|
const popup_width = input_popup.wlr_popup.surface.current.width;
|
||||||
|
if (output_box.width - cursor_box.x >= popup_width) {
|
||||||
|
break :blk cursor_box.x;
|
||||||
|
} else {
|
||||||
|
break :blk cursor_box.x + cursor_box.width - popup_width;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Align the top edge of the popup with the bottom edge of the cursor.
|
||||||
|
// If the popup wouldn't fit on the output instead align the bottom edge
|
||||||
|
// of the popup with the top edge of the cursor.
|
||||||
|
const popup_y = blk: {
|
||||||
|
const popup_height = input_popup.wlr_popup.surface.current.height;
|
||||||
|
if (output_box.height - (cursor_box.y + cursor_box.height) >= popup_height) {
|
||||||
|
break :blk cursor_box.y + cursor_box.height;
|
||||||
|
} else {
|
||||||
|
break :blk cursor_box.y - popup_height;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scene node position is relative to the parent so adjust popup x/y to
|
||||||
|
// be relative to the focused surface.
|
||||||
|
input_popup.surface_tree.node.setPosition(
|
||||||
|
popup_x - focused_x + output_box.x,
|
||||||
|
popup_y - focused_y + output_box.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
// The text input rectangle sent to the input method is relative to the popup.
|
||||||
|
cursor_box.x -= popup_x;
|
||||||
|
cursor_box.y -= popup_y;
|
||||||
|
input_popup.wlr_popup.sendTextInputRectangle(&cursor_box);
|
||||||
|
}
|
@ -26,6 +26,7 @@ const wl = @import("wayland").server.wl;
|
|||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
|
|
||||||
const TextInput = @import("TextInput.zig");
|
const TextInput = @import("TextInput.zig");
|
||||||
|
const InputPopup = @import("InputPopup.zig");
|
||||||
const Seat = @import("Seat.zig");
|
const Seat = @import("Seat.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.input_relay);
|
const log = std.log.scoped(.input_relay);
|
||||||
@ -40,6 +41,7 @@ text_inputs: wl.list.Head(TextInput, .link),
|
|||||||
/// already in use new input methods are ignored.
|
/// already in use new input methods are ignored.
|
||||||
/// If this is null, no text input enter events will be sent.
|
/// If this is null, no text input enter events will be sent.
|
||||||
input_method: ?*wlr.InputMethodV2 = null,
|
input_method: ?*wlr.InputMethodV2 = null,
|
||||||
|
input_popups: wl.list.Head(InputPopup, .link),
|
||||||
/// The currently enabled text input for the currently focused surface.
|
/// The currently enabled text input for the currently focused surface.
|
||||||
/// Always null if there is no input method.
|
/// Always null if there is no input method.
|
||||||
text_input: ?*TextInput = null,
|
text_input: ?*TextInput = null,
|
||||||
@ -50,18 +52,21 @@ grab_keyboard: wl.Listener(*wlr.InputMethodV2.KeyboardGrab) =
|
|||||||
wl.Listener(*wlr.InputMethodV2.KeyboardGrab).init(handleInputMethodGrabKeyboard),
|
wl.Listener(*wlr.InputMethodV2.KeyboardGrab).init(handleInputMethodGrabKeyboard),
|
||||||
input_method_destroy: wl.Listener(*wlr.InputMethodV2) =
|
input_method_destroy: wl.Listener(*wlr.InputMethodV2) =
|
||||||
wl.Listener(*wlr.InputMethodV2).init(handleInputMethodDestroy),
|
wl.Listener(*wlr.InputMethodV2).init(handleInputMethodDestroy),
|
||||||
|
input_method_new_popup: wl.Listener(*wlr.InputPopupSurfaceV2) =
|
||||||
|
wl.Listener(*wlr.InputPopupSurfaceV2).init(handleInputMethodNewPopup),
|
||||||
|
|
||||||
grab_keyboard_destroy: wl.Listener(*wlr.InputMethodV2.KeyboardGrab) =
|
grab_keyboard_destroy: wl.Listener(*wlr.InputMethodV2.KeyboardGrab) =
|
||||||
wl.Listener(*wlr.InputMethodV2.KeyboardGrab).init(handleInputMethodGrabKeyboardDestroy),
|
wl.Listener(*wlr.InputMethodV2.KeyboardGrab).init(handleInputMethodGrabKeyboardDestroy),
|
||||||
|
|
||||||
pub fn init(relay: *InputRelay) void {
|
pub fn init(relay: *InputRelay) void {
|
||||||
relay.* = .{ .text_inputs = undefined };
|
relay.* = .{ .text_inputs = undefined, .input_popups = undefined };
|
||||||
|
|
||||||
relay.text_inputs.init();
|
relay.text_inputs.init();
|
||||||
|
relay.input_popups.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newInputMethod(relay: *InputRelay, input_method: *wlr.InputMethodV2) void {
|
pub fn newInputMethod(relay: *InputRelay, input_method: *wlr.InputMethodV2) void {
|
||||||
const seat = @fieldParentPtr(Seat, "relay", relay);
|
const seat: *Seat = @fieldParentPtr("relay", relay);
|
||||||
|
|
||||||
log.debug("new input method on seat {s}", .{seat.wlr_seat.name});
|
log.debug("new input method on seat {s}", .{seat.wlr_seat.name});
|
||||||
|
|
||||||
@ -77,6 +82,7 @@ pub fn newInputMethod(relay: *InputRelay, input_method: *wlr.InputMethodV2) void
|
|||||||
input_method.events.commit.add(&relay.input_method_commit);
|
input_method.events.commit.add(&relay.input_method_commit);
|
||||||
input_method.events.grab_keyboard.add(&relay.grab_keyboard);
|
input_method.events.grab_keyboard.add(&relay.grab_keyboard);
|
||||||
input_method.events.destroy.add(&relay.input_method_destroy);
|
input_method.events.destroy.add(&relay.input_method_destroy);
|
||||||
|
input_method.events.new_popup_surface.add(&relay.input_method_new_popup);
|
||||||
|
|
||||||
if (seat.focused.surface()) |surface| {
|
if (seat.focused.surface()) |surface| {
|
||||||
relay.focus(surface);
|
relay.focus(surface);
|
||||||
@ -87,7 +93,7 @@ fn handleInputMethodCommit(
|
|||||||
listener: *wl.Listener(*wlr.InputMethodV2),
|
listener: *wl.Listener(*wlr.InputMethodV2),
|
||||||
input_method: *wlr.InputMethodV2,
|
input_method: *wlr.InputMethodV2,
|
||||||
) void {
|
) void {
|
||||||
const relay = @fieldParentPtr(InputRelay, "input_method_commit", listener);
|
const relay: *InputRelay = @fieldParentPtr("input_method_commit", listener);
|
||||||
assert(input_method == relay.input_method);
|
assert(input_method == relay.input_method);
|
||||||
|
|
||||||
if (!input_method.client_active) return;
|
if (!input_method.client_active) return;
|
||||||
@ -121,13 +127,13 @@ fn handleInputMethodDestroy(
|
|||||||
listener: *wl.Listener(*wlr.InputMethodV2),
|
listener: *wl.Listener(*wlr.InputMethodV2),
|
||||||
input_method: *wlr.InputMethodV2,
|
input_method: *wlr.InputMethodV2,
|
||||||
) void {
|
) void {
|
||||||
const relay = @fieldParentPtr(InputRelay, "input_method_destroy", listener);
|
const relay: *InputRelay = @fieldParentPtr("input_method_destroy", listener);
|
||||||
assert(input_method == relay.input_method);
|
assert(input_method == relay.input_method);
|
||||||
|
|
||||||
relay.input_method_commit.link.remove();
|
relay.input_method_commit.link.remove();
|
||||||
relay.grab_keyboard.link.remove();
|
relay.grab_keyboard.link.remove();
|
||||||
relay.input_method_destroy.link.remove();
|
relay.input_method_destroy.link.remove();
|
||||||
|
relay.input_method_new_popup.link.remove();
|
||||||
relay.input_method = null;
|
relay.input_method = null;
|
||||||
|
|
||||||
relay.focus(null);
|
relay.focus(null);
|
||||||
@ -139,8 +145,8 @@ fn handleInputMethodGrabKeyboard(
|
|||||||
listener: *wl.Listener(*wlr.InputMethodV2.KeyboardGrab),
|
listener: *wl.Listener(*wlr.InputMethodV2.KeyboardGrab),
|
||||||
keyboard_grab: *wlr.InputMethodV2.KeyboardGrab,
|
keyboard_grab: *wlr.InputMethodV2.KeyboardGrab,
|
||||||
) void {
|
) void {
|
||||||
const relay = @fieldParentPtr(InputRelay, "grab_keyboard", listener);
|
const relay: *InputRelay = @fieldParentPtr("grab_keyboard", listener);
|
||||||
const seat = @fieldParentPtr(Seat, "relay", relay);
|
const seat: *Seat = @fieldParentPtr("relay", relay);
|
||||||
|
|
||||||
const active_keyboard = seat.wlr_seat.getKeyboard();
|
const active_keyboard = seat.wlr_seat.getKeyboard();
|
||||||
keyboard_grab.setKeyboard(active_keyboard);
|
keyboard_grab.setKeyboard(active_keyboard);
|
||||||
@ -148,11 +154,23 @@ fn handleInputMethodGrabKeyboard(
|
|||||||
keyboard_grab.events.destroy.add(&relay.grab_keyboard_destroy);
|
keyboard_grab.events.destroy.add(&relay.grab_keyboard_destroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handleInputMethodNewPopup(
|
||||||
|
listener: *wl.Listener(*wlr.InputPopupSurfaceV2),
|
||||||
|
wlr_popup: *wlr.InputPopupSurfaceV2,
|
||||||
|
) void {
|
||||||
|
const relay: *InputRelay = @fieldParentPtr("input_method_new_popup", listener);
|
||||||
|
|
||||||
|
InputPopup.create(wlr_popup, relay) catch {
|
||||||
|
log.err("out of memory", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn handleInputMethodGrabKeyboardDestroy(
|
fn handleInputMethodGrabKeyboardDestroy(
|
||||||
listener: *wl.Listener(*wlr.InputMethodV2.KeyboardGrab),
|
listener: *wl.Listener(*wlr.InputMethodV2.KeyboardGrab),
|
||||||
keyboard_grab: *wlr.InputMethodV2.KeyboardGrab,
|
keyboard_grab: *wlr.InputMethodV2.KeyboardGrab,
|
||||||
) void {
|
) void {
|
||||||
const relay = @fieldParentPtr(InputRelay, "grab_keyboard_destroy", listener);
|
const relay: *InputRelay = @fieldParentPtr("grab_keyboard_destroy", listener);
|
||||||
relay.grab_keyboard_destroy.link.remove();
|
relay.grab_keyboard_destroy.link.remove();
|
||||||
|
|
||||||
if (keyboard_grab.keyboard) |keyboard| {
|
if (keyboard_grab.keyboard) |keyboard| {
|
||||||
@ -162,13 +180,16 @@ fn handleInputMethodGrabKeyboardDestroy(
|
|||||||
|
|
||||||
pub fn disableTextInput(relay: *InputRelay) void {
|
pub fn disableTextInput(relay: *InputRelay) void {
|
||||||
assert(relay.text_input != null);
|
assert(relay.text_input != null);
|
||||||
|
relay.text_input = null;
|
||||||
|
|
||||||
if (relay.input_method) |input_method| {
|
if (relay.input_method) |input_method| {
|
||||||
|
{
|
||||||
|
var it = relay.input_popups.iterator(.forward);
|
||||||
|
while (it.next()) |popup| popup.update();
|
||||||
|
}
|
||||||
input_method.sendDeactivate();
|
input_method.sendDeactivate();
|
||||||
input_method.sendDone();
|
input_method.sendDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
relay.text_input = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendInputMethodState(relay: *InputRelay) void {
|
pub fn sendInputMethodState(relay: *InputRelay) void {
|
||||||
@ -197,6 +218,11 @@ pub fn sendInputMethodState(relay: *InputRelay) void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var it = relay.input_popups.iterator(.forward);
|
||||||
|
while (it.next()) |popup| popup.update();
|
||||||
|
}
|
||||||
|
|
||||||
input_method.sendDone();
|
input_method.sendDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,16 +101,9 @@ pub fn init(keyboard: *Keyboard, seat: *Seat, wlr_device: *wlr.InputDevice) !voi
|
|||||||
// wlroots will log a more detailed error if this fails.
|
// wlroots will log a more detailed error if this fails.
|
||||||
if (!wlr_keyboard.setKeymap(server.config.keymap)) return error.OutOfMemory;
|
if (!wlr_keyboard.setKeymap(server.config.keymap)) return error.OutOfMemory;
|
||||||
|
|
||||||
// Add to keyboard-group, if applicable.
|
if (wlr.KeyboardGroup.fromKeyboard(wlr_keyboard) == null) {
|
||||||
var group_it = seat.keyboard_groups.first;
|
// wlroots will log an error on failure
|
||||||
outer: while (group_it) |group_node| : (group_it = group_node.next) {
|
_ = seat.keyboard_group.addKeyboard(wlr_keyboard);
|
||||||
for (group_node.data.globs.items) |glob| {
|
|
||||||
if (globber.match(glob, keyboard.device.identifier)) {
|
|
||||||
// wlroots will log an error if this fails explaining the reason.
|
|
||||||
_ = group_node.data.wlr_group.addKeyboard(wlr_keyboard);
|
|
||||||
break :outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wlr_keyboard.setRepeatInfo(server.config.repeat_rate, server.config.repeat_delay);
|
wlr_keyboard.setRepeatInfo(server.config.repeat_rate, server.config.repeat_delay);
|
||||||
@ -146,7 +139,7 @@ pub fn deinit(keyboard: *Keyboard) void {
|
|||||||
|
|
||||||
fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboard.event.Key) void {
|
fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboard.event.Key) void {
|
||||||
// This event is raised when a key is pressed or released.
|
// This event is raised when a key is pressed or released.
|
||||||
const keyboard = @fieldParentPtr(Keyboard, "key", listener);
|
const keyboard: *Keyboard = @fieldParentPtr("key", listener);
|
||||||
const wlr_keyboard = keyboard.device.wlr_device.toKeyboard();
|
const wlr_keyboard = keyboard.device.wlr_device.toKeyboard();
|
||||||
|
|
||||||
// If the keyboard is in a group, this event will be handled by the group's Keyboard instance.
|
// If the keyboard is in a group, this event will be handled by the group's Keyboard instance.
|
||||||
@ -247,7 +240,7 @@ fn isModifier(keysym: xkb.Keysym) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void {
|
fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void {
|
||||||
const keyboard = @fieldParentPtr(Keyboard, "modifiers", listener);
|
const keyboard: *Keyboard = @fieldParentPtr("modifiers", listener);
|
||||||
const wlr_keyboard = keyboard.device.wlr_device.toKeyboard();
|
const wlr_keyboard = keyboard.device.wlr_device.toKeyboard();
|
||||||
|
|
||||||
// If the keyboard is in a group, this event will be handled by the group's Keyboard instance.
|
// If the keyboard is in a group, this event will be handled by the group's Keyboard instance.
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
// This file is part of river, a dynamic tiling wayland compositor.
|
|
||||||
//
|
|
||||||
// Copyright 2022 The River Developers
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, version 3.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
const KeyboardGroup = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const mem = std.mem;
|
|
||||||
|
|
||||||
const globber = @import("globber");
|
|
||||||
const wlr = @import("wlroots");
|
|
||||||
const wl = @import("wayland").server.wl;
|
|
||||||
const xkb = @import("xkbcommon");
|
|
||||||
|
|
||||||
const log = std.log.scoped(.keyboard_group);
|
|
||||||
|
|
||||||
const server = &@import("main.zig").server;
|
|
||||||
const util = @import("util.zig");
|
|
||||||
|
|
||||||
const Seat = @import("Seat.zig");
|
|
||||||
const Keyboard = @import("Keyboard.zig");
|
|
||||||
|
|
||||||
seat: *Seat,
|
|
||||||
wlr_group: *wlr.KeyboardGroup,
|
|
||||||
name: []const u8,
|
|
||||||
globs: std.ArrayListUnmanaged([]const u8) = .{},
|
|
||||||
|
|
||||||
pub fn create(seat: *Seat, name: []const u8) !void {
|
|
||||||
log.debug("new keyboard group: '{s}'", .{name});
|
|
||||||
|
|
||||||
const node = try util.gpa.create(std.TailQueue(KeyboardGroup).Node);
|
|
||||||
errdefer util.gpa.destroy(node);
|
|
||||||
|
|
||||||
const wlr_group = try wlr.KeyboardGroup.create();
|
|
||||||
errdefer wlr_group.destroy();
|
|
||||||
|
|
||||||
const owned_name = try util.gpa.dupe(u8, name);
|
|
||||||
errdefer util.gpa.free(owned_name);
|
|
||||||
|
|
||||||
node.data = .{
|
|
||||||
.wlr_group = wlr_group,
|
|
||||||
.name = owned_name,
|
|
||||||
.seat = seat,
|
|
||||||
};
|
|
||||||
|
|
||||||
seat.addDevice(&wlr_group.keyboard.base);
|
|
||||||
seat.keyboard_groups.append(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy(group: *KeyboardGroup) void {
|
|
||||||
log.debug("destroying keyboard group: '{s}'", .{group.name});
|
|
||||||
|
|
||||||
util.gpa.free(group.name);
|
|
||||||
|
|
||||||
for (group.globs.items) |glob| {
|
|
||||||
util.gpa.free(glob);
|
|
||||||
}
|
|
||||||
group.globs.deinit(util.gpa);
|
|
||||||
|
|
||||||
group.wlr_group.destroy();
|
|
||||||
|
|
||||||
const node = @fieldParentPtr(std.TailQueue(KeyboardGroup).Node, "data", group);
|
|
||||||
group.seat.keyboard_groups.remove(node);
|
|
||||||
util.gpa.destroy(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addIdentifier(group: *KeyboardGroup, new_id: []const u8) !void {
|
|
||||||
for (group.globs.items) |glob| {
|
|
||||||
if (mem.eql(u8, glob, new_id)) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("keyboard group '{s}' adding identifier: '{s}'", .{ group.name, new_id });
|
|
||||||
|
|
||||||
const owned_id = try util.gpa.dupe(u8, new_id);
|
|
||||||
errdefer util.gpa.free(owned_id);
|
|
||||||
|
|
||||||
// Glob is validated in the command handler.
|
|
||||||
try group.globs.append(util.gpa, owned_id);
|
|
||||||
errdefer {
|
|
||||||
// Not used now, but if at any point this function is modified to that
|
|
||||||
// it may return an error after the glob pattern is added to the list,
|
|
||||||
// the list will have a pointer to freed memory in its last position.
|
|
||||||
_ = group.globs.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add any existing matching keyboards to the group.
|
|
||||||
var it = server.input_manager.devices.iterator(.forward);
|
|
||||||
while (it.next()) |device| {
|
|
||||||
if (device.seat != group.seat) continue;
|
|
||||||
if (device.wlr_device.type != .keyboard) continue;
|
|
||||||
|
|
||||||
if (globber.match(device.identifier, new_id)) {
|
|
||||||
log.debug("found existing matching keyboard; adding to group", .{});
|
|
||||||
|
|
||||||
if (!group.wlr_group.addKeyboard(device.wlr_device.toKeyboard())) {
|
|
||||||
// wlroots logs an error message to explain why this failed.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue, because we may have more than one device with the exact
|
|
||||||
// same identifier. That is in fact one reason for the keyboard group
|
|
||||||
// feature to exist in the first place.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn removeIdentifier(group: *KeyboardGroup, id: []const u8) !void {
|
|
||||||
for (group.globs.items, 0..) |glob, index| {
|
|
||||||
if (mem.eql(u8, glob, id)) {
|
|
||||||
_ = group.globs.orderedRemove(index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var it = server.input_manager.devices.iterator(.forward);
|
|
||||||
while (it.next()) |device| {
|
|
||||||
if (device.seat != group.seat) continue;
|
|
||||||
if (device.wlr_device.type != .keyboard) continue;
|
|
||||||
|
|
||||||
if (globber.match(device.identifier, id)) {
|
|
||||||
const wlr_keyboard = device.wlr_device.toKeyboard();
|
|
||||||
assert(wlr_keyboard.group == group.wlr_group);
|
|
||||||
group.wlr_group.removeKeyboard(wlr_keyboard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -55,7 +55,6 @@ pub fn create(wlr_layer_surface: *wlr.LayerSurfaceV1) error{OutOfMemory}!void {
|
|||||||
.scene_layer_surface = try layer_tree.createSceneLayerSurfaceV1(wlr_layer_surface),
|
.scene_layer_surface = try layer_tree.createSceneLayerSurfaceV1(wlr_layer_surface),
|
||||||
.popup_tree = try output.layers.popups.createSceneTree(),
|
.popup_tree = try output.layers.popups.createSceneTree(),
|
||||||
};
|
};
|
||||||
wlr_layer_surface.data = @intFromPtr(layer_surface);
|
|
||||||
|
|
||||||
try SceneNodeData.attach(&layer_surface.scene_layer_surface.tree.node, .{ .layer_surface = layer_surface });
|
try SceneNodeData.attach(&layer_surface.scene_layer_surface.tree.node, .{ .layer_surface = layer_surface });
|
||||||
try SceneNodeData.attach(&layer_surface.popup_tree.node, .{ .layer_surface = layer_surface });
|
try SceneNodeData.attach(&layer_surface.popup_tree.node, .{ .layer_surface = layer_surface });
|
||||||
@ -67,11 +66,6 @@ pub fn create(wlr_layer_surface: *wlr.LayerSurfaceV1) error{OutOfMemory}!void {
|
|||||||
wlr_layer_surface.surface.events.unmap.add(&layer_surface.unmap);
|
wlr_layer_surface.surface.events.unmap.add(&layer_surface.unmap);
|
||||||
wlr_layer_surface.surface.events.commit.add(&layer_surface.commit);
|
wlr_layer_surface.surface.events.commit.add(&layer_surface.commit);
|
||||||
wlr_layer_surface.events.new_popup.add(&layer_surface.new_popup);
|
wlr_layer_surface.events.new_popup.add(&layer_surface.new_popup);
|
||||||
|
|
||||||
// wlroots only informs us of the new surface after the first commit,
|
|
||||||
// so our listener does not get called for this first commit. However,
|
|
||||||
// we do want our listener called in order to send the initial configure.
|
|
||||||
handleCommit(&layer_surface.commit, wlr_layer_surface.surface);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroyPopups(layer_surface: *LayerSurface) void {
|
pub fn destroyPopups(layer_surface: *LayerSurface) void {
|
||||||
@ -80,7 +74,7 @@ pub fn destroyPopups(layer_surface: *LayerSurface) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), _: *wlr.LayerSurfaceV1) void {
|
fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), _: *wlr.LayerSurfaceV1) void {
|
||||||
const layer_surface = @fieldParentPtr(LayerSurface, "destroy", listener);
|
const layer_surface: *LayerSurface = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
log.debug("layer surface '{s}' destroyed", .{layer_surface.wlr_layer_surface.namespace});
|
log.debug("layer surface '{s}' destroyed", .{layer_surface.wlr_layer_surface.namespace});
|
||||||
|
|
||||||
@ -93,31 +87,42 @@ fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), _: *wlr.LayerSurfa
|
|||||||
|
|
||||||
layer_surface.popup_tree.node.destroy();
|
layer_surface.popup_tree.node.destroy();
|
||||||
|
|
||||||
|
// The wlr_surface may outlive the wlr_layer_surface so we must clean up the user data.
|
||||||
|
layer_surface.wlr_layer_surface.surface.data = 0;
|
||||||
|
|
||||||
util.gpa.destroy(layer_surface);
|
util.gpa.destroy(layer_surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleMap(listener: *wl.Listener(void)) void {
|
fn handleMap(listener: *wl.Listener(void)) void {
|
||||||
const layer_surface = @fieldParentPtr(LayerSurface, "map", listener);
|
const layer_surface: *LayerSurface = @fieldParentPtr("map", listener);
|
||||||
|
const wlr_surface = layer_surface.wlr_layer_surface;
|
||||||
|
|
||||||
log.debug("layer surface '{s}' mapped", .{layer_surface.wlr_layer_surface.namespace});
|
log.debug("layer surface '{s}' mapped", .{wlr_surface.namespace});
|
||||||
|
|
||||||
layer_surface.output.arrangeLayers();
|
layer_surface.output.arrangeLayers();
|
||||||
handleKeyboardInteractiveExclusive(layer_surface.output);
|
|
||||||
|
const consider = wlr_surface.current.keyboard_interactive == .on_demand and
|
||||||
|
(wlr_surface.current.layer == .top or wlr_surface.current.layer == .overlay);
|
||||||
|
handleKeyboardInteractiveExclusive(
|
||||||
|
layer_surface.output,
|
||||||
|
if (consider) layer_surface else null,
|
||||||
|
);
|
||||||
|
|
||||||
server.root.applyPending();
|
server.root.applyPending();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleUnmap(listener: *wl.Listener(void)) void {
|
fn handleUnmap(listener: *wl.Listener(void)) void {
|
||||||
const layer_surface = @fieldParentPtr(LayerSurface, "unmap", listener);
|
const layer_surface: *LayerSurface = @fieldParentPtr("unmap", listener);
|
||||||
|
|
||||||
log.debug("layer surface '{s}' unmapped", .{layer_surface.wlr_layer_surface.namespace});
|
log.debug("layer surface '{s}' unmapped", .{layer_surface.wlr_layer_surface.namespace});
|
||||||
|
|
||||||
layer_surface.output.arrangeLayers();
|
layer_surface.output.arrangeLayers();
|
||||||
handleKeyboardInteractiveExclusive(layer_surface.output);
|
handleKeyboardInteractiveExclusive(layer_surface.output, null);
|
||||||
server.root.applyPending();
|
server.root.applyPending();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
||||||
const layer_surface = @fieldParentPtr(LayerSurface, "commit", listener);
|
const layer_surface: *LayerSurface = @fieldParentPtr("commit", listener);
|
||||||
const wlr_layer_surface = layer_surface.wlr_layer_surface;
|
const wlr_layer_surface = layer_surface.wlr_layer_surface;
|
||||||
|
|
||||||
assert(wlr_layer_surface.output != null);
|
assert(wlr_layer_surface.output != null);
|
||||||
@ -132,18 +137,20 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
|||||||
@as(u32, @bitCast(wlr_layer_surface.current.committed)) != 0)
|
@as(u32, @bitCast(wlr_layer_surface.current.committed)) != 0)
|
||||||
{
|
{
|
||||||
layer_surface.output.arrangeLayers();
|
layer_surface.output.arrangeLayers();
|
||||||
handleKeyboardInteractiveExclusive(layer_surface.output);
|
handleKeyboardInteractiveExclusive(layer_surface.output, null);
|
||||||
server.root.applyPending();
|
server.root.applyPending();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Focus topmost keyboard-interactivity-exclusive layer surface above normal
|
||||||
|
/// content, or if none found, focus the surface given as `consider`.
|
||||||
/// Requires a call to Root.applyPending()
|
/// Requires a call to Root.applyPending()
|
||||||
fn handleKeyboardInteractiveExclusive(output: *Output) void {
|
fn handleKeyboardInteractiveExclusive(output: *Output, consider: ?*LayerSurface) void {
|
||||||
if (server.lock_manager.state != .unlocked) return;
|
if (server.lock_manager.state != .unlocked) return;
|
||||||
|
|
||||||
// Find the topmost layer surface in the top or overlay layers which
|
// Find the topmost layer surface (if any) in the top or overlay layers which
|
||||||
// requests keyboard interactivity if any.
|
// requests exclusive keyboard interactivity.
|
||||||
const topmost_surface = outer: for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top }) |layer| {
|
const to_focus = outer: for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top }) |layer| {
|
||||||
const tree = output.layerSurfaceTree(layer);
|
const tree = output.layerSurfaceTree(layer);
|
||||||
// Iterate in reverse to match rendering order.
|
// Iterate in reverse to match rendering order.
|
||||||
var it = tree.children.iterator(.reverse);
|
var it = tree.children.iterator(.reverse);
|
||||||
@ -159,17 +166,21 @@ fn handleKeyboardInteractiveExclusive(output: *Output) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else null;
|
} else consider;
|
||||||
|
|
||||||
|
if (to_focus) |s| {
|
||||||
|
assert(s.wlr_layer_surface.current.keyboard_interactive != .none);
|
||||||
|
}
|
||||||
|
|
||||||
var it = server.input_manager.seats.first;
|
var it = server.input_manager.seats.first;
|
||||||
while (it) |node| : (it = node.next) {
|
while (it) |node| : (it = node.next) {
|
||||||
const seat = &node.data;
|
const seat = &node.data;
|
||||||
|
|
||||||
if (seat.focused_output == output) {
|
if (seat.focused_output == output) {
|
||||||
if (topmost_surface) |to_focus| {
|
if (to_focus) |s| {
|
||||||
// If we found a surface on the output that requires focus, grab the focus of all
|
// If we found a surface on the output that requires focus, grab the focus of all
|
||||||
// seats that are focusing that output.
|
// seats that are focusing that output.
|
||||||
seat.setFocusRaw(.{ .layer = to_focus });
|
seat.setFocusRaw(.{ .layer = s });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,7 +197,7 @@ fn handleKeyboardInteractiveExclusive(output: *Output) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
|
fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
|
||||||
const layer_surface = @fieldParentPtr(LayerSurface, "new_popup", listener);
|
const layer_surface: *LayerSurface = @fieldParentPtr("new_popup", listener);
|
||||||
|
|
||||||
XdgPopup.create(
|
XdgPopup.create(
|
||||||
wlr_xdg_popup,
|
wlr_xdg_popup,
|
||||||
|
@ -186,7 +186,7 @@ pub fn destroy(layout: *Layout) void {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Remove layout from the list
|
// Remove layout from the list
|
||||||
const node = @fieldParentPtr(std.TailQueue(Layout).Node, "data", layout);
|
const node: *std.TailQueue(Layout).Node = @fieldParentPtr("data", layout);
|
||||||
layout.output.layouts.remove(node);
|
layout.output.layouts.remove(node);
|
||||||
|
|
||||||
// If we are the currently active layout of an output, clean up.
|
// If we are the currently active layout of an output, clean up.
|
||||||
|
@ -44,7 +44,7 @@ pub fn init(layout_manager: *LayoutManager) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
|
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
|
||||||
const layout_manager = @fieldParentPtr(LayoutManager, "server_destroy", listener);
|
const layout_manager: *LayoutManager = @fieldParentPtr("server_destroy", listener);
|
||||||
layout_manager.global.destroy();
|
layout_manager.global.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ pub fn deinit(manager: *LockManager) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleLock(listener: *wl.Listener(*wlr.SessionLockV1), lock: *wlr.SessionLockV1) void {
|
fn handleLock(listener: *wl.Listener(*wlr.SessionLockV1), lock: *wlr.SessionLockV1) void {
|
||||||
const manager = @fieldParentPtr(LockManager, "new_lock", listener);
|
const manager: *LockManager = @fieldParentPtr("new_lock", listener);
|
||||||
|
|
||||||
if (manager.lock != null) {
|
if (manager.lock != null) {
|
||||||
log.info("denying new session lock client, an active one already exists", .{});
|
log.info("denying new session lock client, an active one already exists", .{});
|
||||||
@ -191,7 +191,7 @@ pub fn maybeLock(manager: *LockManager) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleUnlock(listener: *wl.Listener(void)) void {
|
fn handleUnlock(listener: *wl.Listener(void)) void {
|
||||||
const manager = @fieldParentPtr(LockManager, "unlock", listener);
|
const manager: *LockManager = @fieldParentPtr("unlock", listener);
|
||||||
|
|
||||||
manager.state = .unlocked;
|
manager.state = .unlocked;
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ fn handleUnlock(listener: *wl.Listener(void)) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(void)) void {
|
fn handleDestroy(listener: *wl.Listener(void)) void {
|
||||||
const manager = @fieldParentPtr(LockManager, "destroy", listener);
|
const manager: *LockManager = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
log.debug("ext_session_lock_v1 destroyed", .{});
|
log.debug("ext_session_lock_v1 destroyed", .{});
|
||||||
|
|
||||||
@ -248,7 +248,7 @@ fn handleSurface(
|
|||||||
listener: *wl.Listener(*wlr.SessionLockSurfaceV1),
|
listener: *wl.Listener(*wlr.SessionLockSurfaceV1),
|
||||||
wlr_lock_surface: *wlr.SessionLockSurfaceV1,
|
wlr_lock_surface: *wlr.SessionLockSurfaceV1,
|
||||||
) void {
|
) void {
|
||||||
const manager = @fieldParentPtr(LockManager, "new_surface", listener);
|
const manager: *LockManager = @fieldParentPtr("new_surface", listener);
|
||||||
|
|
||||||
log.debug("new ext_session_lock_surface_v1 created", .{});
|
log.debug("new ext_session_lock_surface_v1 created", .{});
|
||||||
|
|
||||||
|
@ -85,6 +85,9 @@ pub fn destroy(lock_surface: *LockSurface) void {
|
|||||||
lock_surface.map.link.remove();
|
lock_surface.map.link.remove();
|
||||||
lock_surface.surface_destroy.link.remove();
|
lock_surface.surface_destroy.link.remove();
|
||||||
|
|
||||||
|
// The wlr_surface may outlive the wlr_lock_surface so we must clean up the user data.
|
||||||
|
lock_surface.wlr_lock_surface.surface.data = 0;
|
||||||
|
|
||||||
util.gpa.destroy(lock_surface);
|
util.gpa.destroy(lock_surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +103,7 @@ pub fn configure(lock_surface: *LockSurface) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleMap(listener: *wl.Listener(void)) void {
|
fn handleMap(listener: *wl.Listener(void)) void {
|
||||||
const lock_surface = @fieldParentPtr(LockSurface, "map", listener);
|
const lock_surface: *LockSurface = @fieldParentPtr("map", listener);
|
||||||
const output = lock_surface.getOutput();
|
const output = lock_surface.getOutput();
|
||||||
|
|
||||||
output.normal_content.node.setEnabled(false);
|
output.normal_content.node.setEnabled(false);
|
||||||
@ -132,7 +135,7 @@ fn updateFocus(lock_surface: *LockSurface) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(void)) void {
|
fn handleDestroy(listener: *wl.Listener(void)) void {
|
||||||
const lock_surface = @fieldParentPtr(LockSurface, "surface_destroy", listener);
|
const lock_surface: *LockSurface = @fieldParentPtr("surface_destroy", listener);
|
||||||
|
|
||||||
lock_surface.destroy();
|
lock_surface.destroy();
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ const std = @import("std");
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const math = std.math;
|
const math = std.math;
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
|
const posix = std.posix;
|
||||||
const fmt = std.fmt;
|
const fmt = std.fmt;
|
||||||
const wlr = @import("wlroots");
|
const wlr = @import("wlroots");
|
||||||
const wayland = @import("wayland");
|
const wayland = @import("wayland");
|
||||||
@ -99,7 +100,7 @@ layers: struct {
|
|||||||
fullscreen: *wlr.SceneTree,
|
fullscreen: *wlr.SceneTree,
|
||||||
/// Overlay layer shell layer
|
/// Overlay layer shell layer
|
||||||
overlay: *wlr.SceneTree,
|
overlay: *wlr.SceneTree,
|
||||||
/// xdg-popups of views and layer-shell surfaces
|
/// Popups from xdg-shell and input-method-v2 clients.
|
||||||
popups: *wlr.SceneTree,
|
popups: *wlr.SceneTree,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -366,6 +367,8 @@ fn sendLayerConfigures(
|
|||||||
if (@as(?*SceneNodeData, @ptrFromInt(node.data))) |node_data| {
|
if (@as(?*SceneNodeData, @ptrFromInt(node.data))) |node_data| {
|
||||||
const layer_surface = node_data.data.layer_surface;
|
const layer_surface = node_data.data.layer_surface;
|
||||||
|
|
||||||
|
if (!layer_surface.wlr_layer_surface.initialized) continue;
|
||||||
|
|
||||||
const exclusive = layer_surface.wlr_layer_surface.current.exclusive_zone > 0;
|
const exclusive = layer_surface.wlr_layer_surface.current.exclusive_zone > 0;
|
||||||
if (exclusive != (mode == .exclusive)) {
|
if (exclusive != (mode == .exclusive)) {
|
||||||
continue;
|
continue;
|
||||||
@ -390,18 +393,22 @@ fn sendLayerConfigures(
|
|||||||
usable_box.* = new_usable_box;
|
usable_box.* = new_usable_box;
|
||||||
}
|
}
|
||||||
|
|
||||||
layer_surface.popup_tree.node.setPosition(
|
const x = layer_surface.scene_layer_surface.tree.node.x;
|
||||||
layer_surface.scene_layer_surface.tree.node.x,
|
const y = layer_surface.scene_layer_surface.tree.node.y;
|
||||||
layer_surface.scene_layer_surface.tree.node.y,
|
layer_surface.popup_tree.node.setPosition(x, y);
|
||||||
);
|
layer_surface.scene_layer_surface.tree.node.subsurfaceTreeSetClip(&.{
|
||||||
layer_surface.scene_layer_surface.tree.node.subsurfaceTreeSetClip(&full_box);
|
.x = -x,
|
||||||
|
.y = -y,
|
||||||
|
.width = full_box.width,
|
||||||
|
.height = full_box.height,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
|
fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
|
||||||
const output = @fieldParentPtr(Output, "destroy", listener);
|
const output: *Output = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
log.debug("output '{s}' destroyed", .{output.wlr_output.name});
|
log.debug("output '{s}' destroyed", .{output.wlr_output.name});
|
||||||
|
|
||||||
@ -436,7 +443,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleRequestState(listener: *wl.Listener(*wlr.Output.event.RequestState), event: *wlr.Output.event.RequestState) void {
|
fn handleRequestState(listener: *wl.Listener(*wlr.Output.event.RequestState), event: *wlr.Output.event.RequestState) void {
|
||||||
const output = @fieldParentPtr(Output, "request_state", listener);
|
const output: *Output = @fieldParentPtr("request_state", listener);
|
||||||
|
|
||||||
output.applyState(event.state) catch {
|
output.applyState(event.state) catch {
|
||||||
log.err("failed to commit requested state", .{});
|
log.err("failed to commit requested state", .{});
|
||||||
@ -473,6 +480,7 @@ pub fn applyState(output: *Output, state: *wlr.Output.State) error{CommitFailed}
|
|||||||
|
|
||||||
fn handleEnableDisable(output: *Output) void {
|
fn handleEnableDisable(output: *Output) void {
|
||||||
output.updateLockRenderStateOnEnableDisable();
|
output.updateLockRenderStateOnEnableDisable();
|
||||||
|
output.gamma_dirty = true;
|
||||||
|
|
||||||
if (output.wlr_output.enabled) {
|
if (output.wlr_output.enabled) {
|
||||||
// Add the output to root.active_outputs and the output layout if it has not
|
// Add the output to root.active_outputs and the output layout if it has not
|
||||||
@ -514,12 +522,12 @@ pub fn updateBackgroundRect(output: *Output) void {
|
|||||||
output.layers.background_color_rect.setSize(width, height);
|
output.layers.background_color_rect.setSize(width, height);
|
||||||
|
|
||||||
var it = output.layers.fullscreen.children.iterator(.forward);
|
var it = output.layers.fullscreen.children.iterator(.forward);
|
||||||
const fullscreen_background = @fieldParentPtr(wlr.SceneRect, "node", it.next().?);
|
const fullscreen_background: *wlr.SceneRect = @fieldParentPtr("node", it.next().?);
|
||||||
fullscreen_background.setSize(width, height);
|
fullscreen_background.setSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
|
fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
|
||||||
const output = @fieldParentPtr(Output, "frame", listener);
|
const output: *Output = @fieldParentPtr("frame", listener);
|
||||||
const scene_output = server.root.scene.getSceneOutput(output.wlr_output).?;
|
const scene_output = server.root.scene.getSceneOutput(output.wlr_output).?;
|
||||||
|
|
||||||
// TODO this should probably be retried on failure
|
// TODO this should probably be retried on failure
|
||||||
@ -528,34 +536,56 @@ fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
|
|||||||
error.CommitFailed => log.err("output commit failed for {s}", .{output.wlr_output.name}),
|
error.CommitFailed => log.err("output commit failed for {s}", .{output.wlr_output.name}),
|
||||||
};
|
};
|
||||||
|
|
||||||
var now: std.os.timespec = undefined;
|
var now: posix.timespec = undefined;
|
||||||
std.os.clock_gettime(std.os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
posix.clock_gettime(posix.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
||||||
scene_output.sendFrameDone(&now);
|
scene_output.sendFrameDone(&now);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderAndCommit(output: *Output, scene_output: *wlr.SceneOutput) !void {
|
fn renderAndCommit(output: *Output, scene_output: *wlr.SceneOutput) !void {
|
||||||
if (output.gamma_dirty) {
|
// TODO(wlroots): replace this with wlr_scene_output_needs_frame()
|
||||||
var state = wlr.Output.State.init();
|
if (!output.wlr_output.needs_frame and !output.gamma_dirty and
|
||||||
defer state.finish();
|
!scene_output.pending_commit_damage.notEmpty())
|
||||||
|
{
|
||||||
if (server.root.gamma_control_manager.getControl(output.wlr_output)) |control| {
|
return;
|
||||||
log.info("applying gamma settings from client", .{});
|
|
||||||
if (!control.apply(&state)) return error.OutOfMemory;
|
|
||||||
} else {
|
|
||||||
log.info("clearing gamma settings from client", .{});
|
|
||||||
state.clearGammaLut();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!scene_output.buildState(&state, null)) return error.CommitFailed;
|
|
||||||
|
|
||||||
if (!output.wlr_output.commitState(&state)) return error.CommitFailed;
|
|
||||||
|
|
||||||
scene_output.damage_ring.rotate();
|
|
||||||
output.gamma_dirty = false;
|
|
||||||
} else {
|
|
||||||
if (!scene_output.commit(null)) return error.CommitFailed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var state = wlr.Output.State.init();
|
||||||
|
defer state.finish();
|
||||||
|
|
||||||
|
if (!scene_output.buildState(&state, null)) return error.CommitFailed;
|
||||||
|
|
||||||
|
if (output.gamma_dirty) {
|
||||||
|
const control = server.root.gamma_control_manager.getControl(output.wlr_output);
|
||||||
|
if (!wlr.GammaControlV1.apply(control, &state)) return error.OutOfMemory;
|
||||||
|
|
||||||
|
// TODO(wlroots): remove this isHeadless() workaround after upstream fix is available
|
||||||
|
// in a release: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4868
|
||||||
|
if (!output.wlr_output.testState(&state) or output.wlr_output.isHeadless()) {
|
||||||
|
wlr.GammaControlV1.sendFailedAndDestroy(control);
|
||||||
|
state.clearGammaLut();
|
||||||
|
// If the backend does not support gamma LUTs it will reject any
|
||||||
|
// state with the gamma LUT committed bit set even if the state
|
||||||
|
// has a null LUT. The wayland backend for example has this behavior.
|
||||||
|
state.committed.gamma_lut = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.current.fullscreen) |fullscreen| {
|
||||||
|
if (fullscreen.allowTearing()) {
|
||||||
|
state.tearing_page_flip = true;
|
||||||
|
if (!output.wlr_output.testState(&state)) {
|
||||||
|
log.debug("tearing page flip test failed for {s}, retrying without tearing", .{
|
||||||
|
output.wlr_output.name,
|
||||||
|
});
|
||||||
|
state.tearing_page_flip = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!output.wlr_output.commitState(&state)) return error.CommitFailed;
|
||||||
|
|
||||||
|
output.gamma_dirty = false;
|
||||||
|
|
||||||
if (server.lock_manager.state == .locked or
|
if (server.lock_manager.state == .locked or
|
||||||
(server.lock_manager.state == .waiting_for_lock_surfaces and output.locked_content.node.enabled) or
|
(server.lock_manager.state == .waiting_for_lock_surfaces and output.locked_content.node.enabled) or
|
||||||
server.lock_manager.state == .waiting_for_blank)
|
server.lock_manager.state == .waiting_for_blank)
|
||||||
@ -591,7 +621,7 @@ fn handlePresent(
|
|||||||
listener: *wl.Listener(*wlr.Output.event.Present),
|
listener: *wl.Listener(*wlr.Output.event.Present),
|
||||||
event: *wlr.Output.event.Present,
|
event: *wlr.Output.event.Present,
|
||||||
) void {
|
) void {
|
||||||
const output = @fieldParentPtr(Output, "present", listener);
|
const output: *Output = @fieldParentPtr("present", listener);
|
||||||
|
|
||||||
if (!event.presented) {
|
if (!event.presented) {
|
||||||
return;
|
return;
|
||||||
|
@ -42,7 +42,7 @@ state: union(enum) {
|
|||||||
} = .inactive,
|
} = .inactive,
|
||||||
|
|
||||||
destroy: wl.Listener(*wlr.PointerConstraintV1) = wl.Listener(*wlr.PointerConstraintV1).init(handleDestroy),
|
destroy: wl.Listener(*wlr.PointerConstraintV1) = wl.Listener(*wlr.PointerConstraintV1).init(handleDestroy),
|
||||||
set_region: wl.Listener(void) = wl.Listener(void).init(handleSetRegion),
|
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
|
||||||
|
|
||||||
node_destroy: wl.Listener(void) = wl.Listener(void).init(handleNodeDestroy),
|
node_destroy: wl.Listener(void) = wl.Listener(void).init(handleNodeDestroy),
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ pub fn create(wlr_constraint: *wlr.PointerConstraintV1) error{OutOfMemory}!void
|
|||||||
wlr_constraint.data = @intFromPtr(constraint);
|
wlr_constraint.data = @intFromPtr(constraint);
|
||||||
|
|
||||||
wlr_constraint.events.destroy.add(&constraint.destroy);
|
wlr_constraint.events.destroy.add(&constraint.destroy);
|
||||||
wlr_constraint.events.set_region.add(&constraint.set_region);
|
wlr_constraint.surface.events.commit.add(&constraint.commit);
|
||||||
|
|
||||||
if (seat.wlr_seat.keyboard_state.focused_surface) |surface| {
|
if (seat.wlr_seat.keyboard_state.focused_surface) |surface| {
|
||||||
if (surface == wlr_constraint.surface) {
|
if (surface == wlr_constraint.surface) {
|
||||||
@ -73,7 +73,6 @@ pub fn maybeActivate(constraint: *PointerConstraint) void {
|
|||||||
const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
|
const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
|
||||||
|
|
||||||
assert(seat.cursor.constraint == constraint);
|
assert(seat.cursor.constraint == constraint);
|
||||||
assert(seat.wlr_seat.keyboard_state.focused_surface == constraint.wlr_constraint.surface);
|
|
||||||
|
|
||||||
if (constraint.state == .active) return;
|
if (constraint.state == .active) return;
|
||||||
|
|
||||||
@ -105,8 +104,6 @@ pub fn maybeActivate(constraint: *PointerConstraint) void {
|
|||||||
pub fn updateState(constraint: *PointerConstraint) void {
|
pub fn updateState(constraint: *PointerConstraint) void {
|
||||||
const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
|
const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
|
||||||
|
|
||||||
assert(seat.wlr_seat.keyboard_state.focused_surface == constraint.wlr_constraint.surface);
|
|
||||||
|
|
||||||
constraint.maybeActivate();
|
constraint.maybeActivate();
|
||||||
|
|
||||||
if (constraint.state != .active) return;
|
if (constraint.state != .active) return;
|
||||||
@ -172,7 +169,7 @@ pub fn deactivate(constraint: *PointerConstraint) void {
|
|||||||
fn warpToHintIfSet(constraint: *PointerConstraint) void {
|
fn warpToHintIfSet(constraint: *PointerConstraint) void {
|
||||||
const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
|
const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
|
||||||
|
|
||||||
if (constraint.wlr_constraint.current.committed.cursor_hint) {
|
if (constraint.wlr_constraint.current.cursor_hint.enabled) {
|
||||||
var lx: i32 = undefined;
|
var lx: i32 = undefined;
|
||||||
var ly: i32 = undefined;
|
var ly: i32 = undefined;
|
||||||
_ = constraint.state.active.node.coords(&lx, &ly);
|
_ = constraint.state.active.node.coords(&lx, &ly);
|
||||||
@ -185,14 +182,14 @@ fn warpToHintIfSet(constraint: *PointerConstraint) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleNodeDestroy(listener: *wl.Listener(void)) void {
|
fn handleNodeDestroy(listener: *wl.Listener(void)) void {
|
||||||
const constraint = @fieldParentPtr(PointerConstraint, "node_destroy", listener);
|
const constraint: *PointerConstraint = @fieldParentPtr("node_destroy", listener);
|
||||||
|
|
||||||
log.info("deactivating pointer constraint, scene node destroyed", .{});
|
log.info("deactivating pointer constraint, scene node destroyed", .{});
|
||||||
constraint.deactivate();
|
constraint.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(*wlr.PointerConstraintV1), _: *wlr.PointerConstraintV1) void {
|
fn handleDestroy(listener: *wl.Listener(*wlr.PointerConstraintV1), _: *wlr.PointerConstraintV1) void {
|
||||||
const constraint = @fieldParentPtr(PointerConstraint, "destroy", listener);
|
const constraint: *PointerConstraint = @fieldParentPtr("destroy", listener);
|
||||||
const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
|
const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
|
||||||
|
|
||||||
if (constraint.state == .active) {
|
if (constraint.state == .active) {
|
||||||
@ -204,7 +201,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.PointerConstraintV1), _: *wlr.Point
|
|||||||
}
|
}
|
||||||
|
|
||||||
constraint.destroy.link.remove();
|
constraint.destroy.link.remove();
|
||||||
constraint.set_region.link.remove();
|
constraint.commit.link.remove();
|
||||||
|
|
||||||
if (seat.cursor.constraint == constraint) {
|
if (seat.cursor.constraint == constraint) {
|
||||||
seat.cursor.constraint = null;
|
seat.cursor.constraint = null;
|
||||||
@ -213,8 +210,11 @@ fn handleDestroy(listener: *wl.Listener(*wlr.PointerConstraintV1), _: *wlr.Point
|
|||||||
util.gpa.destroy(constraint);
|
util.gpa.destroy(constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleSetRegion(listener: *wl.Listener(void)) void {
|
// It is necessary to listen for the commit event rather than the set_region
|
||||||
const constraint = @fieldParentPtr(PointerConstraint, "set_region", listener);
|
// event as the latter is not triggered by wlroots when the input region of
|
||||||
|
// the surface changes.
|
||||||
|
fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
||||||
|
const constraint: *PointerConstraint = @fieldParentPtr("commit", listener);
|
||||||
const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
|
const seat: *Seat = @ptrFromInt(constraint.wlr_constraint.seat.data);
|
||||||
|
|
||||||
switch (constraint.state) {
|
switch (constraint.state) {
|
||||||
@ -222,7 +222,7 @@ fn handleSetRegion(listener: *wl.Listener(void)) void {
|
|||||||
const sx: i32 = @intFromFloat(state.sx);
|
const sx: i32 = @intFromFloat(state.sx);
|
||||||
const sy: i32 = @intFromFloat(state.sy);
|
const sy: i32 = @intFromFloat(state.sy);
|
||||||
if (!constraint.wlr_constraint.region.containsPoint(sx, sy, null)) {
|
if (!constraint.wlr_constraint.region.containsPoint(sx, sy, null)) {
|
||||||
log.info("deactivating pointer constraint, region change left pointer outside constraint", .{});
|
log.info("deactivating pointer constraint, (input) region change left pointer outside constraint", .{});
|
||||||
constraint.deactivate();
|
constraint.deactivate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -117,7 +117,7 @@ transaction_timeout: *wl.EventSource,
|
|||||||
pending_state_dirty: bool = false,
|
pending_state_dirty: bool = false,
|
||||||
|
|
||||||
pub fn init(root: *Root) !void {
|
pub fn init(root: *Root) !void {
|
||||||
const output_layout = try wlr.OutputLayout.create();
|
const output_layout = try wlr.OutputLayout.create(server.wl_server);
|
||||||
errdefer output_layout.destroy();
|
errdefer output_layout.destroy();
|
||||||
|
|
||||||
const scene = try wlr.Scene.create();
|
const scene = try wlr.Scene.create();
|
||||||
@ -131,9 +131,6 @@ pub fn init(root: *Root) !void {
|
|||||||
const outputs = try interactive_content.createSceneTree();
|
const outputs = try interactive_content.createSceneTree();
|
||||||
const override_redirect = if (build_options.xwayland) try interactive_content.createSceneTree();
|
const override_redirect = if (build_options.xwayland) try interactive_content.createSceneTree();
|
||||||
|
|
||||||
const presentation = try wlr.Presentation.create(server.wl_server, server.backend);
|
|
||||||
scene.setPresentation(presentation);
|
|
||||||
|
|
||||||
const event_loop = server.wl_server.getEventLoop();
|
const event_loop = server.wl_server.getEventLoop();
|
||||||
const transaction_timeout = try event_loop.addTimer(*Root, handleTransactionTimeout, root);
|
const transaction_timeout = try event_loop.addTimer(*Root, handleTransactionTimeout, root);
|
||||||
errdefer transaction_timeout.remove();
|
errdefer transaction_timeout.remove();
|
||||||
@ -166,7 +163,7 @@ pub fn init(root: *Root) !void {
|
|||||||
.all_outputs = undefined,
|
.all_outputs = undefined,
|
||||||
.active_outputs = undefined,
|
.active_outputs = undefined,
|
||||||
|
|
||||||
.presentation = presentation,
|
.presentation = try wlr.Presentation.create(server.wl_server, server.backend),
|
||||||
.xdg_output_manager = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout),
|
.xdg_output_manager = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout),
|
||||||
.output_manager = try wlr.OutputManagerV1.create(server.wl_server),
|
.output_manager = try wlr.OutputManagerV1.create(server.wl_server),
|
||||||
.power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
|
.power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
|
||||||
@ -416,6 +413,9 @@ pub fn activateOutput(root: *Root, output: *Output) void {
|
|||||||
}
|
}
|
||||||
assert(root.fallback_pending.focus_stack.empty());
|
assert(root.fallback_pending.focus_stack.empty());
|
||||||
assert(root.fallback_pending.wm_stack.empty());
|
assert(root.fallback_pending.wm_stack.empty());
|
||||||
|
|
||||||
|
// Enforce map-to-output configuration for the newly active output.
|
||||||
|
server.input_manager.reconfigureDevices();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trigger asynchronous application of pending state for all outputs and views.
|
/// Trigger asynchronous application of pending state for all outputs and views.
|
||||||
@ -673,24 +673,12 @@ fn commitTransaction(root: *Root) void {
|
|||||||
while (focus_stack_it.next()) |view| {
|
while (focus_stack_it.next()) |view| {
|
||||||
assert(view.inflight.output == output);
|
assert(view.inflight.output == output);
|
||||||
|
|
||||||
if (view.current.output != view.inflight.output or
|
if (view.inflight.float) {
|
||||||
(output.current.fullscreen == view and output.inflight.fullscreen != view))
|
view.tree.node.reparent(output.layers.float);
|
||||||
{
|
} else {
|
||||||
if (view.inflight.float) {
|
view.tree.node.reparent(output.layers.layout);
|
||||||
view.tree.node.reparent(output.layers.float);
|
|
||||||
} else {
|
|
||||||
view.tree.node.reparent(output.layers.layout);
|
|
||||||
}
|
|
||||||
view.popup_tree.node.reparent(output.layers.popups);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (view.current.float != view.inflight.float) {
|
|
||||||
if (view.inflight.float) {
|
|
||||||
view.tree.node.reparent(output.layers.float);
|
|
||||||
} else {
|
|
||||||
view.tree.node.reparent(output.layers.layout);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
view.popup_tree.node.reparent(output.layers.popups);
|
||||||
|
|
||||||
view.commitTransaction();
|
view.commitTransaction();
|
||||||
|
|
||||||
@ -703,15 +691,13 @@ fn commitTransaction(root: *Root) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.inflight.fullscreen != output.current.fullscreen) {
|
if (output.inflight.fullscreen) |view| {
|
||||||
if (output.inflight.fullscreen) |view| {
|
assert(view.inflight.output == output);
|
||||||
assert(view.inflight.output == output);
|
assert(view.current.output == output);
|
||||||
assert(view.current.output == output);
|
view.tree.node.reparent(output.layers.fullscreen);
|
||||||
view.tree.node.reparent(output.layers.fullscreen);
|
|
||||||
}
|
|
||||||
output.current.fullscreen = output.inflight.fullscreen;
|
|
||||||
output.layers.fullscreen.node.setEnabled(output.current.fullscreen != null);
|
|
||||||
}
|
}
|
||||||
|
output.current.fullscreen = output.inflight.fullscreen;
|
||||||
|
output.layers.fullscreen.node.setEnabled(output.current.fullscreen != null);
|
||||||
|
|
||||||
output.status.handleTransactionCommit(output);
|
output.status.handleTransactionCommit(output);
|
||||||
}
|
}
|
||||||
@ -743,7 +729,7 @@ fn commitTransaction(root: *Root) void {
|
|||||||
// We need this listener to deal with outputs that have their position auto-configured
|
// We need this listener to deal with outputs that have their position auto-configured
|
||||||
// by the wlr_output_layout.
|
// by the wlr_output_layout.
|
||||||
fn handleLayoutChange(listener: *wl.Listener(*wlr.OutputLayout), _: *wlr.OutputLayout) void {
|
fn handleLayoutChange(listener: *wl.Listener(*wlr.OutputLayout), _: *wlr.OutputLayout) void {
|
||||||
const root = @fieldParentPtr(Root, "layout_change", listener);
|
const root: *Root = @fieldParentPtr("layout_change", listener);
|
||||||
|
|
||||||
root.handleOutputConfigChange() catch std.log.err("out of memory", .{});
|
root.handleOutputConfigChange() catch std.log.err("out of memory", .{});
|
||||||
}
|
}
|
||||||
@ -778,7 +764,7 @@ fn handleManagerApply(
|
|||||||
listener: *wl.Listener(*wlr.OutputConfigurationV1),
|
listener: *wl.Listener(*wlr.OutputConfigurationV1),
|
||||||
config: *wlr.OutputConfigurationV1,
|
config: *wlr.OutputConfigurationV1,
|
||||||
) void {
|
) void {
|
||||||
const root = @fieldParentPtr(Root, "manager_apply", listener);
|
const root: *Root = @fieldParentPtr("manager_apply", listener);
|
||||||
defer config.destroy();
|
defer config.destroy();
|
||||||
|
|
||||||
std.log.scoped(.output_manager).info("applying output configuration", .{});
|
std.log.scoped(.output_manager).info("applying output configuration", .{});
|
||||||
@ -792,7 +778,7 @@ fn handleManagerTest(
|
|||||||
listener: *wl.Listener(*wlr.OutputConfigurationV1),
|
listener: *wl.Listener(*wlr.OutputConfigurationV1),
|
||||||
config: *wlr.OutputConfigurationV1,
|
config: *wlr.OutputConfigurationV1,
|
||||||
) void {
|
) void {
|
||||||
const root = @fieldParentPtr(Root, "manager_test", listener);
|
const root: *Root = @fieldParentPtr("manager_test", listener);
|
||||||
defer config.destroy();
|
defer config.destroy();
|
||||||
|
|
||||||
root.processOutputConfig(config, .test_only);
|
root.processOutputConfig(config, .test_only);
|
||||||
@ -894,6 +880,7 @@ fn handlePowerManagerSetMode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
output.updateLockRenderStateOnEnableDisable();
|
output.updateLockRenderStateOnEnableDisable();
|
||||||
|
output.gamma_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleSetGamma(
|
fn handleSetGamma(
|
||||||
|
@ -24,6 +24,7 @@ const util = @import("util.zig");
|
|||||||
|
|
||||||
const LayerSurface = @import("LayerSurface.zig");
|
const LayerSurface = @import("LayerSurface.zig");
|
||||||
const LockSurface = @import("LockSurface.zig");
|
const LockSurface = @import("LockSurface.zig");
|
||||||
|
const InputPopup = @import("InputPopup.zig");
|
||||||
const View = @import("View.zig");
|
const View = @import("View.zig");
|
||||||
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
||||||
|
|
||||||
@ -65,16 +66,14 @@ pub fn fromNode(node: *wlr.SceneNode) ?*SceneNodeData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn fromSurface(surface: *wlr.Surface) ?*SceneNodeData {
|
pub fn fromSurface(surface: *wlr.Surface) ?*SceneNodeData {
|
||||||
if (surface.getRootSurface()) |root_surface| {
|
if (@as(?*wlr.SceneNode, @ptrFromInt(surface.getRootSurface().data))) |node| {
|
||||||
if (@as(?*wlr.SceneNode, @ptrFromInt(root_surface.data))) |node| {
|
return fromNode(node);
|
||||||
return fromNode(node);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(void)) void {
|
fn handleDestroy(listener: *wl.Listener(void)) void {
|
||||||
const scene_node_data = @fieldParentPtr(SceneNodeData, "destroy", listener);
|
const scene_node_data: *SceneNodeData = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
scene_node_data.destroy.link.remove();
|
scene_node_data.destroy.link.remove();
|
||||||
scene_node_data.node.data = 0;
|
scene_node_data.node.data = 0;
|
||||||
|
@ -33,7 +33,6 @@ const InputDevice = @import("InputDevice.zig");
|
|||||||
const InputManager = @import("InputManager.zig");
|
const InputManager = @import("InputManager.zig");
|
||||||
const InputRelay = @import("InputRelay.zig");
|
const InputRelay = @import("InputRelay.zig");
|
||||||
const Keyboard = @import("Keyboard.zig");
|
const Keyboard = @import("Keyboard.zig");
|
||||||
const KeyboardGroup = @import("KeyboardGroup.zig");
|
|
||||||
const LayerSurface = @import("LayerSurface.zig");
|
const LayerSurface = @import("LayerSurface.zig");
|
||||||
const LockSurface = @import("LockSurface.zig");
|
const LockSurface = @import("LockSurface.zig");
|
||||||
const Mapping = @import("Mapping.zig");
|
const Mapping = @import("Mapping.zig");
|
||||||
@ -84,7 +83,7 @@ mapping_repeat_timer: *wl.EventSource,
|
|||||||
/// Currently repeating mapping, if any
|
/// Currently repeating mapping, if any
|
||||||
repeating_mapping: ?*const Mapping = null,
|
repeating_mapping: ?*const Mapping = null,
|
||||||
|
|
||||||
keyboard_groups: std.TailQueue(KeyboardGroup) = .{},
|
keyboard_group: *wlr.KeyboardGroup,
|
||||||
|
|
||||||
/// Currently focused output. Null only when there are no outputs at all.
|
/// Currently focused output. Null only when there are no outputs at all.
|
||||||
focused_output: ?*Output = null,
|
focused_output: ?*Output = null,
|
||||||
@ -121,12 +120,15 @@ pub fn init(seat: *Seat, name: [*:0]const u8) !void {
|
|||||||
.cursor = undefined,
|
.cursor = undefined,
|
||||||
.relay = undefined,
|
.relay = undefined,
|
||||||
.mapping_repeat_timer = mapping_repeat_timer,
|
.mapping_repeat_timer = mapping_repeat_timer,
|
||||||
|
.keyboard_group = try wlr.KeyboardGroup.create(),
|
||||||
};
|
};
|
||||||
seat.wlr_seat.data = @intFromPtr(seat);
|
seat.wlr_seat.data = @intFromPtr(seat);
|
||||||
|
|
||||||
try seat.cursor.init(seat);
|
try seat.cursor.init(seat);
|
||||||
seat.relay.init();
|
seat.relay.init();
|
||||||
|
|
||||||
|
try seat.tryAddDevice(&seat.keyboard_group.keyboard.base);
|
||||||
|
|
||||||
seat.wlr_seat.events.request_set_selection.add(&seat.request_set_selection);
|
seat.wlr_seat.events.request_set_selection.add(&seat.request_set_selection);
|
||||||
seat.wlr_seat.events.request_start_drag.add(&seat.request_start_drag);
|
seat.wlr_seat.events.request_start_drag.add(&seat.request_start_drag);
|
||||||
seat.wlr_seat.events.start_drag.add(&seat.start_drag);
|
seat.wlr_seat.events.start_drag.add(&seat.start_drag);
|
||||||
@ -142,9 +144,7 @@ pub fn deinit(seat: *Seat) void {
|
|||||||
seat.cursor.deinit();
|
seat.cursor.deinit();
|
||||||
seat.mapping_repeat_timer.remove();
|
seat.mapping_repeat_timer.remove();
|
||||||
|
|
||||||
while (seat.keyboard_groups.first) |node| {
|
seat.keyboard_group.destroy();
|
||||||
node.data.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
seat.request_set_selection.link.remove();
|
seat.request_set_selection.link.remove();
|
||||||
seat.request_start_drag.link.remove();
|
seat.request_start_drag.link.remove();
|
||||||
@ -165,14 +165,21 @@ pub fn focus(seat: *Seat, _target: ?*View) void {
|
|||||||
// Views may not receive focus while locked.
|
// Views may not receive focus while locked.
|
||||||
if (server.lock_manager.state != .unlocked) return;
|
if (server.lock_manager.state != .unlocked) return;
|
||||||
|
|
||||||
// While a layer surface is exclusively focused, views may not receive focus
|
// A layer surface with exclusive focus will prevent any view from gaining
|
||||||
|
// focus if it is on the top or overlay layer. Otherwise, only steal focus
|
||||||
|
// from a focused layer surface if there is an explicit target view.
|
||||||
if (seat.focused == .layer) {
|
if (seat.focused == .layer) {
|
||||||
const wlr_layer_surface = seat.focused.layer.wlr_layer_surface;
|
const wlr_layer_surface = seat.focused.layer.wlr_layer_surface;
|
||||||
assert(wlr_layer_surface.surface.mapped);
|
assert(wlr_layer_surface.surface.mapped);
|
||||||
if (wlr_layer_surface.current.keyboard_interactive == .exclusive and
|
switch (wlr_layer_surface.current.keyboard_interactive) {
|
||||||
(wlr_layer_surface.current.layer == .top or wlr_layer_surface.current.layer == .overlay))
|
.none => {},
|
||||||
{
|
.exclusive => switch (wlr_layer_surface.current.layer) {
|
||||||
return;
|
.top, .overlay => return,
|
||||||
|
.bottom, .background => if (target == null) return,
|
||||||
|
_ => {},
|
||||||
|
},
|
||||||
|
.on_demand => if (target == null) return,
|
||||||
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,11 +511,11 @@ fn tryAddDevice(seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
|||||||
|
|
||||||
seat.cursor.wlr_cursor.attachInputDevice(wlr_device);
|
seat.cursor.wlr_cursor.attachInputDevice(wlr_device);
|
||||||
},
|
},
|
||||||
.tablet_tool => {
|
.tablet => {
|
||||||
try Tablet.create(seat, wlr_device);
|
try Tablet.create(seat, wlr_device);
|
||||||
seat.cursor.wlr_cursor.attachInputDevice(wlr_device);
|
seat.cursor.wlr_cursor.attachInputDevice(wlr_device);
|
||||||
},
|
},
|
||||||
.switch_device => {
|
.@"switch" => {
|
||||||
const switch_device = try util.gpa.create(Switch);
|
const switch_device = try util.gpa.create(Switch);
|
||||||
errdefer util.gpa.destroy(switch_device);
|
errdefer util.gpa.destroy(switch_device);
|
||||||
|
|
||||||
@ -531,7 +538,7 @@ pub fn updateCapabilities(seat: *Seat) void {
|
|||||||
switch (device.wlr_device.type) {
|
switch (device.wlr_device.type) {
|
||||||
.keyboard => capabilities.keyboard = true,
|
.keyboard => capabilities.keyboard = true,
|
||||||
.touch => capabilities.touch = true,
|
.touch => capabilities.touch = true,
|
||||||
.pointer, .switch_device, .tablet_tool => {},
|
.pointer, .@"switch", .tablet => {},
|
||||||
.tablet_pad => unreachable,
|
.tablet_pad => unreachable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -544,7 +551,7 @@ fn handleRequestSetSelection(
|
|||||||
listener: *wl.Listener(*wlr.Seat.event.RequestSetSelection),
|
listener: *wl.Listener(*wlr.Seat.event.RequestSetSelection),
|
||||||
event: *wlr.Seat.event.RequestSetSelection,
|
event: *wlr.Seat.event.RequestSetSelection,
|
||||||
) void {
|
) void {
|
||||||
const seat = @fieldParentPtr(Seat, "request_set_selection", listener);
|
const seat: *Seat = @fieldParentPtr("request_set_selection", listener);
|
||||||
seat.wlr_seat.setSelection(event.source, event.serial);
|
seat.wlr_seat.setSelection(event.source, event.serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,7 +559,7 @@ fn handleRequestStartDrag(
|
|||||||
listener: *wl.Listener(*wlr.Seat.event.RequestStartDrag),
|
listener: *wl.Listener(*wlr.Seat.event.RequestStartDrag),
|
||||||
event: *wlr.Seat.event.RequestStartDrag,
|
event: *wlr.Seat.event.RequestStartDrag,
|
||||||
) void {
|
) void {
|
||||||
const seat = @fieldParentPtr(Seat, "request_start_drag", listener);
|
const seat: *Seat = @fieldParentPtr("request_start_drag", listener);
|
||||||
|
|
||||||
// The start_drag request is ignored by wlroots if a drag is currently in progress.
|
// The start_drag request is ignored by wlroots if a drag is currently in progress.
|
||||||
assert(seat.drag == .none);
|
assert(seat.drag == .none);
|
||||||
@ -576,7 +583,7 @@ fn handleRequestStartDrag(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleStartDrag(listener: *wl.Listener(*wlr.Drag), wlr_drag: *wlr.Drag) void {
|
fn handleStartDrag(listener: *wl.Listener(*wlr.Drag), wlr_drag: *wlr.Drag) void {
|
||||||
const seat = @fieldParentPtr(Seat, "start_drag", listener);
|
const seat: *Seat = @fieldParentPtr("start_drag", listener);
|
||||||
|
|
||||||
assert(seat.drag == .none);
|
assert(seat.drag == .none);
|
||||||
switch (wlr_drag.grab_type) {
|
switch (wlr_drag.grab_type) {
|
||||||
@ -599,7 +606,7 @@ fn handleStartDrag(listener: *wl.Listener(*wlr.Drag), wlr_drag: *wlr.Drag) void
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDragDestroy(listener: *wl.Listener(*wlr.Drag), _: *wlr.Drag) void {
|
fn handleDragDestroy(listener: *wl.Listener(*wlr.Drag), _: *wlr.Drag) void {
|
||||||
const seat = @fieldParentPtr(Seat, "drag_destroy", listener);
|
const seat: *Seat = @fieldParentPtr("drag_destroy", listener);
|
||||||
seat.drag_destroy.link.remove();
|
seat.drag_destroy.link.remove();
|
||||||
|
|
||||||
switch (seat.drag) {
|
switch (seat.drag) {
|
||||||
@ -617,6 +624,6 @@ fn handleRequestSetPrimarySelection(
|
|||||||
listener: *wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection),
|
listener: *wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection),
|
||||||
event: *wlr.Seat.event.RequestSetPrimarySelection,
|
event: *wlr.Seat.event.RequestSetPrimarySelection,
|
||||||
) void {
|
) void {
|
||||||
const seat = @fieldParentPtr(Seat, "request_set_primary_selection", listener);
|
const seat: *Seat = @fieldParentPtr("request_set_primary_selection", listener);
|
||||||
seat.wlr_seat.setPrimarySelection(event.source, event.serial);
|
seat.wlr_seat.setPrimarySelection(event.source, event.serial);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ fn handleRequest(seat_status_v1: *zriver.SeatStatusV1, request: zriver.SeatStatu
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(_: *zriver.SeatStatusV1, seat_status: *SeatStatus) void {
|
fn handleDestroy(_: *zriver.SeatStatusV1, seat_status: *SeatStatus) void {
|
||||||
const node = @fieldParentPtr(std.SinglyLinkedList(SeatStatus).Node, "data", seat_status);
|
const node: *std.SinglyLinkedList(SeatStatus).Node = @fieldParentPtr("data", seat_status);
|
||||||
seat_status.seat.status_trackers.remove(node);
|
seat_status.seat.status_trackers.remove(node);
|
||||||
util.gpa.destroy(node);
|
util.gpa.destroy(node);
|
||||||
}
|
}
|
||||||
|
144
river/Server.zig
144
river/Server.zig
@ -19,6 +19,8 @@ const Server = @This();
|
|||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const mem = std.mem;
|
||||||
|
const posix = std.posix;
|
||||||
const wlr = @import("wlroots");
|
const wlr = @import("wlroots");
|
||||||
const wl = @import("wayland").server.wl;
|
const wl = @import("wayland").server.wl;
|
||||||
|
|
||||||
@ -37,6 +39,7 @@ const Root = @import("Root.zig");
|
|||||||
const Seat = @import("Seat.zig");
|
const Seat = @import("Seat.zig");
|
||||||
const SceneNodeData = @import("SceneNodeData.zig");
|
const SceneNodeData = @import("SceneNodeData.zig");
|
||||||
const StatusManager = @import("StatusManager.zig");
|
const StatusManager = @import("StatusManager.zig");
|
||||||
|
const TabletTool = @import("TabletTool.zig");
|
||||||
const XdgDecoration = @import("XdgDecoration.zig");
|
const XdgDecoration = @import("XdgDecoration.zig");
|
||||||
const XdgToplevel = @import("XdgToplevel.zig");
|
const XdgToplevel = @import("XdgToplevel.zig");
|
||||||
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
||||||
@ -82,6 +85,10 @@ screencopy_manager: *wlr.ScreencopyManagerV1,
|
|||||||
|
|
||||||
foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
|
foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
|
||||||
|
|
||||||
|
tearing_control_manager: *wlr.TearingControlManagerV1,
|
||||||
|
|
||||||
|
alpha_modifier: *wlr.AlphaModifierV1,
|
||||||
|
|
||||||
input_manager: InputManager,
|
input_manager: InputManager,
|
||||||
root: Root,
|
root: Root,
|
||||||
config: Config,
|
config: Config,
|
||||||
@ -95,8 +102,10 @@ xwayland: if (build_options.xwayland) ?*wlr.Xwayland else void = if (build_optio
|
|||||||
new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void =
|
new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void =
|
||||||
if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface).init(handleNewXwaylandSurface),
|
if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface).init(handleNewXwaylandSurface),
|
||||||
|
|
||||||
new_xdg_surface: wl.Listener(*wlr.XdgSurface) =
|
renderer_lost: wl.Listener(void) = wl.Listener(void).init(handleRendererLost),
|
||||||
wl.Listener(*wlr.XdgSurface).init(handleNewXdgSurface),
|
|
||||||
|
new_xdg_toplevel: wl.Listener(*wlr.XdgToplevel) =
|
||||||
|
wl.Listener(*wlr.XdgToplevel).init(handleNewXdgToplevel),
|
||||||
new_toplevel_decoration: wl.Listener(*wlr.XdgToplevelDecorationV1) =
|
new_toplevel_decoration: wl.Listener(*wlr.XdgToplevelDecorationV1) =
|
||||||
wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleNewToplevelDecoration),
|
wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleNewToplevelDecoration),
|
||||||
new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1) =
|
new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1) =
|
||||||
@ -112,18 +121,18 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
|
|||||||
// This keeps the code simpler and more readable.
|
// This keeps the code simpler and more readable.
|
||||||
|
|
||||||
const wl_server = try wl.Server.create();
|
const wl_server = try wl.Server.create();
|
||||||
|
const loop = wl_server.getEventLoop();
|
||||||
|
|
||||||
var session: ?*wlr.Session = undefined;
|
var session: ?*wlr.Session = undefined;
|
||||||
const backend = try wlr.Backend.autocreate(wl_server, &session);
|
const backend = try wlr.Backend.autocreate(loop, &session);
|
||||||
const renderer = try wlr.Renderer.autocreate(backend);
|
const renderer = try wlr.Renderer.autocreate(backend);
|
||||||
|
|
||||||
const compositor = try wlr.Compositor.create(wl_server, 6, renderer);
|
const compositor = try wlr.Compositor.create(wl_server, 6, renderer);
|
||||||
|
|
||||||
const loop = wl_server.getEventLoop();
|
|
||||||
server.* = .{
|
server.* = .{
|
||||||
.wl_server = wl_server,
|
.wl_server = wl_server,
|
||||||
.sigint_source = try loop.addSignal(*wl.Server, std.os.SIG.INT, terminate, wl_server),
|
.sigint_source = try loop.addSignal(*wl.Server, posix.SIG.INT, terminate, wl_server),
|
||||||
.sigterm_source = try loop.addSignal(*wl.Server, std.os.SIG.TERM, terminate, wl_server),
|
.sigterm_source = try loop.addSignal(*wl.Server, posix.SIG.TERM, terminate, wl_server),
|
||||||
|
|
||||||
.backend = backend,
|
.backend = backend,
|
||||||
.session = session,
|
.session = session,
|
||||||
@ -155,6 +164,10 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
|
|||||||
|
|
||||||
.foreign_toplevel_manager = try wlr.ForeignToplevelManagerV1.create(wl_server),
|
.foreign_toplevel_manager = try wlr.ForeignToplevelManagerV1.create(wl_server),
|
||||||
|
|
||||||
|
.tearing_control_manager = try wlr.TearingControlManagerV1.create(wl_server, 1),
|
||||||
|
|
||||||
|
.alpha_modifier = try wlr.AlphaModifierV1.create(wl_server),
|
||||||
|
|
||||||
.config = try Config.init(),
|
.config = try Config.init(),
|
||||||
|
|
||||||
.root = undefined,
|
.root = undefined,
|
||||||
@ -166,7 +179,7 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
|
|||||||
.lock_manager = undefined,
|
.lock_manager = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (renderer.getDmabufFormats() != null and renderer.getDrmFd() >= 0) {
|
if (renderer.getTextureFormats(@intFromEnum(wlr.BufferCap.dmabuf)) != null) {
|
||||||
// wl_drm is a legacy interface and all clients should switch to linux_dmabuf.
|
// wl_drm is a legacy interface and all clients should switch to linux_dmabuf.
|
||||||
// However, enough widely used clients still rely on wl_drm that the pragmatic option
|
// However, enough widely used clients still rely on wl_drm that the pragmatic option
|
||||||
// is to keep it around for the near future.
|
// is to keep it around for the near future.
|
||||||
@ -189,7 +202,8 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
|
|||||||
try server.idle_inhibit_manager.init();
|
try server.idle_inhibit_manager.init();
|
||||||
try server.lock_manager.init();
|
try server.lock_manager.init();
|
||||||
|
|
||||||
server.xdg_shell.events.new_surface.add(&server.new_xdg_surface);
|
server.renderer.events.lost.add(&server.renderer_lost);
|
||||||
|
server.xdg_shell.events.new_toplevel.add(&server.new_xdg_toplevel);
|
||||||
server.xdg_decoration_manager.events.new_toplevel_decoration.add(&server.new_toplevel_decoration);
|
server.xdg_decoration_manager.events.new_toplevel_decoration.add(&server.new_toplevel_decoration);
|
||||||
server.layer_shell.events.new_surface.add(&server.new_layer_surface);
|
server.layer_shell.events.new_surface.add(&server.new_layer_surface);
|
||||||
server.xdg_activation.events.request_activate.add(&server.request_activate);
|
server.xdg_activation.events.request_activate.add(&server.request_activate);
|
||||||
@ -203,7 +217,8 @@ pub fn deinit(server: *Server) void {
|
|||||||
server.sigint_source.remove();
|
server.sigint_source.remove();
|
||||||
server.sigterm_source.remove();
|
server.sigterm_source.remove();
|
||||||
|
|
||||||
server.new_xdg_surface.link.remove();
|
server.renderer_lost.link.remove();
|
||||||
|
server.new_xdg_toplevel.link.remove();
|
||||||
server.new_toplevel_decoration.link.remove();
|
server.new_toplevel_decoration.link.remove();
|
||||||
server.new_layer_surface.link.remove();
|
server.new_layer_surface.link.remove();
|
||||||
server.request_activate.link.remove();
|
server.request_activate.link.remove();
|
||||||
@ -276,14 +291,6 @@ fn globalFilter(client: *const wl.Client, global: *const wl.Global, server: *Ser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hackGlobal(ptr: *anyopaque) *wl.Global {
|
|
||||||
// TODO(wlroots) MR that eliminates the need for this hack:
|
|
||||||
// https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4612
|
|
||||||
if (wlr.version.major != 0 or wlr.version.minor != 17) @compileError("FIXME");
|
|
||||||
|
|
||||||
return @as(*extern struct { global: *wl.Global }, @alignCast(@ptrCast(ptr))).global;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the global is allowlisted for security contexts
|
/// Returns true if the global is allowlisted for security contexts
|
||||||
fn allowlist(server: *Server, global: *const wl.Global) bool {
|
fn allowlist(server: *Server, global: *const wl.Global) bool {
|
||||||
if (server.drm) |drm| if (global == drm.global) return true;
|
if (server.drm) |drm| if (global == drm.global) return true;
|
||||||
@ -293,14 +300,17 @@ fn allowlist(server: *Server, global: *const wl.Global) bool {
|
|||||||
// such as wl_output and wl_seat since the wl_global_create() function will
|
// such as wl_output and wl_seat since the wl_global_create() function will
|
||||||
// advertise the global to clients and invoke this filter before returning
|
// advertise the global to clients and invoke this filter before returning
|
||||||
// the new global pointer.
|
// the new global pointer.
|
||||||
//
|
if ((mem.orderZ(u8, global.getInterface().name, "wl_output") == .eq) or
|
||||||
|
(mem.orderZ(u8, global.getInterface().name, "wl_seat") == .eq))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// For other globals I like the current pointer comparison approach as it
|
// For other globals I like the current pointer comparison approach as it
|
||||||
// should catch river accidentally exposing multiple copies of e.g. wl_shm
|
// should catch river accidentally exposing multiple copies of e.g. wl_shm
|
||||||
// with an assertion failure.
|
// with an assertion failure.
|
||||||
return global.getInterface() == wl.Output.getInterface() or
|
return global == server.shm.global or
|
||||||
global.getInterface() == wl.Seat.getInterface() or
|
global == server.single_pixel_buffer_manager.global or
|
||||||
global == hackGlobal(server.shm) or
|
|
||||||
global == hackGlobal(server.single_pixel_buffer_manager) or
|
|
||||||
global == server.viewporter.global or
|
global == server.viewporter.global or
|
||||||
global == server.fractional_scale_manager.global or
|
global == server.fractional_scale_manager.global or
|
||||||
global == server.compositor.global or
|
global == server.compositor.global or
|
||||||
@ -318,7 +328,9 @@ fn allowlist(server: *Server, global: *const wl.Global) bool {
|
|||||||
global == server.input_manager.text_input_manager.global or
|
global == server.input_manager.text_input_manager.global or
|
||||||
global == server.input_manager.tablet_manager.global or
|
global == server.input_manager.tablet_manager.global or
|
||||||
global == server.input_manager.pointer_gestures.global or
|
global == server.input_manager.pointer_gestures.global or
|
||||||
global == server.idle_inhibit_manager.wlr_manager.global;
|
global == server.idle_inhibit_manager.wlr_manager.global or
|
||||||
|
global == server.tearing_control_manager.global or
|
||||||
|
global == server.alpha_modifier.global;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the global is blocked for security contexts
|
/// Returns true if the global is blocked for security contexts
|
||||||
@ -335,7 +347,7 @@ fn blocklist(server: *Server, global: *const wl.Global) bool {
|
|||||||
global == server.root.output_manager.global or
|
global == server.root.output_manager.global or
|
||||||
global == server.root.power_manager.global or
|
global == server.root.power_manager.global or
|
||||||
global == server.root.gamma_control_manager.global or
|
global == server.root.gamma_control_manager.global or
|
||||||
global == hackGlobal(server.input_manager.idle_notifier) or
|
global == server.input_manager.idle_notifier.global or
|
||||||
global == server.input_manager.virtual_pointer_manager.global or
|
global == server.input_manager.virtual_pointer_manager.global or
|
||||||
global == server.input_manager.virtual_keyboard_manager.global or
|
global == server.input_manager.virtual_keyboard_manager.global or
|
||||||
global == server.input_manager.input_method_manager.global or
|
global == server.input_manager.input_method_manager.global or
|
||||||
@ -348,17 +360,55 @@ fn terminate(_: c_int, wl_server: *wl.Server) c_int {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleNewXdgSurface(_: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
|
fn handleRendererLost(listener: *wl.Listener(void)) void {
|
||||||
if (xdg_surface.role == .popup) {
|
const server: *Server = @fieldParentPtr("renderer_lost", listener);
|
||||||
log.debug("new xdg_popup", .{});
|
|
||||||
return;
|
log.info("recovering from GPU reset", .{});
|
||||||
|
|
||||||
|
// There's not much that can be done if creating a new renderer or allocator fails.
|
||||||
|
// With luck there might be another GPU reset after which we try again and succeed.
|
||||||
|
|
||||||
|
server.recoverFromGpuReset() catch |err| switch (err) {
|
||||||
|
error.RendererCreateFailed => log.err("failed to create new renderer after GPU reset", .{}),
|
||||||
|
error.AllocatorCreateFailed => log.err("failed to create new allocator after GPU reset", .{}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recoverFromGpuReset(server: *Server) !void {
|
||||||
|
const new_renderer = try wlr.Renderer.autocreate(server.backend);
|
||||||
|
errdefer new_renderer.destroy();
|
||||||
|
|
||||||
|
const new_allocator = try wlr.Allocator.autocreate(server.backend, new_renderer);
|
||||||
|
errdefer comptime unreachable; // no failure allowed after this point
|
||||||
|
|
||||||
|
server.renderer_lost.link.remove();
|
||||||
|
new_renderer.events.lost.add(&server.renderer_lost);
|
||||||
|
|
||||||
|
server.compositor.setRenderer(new_renderer);
|
||||||
|
|
||||||
|
{
|
||||||
|
var it = server.root.all_outputs.iterator(.forward);
|
||||||
|
while (it.next()) |output| {
|
||||||
|
// This should never fail here as failure with this combination of
|
||||||
|
// renderer, allocator, and backend should have prevented creating
|
||||||
|
// the output in the first place.
|
||||||
|
_ = output.wlr_output.initRender(new_allocator, new_renderer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server.renderer.destroy();
|
||||||
|
server.renderer = new_renderer;
|
||||||
|
|
||||||
|
server.allocator.destroy();
|
||||||
|
server.allocator = new_allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleNewXdgToplevel(_: *wl.Listener(*wlr.XdgToplevel), xdg_toplevel: *wlr.XdgToplevel) void {
|
||||||
log.debug("new xdg_toplevel", .{});
|
log.debug("new xdg_toplevel", .{});
|
||||||
|
|
||||||
XdgToplevel.create(xdg_surface.role_data.toplevel.?) catch {
|
XdgToplevel.create(xdg_toplevel) catch {
|
||||||
log.err("out of memory", .{});
|
log.err("out of memory", .{});
|
||||||
xdg_surface.resource.postNoMemory();
|
xdg_toplevel.resource.postNoMemory();
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -371,7 +421,7 @@ fn handleNewToplevelDecoration(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleNewLayerSurface(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
|
fn handleNewLayerSurface(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
|
||||||
const server = @fieldParentPtr(Server, "new_layer_surface", listener);
|
const server: *Server = @fieldParentPtr("new_layer_surface", listener);
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"new layer surface: namespace {s}, layer {s}, anchor {b:0>4}, size {},{}, margin {},{},{},{}, exclusive_zone {}",
|
"new layer surface: namespace {s}, layer {s}, anchor {b:0>4}, size {},{}, margin {},{},{},{}, exclusive_zone {}",
|
||||||
@ -431,7 +481,7 @@ fn handleRequestActivate(
|
|||||||
listener: *wl.Listener(*wlr.XdgActivationV1.event.RequestActivate),
|
listener: *wl.Listener(*wlr.XdgActivationV1.event.RequestActivate),
|
||||||
event: *wlr.XdgActivationV1.event.RequestActivate,
|
event: *wlr.XdgActivationV1.event.RequestActivate,
|
||||||
) void {
|
) void {
|
||||||
const server = @fieldParentPtr(Server, "request_activate", listener);
|
const server: *Server = @fieldParentPtr("request_activate", listener);
|
||||||
|
|
||||||
const node_data = SceneNodeData.fromSurface(event.surface) orelse return;
|
const node_data = SceneNodeData.fromSurface(event.surface) orelse return;
|
||||||
switch (node_data.data) {
|
switch (node_data.data) {
|
||||||
@ -449,17 +499,27 @@ fn handleRequestSetCursorShape(
|
|||||||
_: *wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape),
|
_: *wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape),
|
||||||
event: *wlr.CursorShapeManagerV1.event.RequestSetShape,
|
event: *wlr.CursorShapeManagerV1.event.RequestSetShape,
|
||||||
) void {
|
) void {
|
||||||
// Ignore requests to set a tablet tool's cursor shape for now
|
const seat: *Seat = @ptrFromInt(event.seat_client.seat.data);
|
||||||
// TODO(wlroots): https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3821
|
|
||||||
if (event.device_type == .tablet_tool) return;
|
|
||||||
|
|
||||||
const focused_client = event.seat_client.seat.pointer_state.focused_client;
|
if (event.tablet_tool) |wp_tool| {
|
||||||
|
assert(event.device_type == .tablet_tool);
|
||||||
|
|
||||||
// This can be sent by any client, so we check to make sure this one is
|
const tool = TabletTool.get(event.seat_client.seat, wp_tool.wlr_tool) catch return;
|
||||||
// actually has pointer focus first.
|
|
||||||
if (focused_client == event.seat_client) {
|
if (tool.allowSetCursor(event.seat_client, event.serial)) {
|
||||||
const seat: *Seat = @ptrFromInt(event.seat_client.seat.data);
|
const name = wlr.CursorShapeManagerV1.shapeName(event.shape);
|
||||||
const name = wlr.CursorShapeManagerV1.shapeName(event.shape);
|
tool.wlr_cursor.setXcursor(seat.cursor.xcursor_manager, name);
|
||||||
seat.cursor.setXcursor(name);
|
}
|
||||||
|
} else {
|
||||||
|
assert(event.device_type == .pointer);
|
||||||
|
|
||||||
|
const focused_client = event.seat_client.seat.pointer_state.focused_client;
|
||||||
|
|
||||||
|
// This can be sent by any client, so we check to make sure this one is
|
||||||
|
// actually has pointer focus first.
|
||||||
|
if (focused_client == event.seat_client) {
|
||||||
|
const name = wlr.CursorShapeManagerV1.shapeName(event.shape);
|
||||||
|
seat.cursor.setImage(.{ .xcursor = name });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ pub fn init(status_manager: *StatusManager) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
|
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
|
||||||
const status_manager = @fieldParentPtr(StatusManager, "server_destroy", listener);
|
const status_manager: *StatusManager = @fieldParentPtr("server_destroy", listener);
|
||||||
status_manager.global.destroy();
|
status_manager.global.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ pub fn deinit(switch_device: *Switch) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleToggle(listener: *wl.Listener(*wlr.Switch.event.Toggle), event: *wlr.Switch.event.Toggle) void {
|
fn handleToggle(listener: *wl.Listener(*wlr.Switch.event.Toggle), event: *wlr.Switch.event.Toggle) void {
|
||||||
const switch_device = @fieldParentPtr(Switch, "toggle", listener);
|
const switch_device: *Switch = @fieldParentPtr("toggle", listener);
|
||||||
|
|
||||||
switch_device.device.seat.handleActivity();
|
switch_device.device.seat.handleActivity();
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ wp_tablet: *wlr.TabletV2Tablet,
|
|||||||
output_mapping: ?*wlr.Output = null,
|
output_mapping: ?*wlr.Output = null,
|
||||||
|
|
||||||
pub fn create(seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
pub fn create(seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
||||||
assert(wlr_device.type == .tablet_tool);
|
assert(wlr_device.type == .tablet);
|
||||||
|
|
||||||
const tablet = try util.gpa.create(Tablet);
|
const tablet = try util.gpa.create(Tablet);
|
||||||
errdefer util.gpa.destroy(tablet);
|
errdefer util.gpa.destroy(tablet);
|
||||||
|
@ -90,7 +90,7 @@ fn create(wlr_seat: *wlr.Seat, wlr_tool: *wlr.TabletTool) error{OutOfMemory}!*Ta
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(*wlr.TabletTool), _: *wlr.TabletTool) void {
|
fn handleDestroy(listener: *wl.Listener(*wlr.TabletTool), _: *wlr.TabletTool) void {
|
||||||
const tool = @fieldParentPtr(TabletTool, "destroy", listener);
|
const tool: *TabletTool = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
tool.wp_tool.wlr_tool.data = 0;
|
tool.wp_tool.wlr_tool.data = 0;
|
||||||
|
|
||||||
@ -102,24 +102,29 @@ fn handleDestroy(listener: *wl.Listener(*wlr.TabletTool), _: *wlr.TabletTool) vo
|
|||||||
util.gpa.destroy(tool);
|
util.gpa.destroy(tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn allowSetCursor(tool: *TabletTool, seat_client: *wlr.Seat.Client, serial: u32) bool {
|
||||||
|
if (tool.wp_tool.focused_surface == null or
|
||||||
|
tool.wp_tool.focused_surface.?.resource.getClient() != seat_client.client)
|
||||||
|
{
|
||||||
|
log.debug("client tried to set cursor without focus", .{});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (serial != tool.wp_tool.proximity_serial) {
|
||||||
|
log.debug("focused client tried to set cursor with incorrect serial", .{});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
fn handleSetCursor(
|
fn handleSetCursor(
|
||||||
listener: *wl.Listener(*wlr.TabletV2TabletTool.event.SetCursor),
|
listener: *wl.Listener(*wlr.TabletV2TabletTool.event.SetCursor),
|
||||||
event: *wlr.TabletV2TabletTool.event.SetCursor,
|
event: *wlr.TabletV2TabletTool.event.SetCursor,
|
||||||
) void {
|
) void {
|
||||||
const tool = @fieldParentPtr(TabletTool, "set_cursor", listener);
|
const tool: *TabletTool = @fieldParentPtr("set_cursor", listener);
|
||||||
|
|
||||||
if (tool.wp_tool.focused_surface == null or
|
if (tool.allowSetCursor(event.seat_client, event.serial)) {
|
||||||
tool.wp_tool.focused_surface.?.resource.getClient() != event.seat_client.client)
|
tool.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y);
|
||||||
{
|
|
||||||
log.debug("client tried to set cursor without focus", .{});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (event.serial != tool.wp_tool.proximity_serial) {
|
|
||||||
log.debug("focused client tried to set cursor with incorrect serial", .{});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tool.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn axis(tool: *TabletTool, tablet: *Tablet, event: *wlr.Tablet.event.Axis) void {
|
pub fn axis(tool: *TabletTool, tablet: *Tablet, event: *wlr.Tablet.event.Axis) void {
|
||||||
|
@ -63,9 +63,14 @@ pub fn create(wlr_text_input: *wlr.TextInputV3) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
|
fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
|
||||||
const text_input = @fieldParentPtr(TextInput, "enable", listener);
|
const text_input: *TextInput = @fieldParentPtr("enable", listener);
|
||||||
const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
|
const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
|
||||||
|
|
||||||
|
if (text_input.wlr_text_input.focused_surface == null) {
|
||||||
|
log.err("client requested to enable text input without focus, ignoring request", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// The same text_input object may be enabled multiple times consecutively
|
// The same text_input object may be enabled multiple times consecutively
|
||||||
// without first disabling it. Enabling a different text input object without
|
// without first disabling it. Enabling a different text input object without
|
||||||
// first disabling the current one is disallowed by the protocol however.
|
// first disabling the current one is disallowed by the protocol however.
|
||||||
@ -85,7 +90,7 @@ fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) v
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
|
fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
|
||||||
const text_input = @fieldParentPtr(TextInput, "commit", listener);
|
const text_input: *TextInput = @fieldParentPtr("commit", listener);
|
||||||
const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
|
const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
|
||||||
|
|
||||||
if (seat.relay.text_input != text_input) {
|
if (seat.relay.text_input != text_input) {
|
||||||
@ -99,7 +104,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) v
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
|
fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
|
||||||
const text_input = @fieldParentPtr(TextInput, "disable", listener);
|
const text_input: *TextInput = @fieldParentPtr("disable", listener);
|
||||||
const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
|
const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
|
||||||
|
|
||||||
if (seat.relay.text_input == text_input) {
|
if (seat.relay.text_input == text_input) {
|
||||||
@ -108,7 +113,7 @@ fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
|
fn handleDestroy(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
|
||||||
const text_input = @fieldParentPtr(TextInput, "destroy", listener);
|
const text_input: *TextInput = @fieldParentPtr("destroy", listener);
|
||||||
const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
|
const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
|
||||||
|
|
||||||
if (seat.relay.text_input == text_input) {
|
if (seat.relay.text_input == text_input) {
|
||||||
|
@ -44,7 +44,7 @@ pub fn direction(v: Vector) ?wlr.OutputLayout.Direction {
|
|||||||
// A zero length vector has no direction
|
// A zero length vector has no direction
|
||||||
if (v.x == 0 and v.y == 0) return null;
|
if (v.x == 0 and v.y == 0) return null;
|
||||||
|
|
||||||
if ((math.absInt(v.y) catch return null) > (math.absInt(v.x) catch return null)) {
|
if (@abs(v.y) > @abs(v.x)) {
|
||||||
// Careful: We are operating in a Y-inverted coordinate system.
|
// Careful: We are operating in a Y-inverted coordinate system.
|
||||||
return if (v.y > 0) .down else .up;
|
return if (v.y > 0) .down else .up;
|
||||||
} else {
|
} else {
|
||||||
|
@ -20,9 +20,10 @@ const build_options = @import("build_options");
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const math = std.math;
|
const math = std.math;
|
||||||
const os = std.os;
|
const posix = std.posix;
|
||||||
const wlr = @import("wlroots");
|
const wlr = @import("wlroots");
|
||||||
const wl = @import("wayland").server.wl;
|
const wl = @import("wayland").server.wl;
|
||||||
|
const wp = @import("wayland").server.wp;
|
||||||
|
|
||||||
const server = &@import("main.zig").server;
|
const server = &@import("main.zig").server;
|
||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
@ -59,6 +60,12 @@ const AttachRelativeMode = enum {
|
|||||||
below,
|
below,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TearingMode = enum {
|
||||||
|
no_tearing,
|
||||||
|
tearing,
|
||||||
|
window_hint,
|
||||||
|
};
|
||||||
|
|
||||||
pub const State = struct {
|
pub const State = struct {
|
||||||
/// The output the view is currently assigned to.
|
/// The output the view is currently assigned to.
|
||||||
/// May be null if there are no outputs or for newly created views.
|
/// May be null if there are no outputs or for newly created views.
|
||||||
@ -177,6 +184,8 @@ foreign_toplevel_handle: ForeignToplevelHandle = .{},
|
|||||||
/// Connector name of the output this view occupied before an evacuation.
|
/// Connector name of the output this view occupied before an evacuation.
|
||||||
output_before_evac: ?[]const u8 = null,
|
output_before_evac: ?[]const u8 = null,
|
||||||
|
|
||||||
|
tearing_mode: TearingMode = .window_hint,
|
||||||
|
|
||||||
pub fn create(impl: Impl) error{OutOfMemory}!*View {
|
pub fn create(impl: Impl) error{OutOfMemory}!*View {
|
||||||
assert(impl != .none);
|
assert(impl != .none);
|
||||||
|
|
||||||
@ -472,8 +481,9 @@ pub fn rootSurface(view: View) ?*wlr.Surface {
|
|||||||
|
|
||||||
pub fn sendFrameDone(view: View) void {
|
pub fn sendFrameDone(view: View) void {
|
||||||
assert(view.mapped and !view.destroying);
|
assert(view.mapped and !view.destroying);
|
||||||
var now: os.timespec = undefined;
|
|
||||||
os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
var now: posix.timespec = undefined;
|
||||||
|
posix.clock_gettime(posix.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
||||||
view.rootSurface().?.sendFrameDone(&now);
|
view.rootSurface().?.sendFrameDone(&now);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -571,6 +581,22 @@ pub fn getAppId(view: View) ?[*:0]const u8 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return true if tearing should be allowed for the view.
|
||||||
|
pub fn allowTearing(view: *View) bool {
|
||||||
|
switch (view.tearing_mode) {
|
||||||
|
.no_tearing => return false,
|
||||||
|
.tearing => return true,
|
||||||
|
.window_hint => {
|
||||||
|
if (server.config.allow_tearing) {
|
||||||
|
if (view.rootSurface()) |root_surface| {
|
||||||
|
return server.tearing_control_manager.hintFromSurface(root_surface) == .@"async";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Clamp the width/height of the box to the constraints of the view
|
/// Clamp the width/height of the box to the constraints of the view
|
||||||
pub fn applyConstraints(view: *View, box: *wlr.Box) void {
|
pub fn applyConstraints(view: *View, box: *wlr.Box) void {
|
||||||
box.width = math.clamp(box.width, view.constraints.min_width, view.constraints.max_width);
|
box.width = math.clamp(box.width, view.constraints.min_width, view.constraints.max_width);
|
||||||
@ -639,6 +665,10 @@ pub fn map(view: *View) !void {
|
|||||||
view.pending.ssd = ssd;
|
view.pending.ssd = ssd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (server.config.rules.tearing.match(view)) |tearing| {
|
||||||
|
view.tearing_mode = if (tearing) .tearing else .no_tearing;
|
||||||
|
}
|
||||||
|
|
||||||
if (server.config.rules.dimensions.match(view)) |dimensions| {
|
if (server.config.rules.dimensions.match(view)) |dimensions| {
|
||||||
view.pending.box.width = dimensions.width;
|
view.pending.box.width = dimensions.width;
|
||||||
view.pending.box.height = dimensions.height;
|
view.pending.box.height = dimensions.height;
|
||||||
@ -648,8 +678,18 @@ pub fn map(view: *View) !void {
|
|||||||
server.input_manager.defaultSeat().focused_output;
|
server.input_manager.defaultSeat().focused_output;
|
||||||
|
|
||||||
if (server.config.rules.position.match(view)) |position| {
|
if (server.config.rules.position.match(view)) |position| {
|
||||||
view.pending.box.x = position.x;
|
var base_x: i31 = 0;
|
||||||
view.pending.box.y = position.y;
|
var base_y: i31 = 0;
|
||||||
|
switch (position.anchor) {
|
||||||
|
.absolute => {},
|
||||||
|
.mouse => {
|
||||||
|
const cursor = server.input_manager.defaultSeat().wlr_seat.pointer_state;
|
||||||
|
base_x = @intCast(@as(i31, @intFromFloat(cursor.sx)));
|
||||||
|
base_y = @intCast(@as(i31, @intFromFloat(cursor.sy)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
view.pending.box.x = base_x + position.x;
|
||||||
|
view.pending.box.y = base_y + position.y;
|
||||||
} else if (output) |o| {
|
} else if (output) |o| {
|
||||||
// Center the initial pending box on the output
|
// Center the initial pending box on the output
|
||||||
view.pending.box.x = @divTrunc(@max(0, o.usable_box.width - view.pending.box.width), 2);
|
view.pending.box.x = @divTrunc(@max(0, o.usable_box.width - view.pending.box.width), 2);
|
||||||
|
@ -42,14 +42,9 @@ pub fn init(wlr_decoration: *wlr.XdgToplevelDecorationV1) void {
|
|||||||
wlr_decoration.events.destroy.add(&decoration.destroy);
|
wlr_decoration.events.destroy.add(&decoration.destroy);
|
||||||
wlr_decoration.events.request_mode.add(&decoration.request_mode);
|
wlr_decoration.events.request_mode.add(&decoration.request_mode);
|
||||||
|
|
||||||
const ssd = server.config.rules.ssd.match(toplevel.view) orelse
|
if (toplevel.wlr_toplevel.base.initialized) {
|
||||||
(decoration.wlr_decoration.requested_mode != .client_side);
|
handleRequestMode(&decoration.request_mode, wlr_decoration);
|
||||||
|
}
|
||||||
// TODO(wlroots): make sure this is properly batched in a single configure
|
|
||||||
// with all other initial state when wlroots makes this possible.
|
|
||||||
_ = wlr_decoration.setMode(if (ssd) .server_side else .client_side);
|
|
||||||
|
|
||||||
toplevel.view.pending.ssd = ssd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(decoration: *XdgDecoration) void {
|
pub fn deinit(decoration: *XdgDecoration) void {
|
||||||
@ -66,7 +61,7 @@ fn handleDestroy(
|
|||||||
listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
|
listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
|
||||||
_: *wlr.XdgToplevelDecorationV1,
|
_: *wlr.XdgToplevelDecorationV1,
|
||||||
) void {
|
) void {
|
||||||
const decoration = @fieldParentPtr(XdgDecoration, "destroy", listener);
|
const decoration: *XdgDecoration = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
decoration.deinit();
|
decoration.deinit();
|
||||||
}
|
}
|
||||||
@ -75,7 +70,7 @@ fn handleRequestMode(
|
|||||||
listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
|
listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
|
||||||
_: *wlr.XdgToplevelDecorationV1,
|
_: *wlr.XdgToplevelDecorationV1,
|
||||||
) void {
|
) void {
|
||||||
const decoration = @fieldParentPtr(XdgDecoration, "request_mode", listener);
|
const decoration: *XdgDecoration = @fieldParentPtr("request_mode", listener);
|
||||||
|
|
||||||
const toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.toplevel.base.data);
|
const toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.toplevel.base.data);
|
||||||
const view = toplevel.view;
|
const view = toplevel.view;
|
||||||
|
@ -35,6 +35,7 @@ root: *wlr.SceneTree,
|
|||||||
tree: *wlr.SceneTree,
|
tree: *wlr.SceneTree,
|
||||||
|
|
||||||
destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy),
|
destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy),
|
||||||
|
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
|
||||||
new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
|
new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
|
||||||
reposition: wl.Listener(void) = wl.Listener(void).init(handleReposition),
|
reposition: wl.Listener(void) = wl.Listener(void).init(handleReposition),
|
||||||
|
|
||||||
@ -53,25 +54,33 @@ pub fn create(
|
|||||||
.tree = try parent.createSceneXdgSurface(wlr_xdg_popup.base),
|
.tree = try parent.createSceneXdgSurface(wlr_xdg_popup.base),
|
||||||
};
|
};
|
||||||
|
|
||||||
wlr_xdg_popup.base.events.destroy.add(&xdg_popup.destroy);
|
wlr_xdg_popup.events.destroy.add(&xdg_popup.destroy);
|
||||||
|
wlr_xdg_popup.base.surface.events.commit.add(&xdg_popup.commit);
|
||||||
wlr_xdg_popup.base.events.new_popup.add(&xdg_popup.new_popup);
|
wlr_xdg_popup.base.events.new_popup.add(&xdg_popup.new_popup);
|
||||||
wlr_xdg_popup.events.reposition.add(&xdg_popup.reposition);
|
wlr_xdg_popup.events.reposition.add(&xdg_popup.reposition);
|
||||||
|
|
||||||
handleReposition(&xdg_popup.reposition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(void)) void {
|
fn handleDestroy(listener: *wl.Listener(void)) void {
|
||||||
const xdg_popup = @fieldParentPtr(XdgPopup, "destroy", listener);
|
const xdg_popup: *XdgPopup = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
xdg_popup.destroy.link.remove();
|
xdg_popup.destroy.link.remove();
|
||||||
|
xdg_popup.commit.link.remove();
|
||||||
xdg_popup.new_popup.link.remove();
|
xdg_popup.new_popup.link.remove();
|
||||||
xdg_popup.reposition.link.remove();
|
xdg_popup.reposition.link.remove();
|
||||||
|
|
||||||
util.gpa.destroy(xdg_popup);
|
util.gpa.destroy(xdg_popup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
||||||
|
const xdg_popup: *XdgPopup = @fieldParentPtr("commit", listener);
|
||||||
|
|
||||||
|
if (xdg_popup.wlr_xdg_popup.base.initial_commit) {
|
||||||
|
handleReposition(&xdg_popup.reposition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
|
fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
|
||||||
const xdg_popup = @fieldParentPtr(XdgPopup, "new_popup", listener);
|
const xdg_popup: *XdgPopup = @fieldParentPtr("new_popup", listener);
|
||||||
|
|
||||||
XdgPopup.create(
|
XdgPopup.create(
|
||||||
wlr_xdg_popup,
|
wlr_xdg_popup,
|
||||||
@ -84,7 +93,7 @@ fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.Xdg
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleReposition(listener: *wl.Listener(void)) void {
|
fn handleReposition(listener: *wl.Listener(void)) void {
|
||||||
const xdg_popup = @fieldParentPtr(XdgPopup, "reposition", listener);
|
const xdg_popup: *XdgPopup = @fieldParentPtr("reposition", listener);
|
||||||
|
|
||||||
const output = switch (SceneNodeData.fromNode(&xdg_popup.root.node).?.data) {
|
const output = switch (SceneNodeData.fromNode(&xdg_popup.root.node).?.data) {
|
||||||
.view => |view| view.current.output orelse return,
|
.view => |view| view.current.output orelse return,
|
||||||
|
@ -62,12 +62,12 @@ configure_state: union(enum) {
|
|||||||
destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy),
|
destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy),
|
||||||
map: wl.Listener(void) = wl.Listener(void).init(handleMap),
|
map: wl.Listener(void) = wl.Listener(void).init(handleMap),
|
||||||
unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap),
|
unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap),
|
||||||
|
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
|
||||||
new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
|
new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
|
||||||
|
|
||||||
// Listeners that are only active while the view is mapped
|
// Listeners that are only active while the view is mapped
|
||||||
ack_configure: wl.Listener(*wlr.XdgSurface.Configure) =
|
ack_configure: wl.Listener(*wlr.XdgSurface.Configure) =
|
||||||
wl.Listener(*wlr.XdgSurface.Configure).init(handleAckConfigure),
|
wl.Listener(*wlr.XdgSurface.Configure).init(handleAckConfigure),
|
||||||
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
|
|
||||||
request_fullscreen: wl.Listener(void) = wl.Listener(void).init(handleRequestFullscreen),
|
request_fullscreen: wl.Listener(void) = wl.Listener(void).init(handleRequestFullscreen),
|
||||||
request_move: wl.Listener(*wlr.XdgToplevel.event.Move) =
|
request_move: wl.Listener(*wlr.XdgToplevel.event.Move) =
|
||||||
wl.Listener(*wlr.XdgToplevel.event.Move).init(handleRequestMove),
|
wl.Listener(*wlr.XdgToplevel.event.Move).init(handleRequestMove),
|
||||||
@ -104,11 +104,10 @@ pub fn create(wlr_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void {
|
|||||||
wlr_toplevel.base.surface.data = @intFromPtr(&view.tree.node);
|
wlr_toplevel.base.surface.data = @intFromPtr(&view.tree.node);
|
||||||
|
|
||||||
// Add listeners that are active over the toplevel's entire lifetime
|
// Add listeners that are active over the toplevel's entire lifetime
|
||||||
wlr_toplevel.base.events.destroy.add(&toplevel.destroy);
|
wlr_toplevel.events.destroy.add(&toplevel.destroy);
|
||||||
wlr_toplevel.base.surface.events.map.add(&toplevel.map);
|
wlr_toplevel.base.surface.events.map.add(&toplevel.map);
|
||||||
|
wlr_toplevel.base.surface.events.commit.add(&toplevel.commit);
|
||||||
wlr_toplevel.base.events.new_popup.add(&toplevel.new_popup);
|
wlr_toplevel.base.events.new_popup.add(&toplevel.new_popup);
|
||||||
|
|
||||||
_ = wlr_toplevel.setWmCapabilities(.{ .fullscreen = true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a configure event, applying the inflight state of the view.
|
/// Send a configure event, applying the inflight state of the view.
|
||||||
@ -200,7 +199,7 @@ pub fn destroyPopups(toplevel: XdgToplevel) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(void)) void {
|
fn handleDestroy(listener: *wl.Listener(void)) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "destroy", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
// This can be be non-null here if the client commits a protocol error or
|
// This can be be non-null here if the client commits a protocol error or
|
||||||
// if it exits without destroying its wayland objects.
|
// if it exits without destroying its wayland objects.
|
||||||
@ -213,8 +212,10 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
|
|||||||
toplevel.destroy.link.remove();
|
toplevel.destroy.link.remove();
|
||||||
toplevel.map.link.remove();
|
toplevel.map.link.remove();
|
||||||
toplevel.unmap.link.remove();
|
toplevel.unmap.link.remove();
|
||||||
|
toplevel.commit.link.remove();
|
||||||
|
toplevel.new_popup.link.remove();
|
||||||
|
|
||||||
// The wlr_surface may outlive the wlr_xdg_surface so we must clean up the user data.
|
// The wlr_surface may outlive the wlr_xdg_toplevel so we must clean up the user data.
|
||||||
toplevel.wlr_toplevel.base.surface.data = 0;
|
toplevel.wlr_toplevel.base.surface.data = 0;
|
||||||
|
|
||||||
const view = toplevel.view;
|
const view = toplevel.view;
|
||||||
@ -223,12 +224,11 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleMap(listener: *wl.Listener(void)) void {
|
fn handleMap(listener: *wl.Listener(void)) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "map", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("map", listener);
|
||||||
const view = toplevel.view;
|
const view = toplevel.view;
|
||||||
|
|
||||||
// Add listeners that are only active while mapped
|
// Add listeners that are only active while mapped
|
||||||
toplevel.wlr_toplevel.base.events.ack_configure.add(&toplevel.ack_configure);
|
toplevel.wlr_toplevel.base.events.ack_configure.add(&toplevel.ack_configure);
|
||||||
toplevel.wlr_toplevel.base.surface.events.commit.add(&toplevel.commit);
|
|
||||||
toplevel.wlr_toplevel.events.request_fullscreen.add(&toplevel.request_fullscreen);
|
toplevel.wlr_toplevel.events.request_fullscreen.add(&toplevel.request_fullscreen);
|
||||||
toplevel.wlr_toplevel.events.request_move.add(&toplevel.request_move);
|
toplevel.wlr_toplevel.events.request_move.add(&toplevel.request_move);
|
||||||
toplevel.wlr_toplevel.events.request_resize.add(&toplevel.request_resize);
|
toplevel.wlr_toplevel.events.request_resize.add(&toplevel.request_resize);
|
||||||
@ -266,11 +266,10 @@ fn handleMap(listener: *wl.Listener(void)) void {
|
|||||||
|
|
||||||
/// Called when the surface is unmapped and will no longer be displayed.
|
/// Called when the surface is unmapped and will no longer be displayed.
|
||||||
fn handleUnmap(listener: *wl.Listener(void)) void {
|
fn handleUnmap(listener: *wl.Listener(void)) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "unmap", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("unmap", listener);
|
||||||
|
|
||||||
// Remove listeners that are only active while mapped
|
// Remove listeners that are only active while mapped
|
||||||
toplevel.ack_configure.link.remove();
|
toplevel.ack_configure.link.remove();
|
||||||
toplevel.commit.link.remove();
|
|
||||||
toplevel.request_fullscreen.link.remove();
|
toplevel.request_fullscreen.link.remove();
|
||||||
toplevel.request_move.link.remove();
|
toplevel.request_move.link.remove();
|
||||||
toplevel.request_resize.link.remove();
|
toplevel.request_resize.link.remove();
|
||||||
@ -281,7 +280,7 @@ fn handleUnmap(listener: *wl.Listener(void)) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
|
fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "new_popup", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("new_popup", listener);
|
||||||
|
|
||||||
XdgPopup.create(wlr_xdg_popup, toplevel.view.popup_tree, toplevel.view.popup_tree) catch {
|
XdgPopup.create(wlr_xdg_popup, toplevel.view.popup_tree, toplevel.view.popup_tree) catch {
|
||||||
wlr_xdg_popup.resource.postNoMemory();
|
wlr_xdg_popup.resource.postNoMemory();
|
||||||
@ -293,7 +292,7 @@ fn handleAckConfigure(
|
|||||||
listener: *wl.Listener(*wlr.XdgSurface.Configure),
|
listener: *wl.Listener(*wlr.XdgSurface.Configure),
|
||||||
acked_configure: *wlr.XdgSurface.Configure,
|
acked_configure: *wlr.XdgSurface.Configure,
|
||||||
) void {
|
) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "ack_configure", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("ack_configure", listener);
|
||||||
switch (toplevel.configure_state) {
|
switch (toplevel.configure_state) {
|
||||||
.inflight => |serial| if (acked_configure.serial == serial) {
|
.inflight => |serial| if (acked_configure.serial == serial) {
|
||||||
toplevel.configure_state = .acked;
|
toplevel.configure_state = .acked;
|
||||||
@ -306,9 +305,26 @@ fn handleAckConfigure(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "commit", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("commit", listener);
|
||||||
const view = toplevel.view;
|
const view = toplevel.view;
|
||||||
|
|
||||||
|
if (toplevel.wlr_toplevel.base.initial_commit) {
|
||||||
|
_ = toplevel.wlr_toplevel.setWmCapabilities(.{ .fullscreen = true });
|
||||||
|
|
||||||
|
if (toplevel.decoration) |decoration| {
|
||||||
|
const ssd = server.config.rules.ssd.match(toplevel.view) orelse
|
||||||
|
(decoration.wlr_decoration.requested_mode != .client_side);
|
||||||
|
_ = decoration.wlr_decoration.setMode(if (ssd) .server_side else .client_side);
|
||||||
|
toplevel.view.pending.ssd = ssd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!view.mapped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const state = &toplevel.wlr_toplevel.current;
|
const state = &toplevel.wlr_toplevel.current;
|
||||||
view.constraints = .{
|
view.constraints = .{
|
||||||
@ -395,7 +411,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
|||||||
/// Called when the client asks to be fullscreened. We always honor the request
|
/// Called when the client asks to be fullscreened. We always honor the request
|
||||||
/// for now, perhaps it should be denied in some cases in the future.
|
/// for now, perhaps it should be denied in some cases in the future.
|
||||||
fn handleRequestFullscreen(listener: *wl.Listener(void)) void {
|
fn handleRequestFullscreen(listener: *wl.Listener(void)) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "request_fullscreen", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("request_fullscreen", listener);
|
||||||
if (toplevel.view.pending.fullscreen != toplevel.wlr_toplevel.requested.fullscreen) {
|
if (toplevel.view.pending.fullscreen != toplevel.wlr_toplevel.requested.fullscreen) {
|
||||||
toplevel.view.pending.fullscreen = toplevel.wlr_toplevel.requested.fullscreen;
|
toplevel.view.pending.fullscreen = toplevel.wlr_toplevel.requested.fullscreen;
|
||||||
server.root.applyPending();
|
server.root.applyPending();
|
||||||
@ -406,7 +422,7 @@ fn handleRequestMove(
|
|||||||
listener: *wl.Listener(*wlr.XdgToplevel.event.Move),
|
listener: *wl.Listener(*wlr.XdgToplevel.event.Move),
|
||||||
event: *wlr.XdgToplevel.event.Move,
|
event: *wlr.XdgToplevel.event.Move,
|
||||||
) void {
|
) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "request_move", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("request_move", listener);
|
||||||
const seat: *Seat = @ptrFromInt(event.seat.seat.data);
|
const seat: *Seat = @ptrFromInt(event.seat.seat.data);
|
||||||
const view = toplevel.view;
|
const view = toplevel.view;
|
||||||
|
|
||||||
@ -429,7 +445,7 @@ fn handleRequestMove(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), event: *wlr.XdgToplevel.event.Resize) void {
|
fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), event: *wlr.XdgToplevel.event.Resize) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "request_resize", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("request_resize", listener);
|
||||||
const seat: *Seat = @ptrFromInt(event.seat.seat.data);
|
const seat: *Seat = @ptrFromInt(event.seat.seat.data);
|
||||||
const view = toplevel.view;
|
const view = toplevel.view;
|
||||||
|
|
||||||
@ -453,12 +469,12 @@ fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), ev
|
|||||||
|
|
||||||
/// Called when the client sets / updates its title
|
/// Called when the client sets / updates its title
|
||||||
fn handleSetTitle(listener: *wl.Listener(void)) void {
|
fn handleSetTitle(listener: *wl.Listener(void)) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "set_title", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("set_title", listener);
|
||||||
toplevel.view.notifyState();
|
toplevel.view.notifyState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the client sets / updates its app_id
|
/// Called when the client sets / updates its app_id
|
||||||
fn handleSetAppId(listener: *wl.Listener(void)) void {
|
fn handleSetAppId(listener: *wl.Listener(void)) void {
|
||||||
const toplevel = @fieldParentPtr(XdgToplevel, "set_app_id", listener);
|
const toplevel: *XdgToplevel = @fieldParentPtr("set_app_id", listener);
|
||||||
toplevel.view.notifyAppId();
|
toplevel.view.notifyAppId();
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ fn handleRequestConfigure(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(void)) void {
|
fn handleDestroy(listener: *wl.Listener(void)) void {
|
||||||
const override_redirect = @fieldParentPtr(XwaylandOverrideRedirect, "destroy", listener);
|
const override_redirect: *XwaylandOverrideRedirect = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
override_redirect.request_configure.link.remove();
|
override_redirect.request_configure.link.remove();
|
||||||
override_redirect.destroy.link.remove();
|
override_redirect.destroy.link.remove();
|
||||||
@ -90,21 +90,21 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleAssociate(listener: *wl.Listener(void)) void {
|
fn handleAssociate(listener: *wl.Listener(void)) void {
|
||||||
const override_redirect = @fieldParentPtr(XwaylandOverrideRedirect, "associate", listener);
|
const override_redirect: *XwaylandOverrideRedirect = @fieldParentPtr("associate", listener);
|
||||||
|
|
||||||
override_redirect.xwayland_surface.surface.?.events.map.add(&override_redirect.map);
|
override_redirect.xwayland_surface.surface.?.events.map.add(&override_redirect.map);
|
||||||
override_redirect.xwayland_surface.surface.?.events.unmap.add(&override_redirect.unmap);
|
override_redirect.xwayland_surface.surface.?.events.unmap.add(&override_redirect.unmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleDissociate(listener: *wl.Listener(void)) void {
|
fn handleDissociate(listener: *wl.Listener(void)) void {
|
||||||
const override_redirect = @fieldParentPtr(XwaylandOverrideRedirect, "dissociate", listener);
|
const override_redirect: *XwaylandOverrideRedirect = @fieldParentPtr("dissociate", listener);
|
||||||
|
|
||||||
override_redirect.map.link.remove();
|
override_redirect.map.link.remove();
|
||||||
override_redirect.unmap.link.remove();
|
override_redirect.unmap.link.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handleMap(listener: *wl.Listener(void)) void {
|
pub fn handleMap(listener: *wl.Listener(void)) void {
|
||||||
const override_redirect = @fieldParentPtr(XwaylandOverrideRedirect, "map", listener);
|
const override_redirect: *XwaylandOverrideRedirect = @fieldParentPtr("map", listener);
|
||||||
|
|
||||||
override_redirect.mapImpl() catch {
|
override_redirect.mapImpl() catch {
|
||||||
log.err("out of memory", .{});
|
log.err("out of memory", .{});
|
||||||
@ -155,7 +155,7 @@ pub fn focusIfDesired(override_redirect: *XwaylandOverrideRedirect) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleUnmap(listener: *wl.Listener(void)) void {
|
fn handleUnmap(listener: *wl.Listener(void)) void {
|
||||||
const override_redirect = @fieldParentPtr(XwaylandOverrideRedirect, "unmap", listener);
|
const override_redirect: *XwaylandOverrideRedirect = @fieldParentPtr("unmap", listener);
|
||||||
|
|
||||||
override_redirect.set_geometry.link.remove();
|
override_redirect.set_geometry.link.remove();
|
||||||
|
|
||||||
@ -180,7 +180,7 @@ fn handleUnmap(listener: *wl.Listener(void)) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleSetGeometry(listener: *wl.Listener(void)) void {
|
fn handleSetGeometry(listener: *wl.Listener(void)) void {
|
||||||
const override_redirect = @fieldParentPtr(XwaylandOverrideRedirect, "set_geometry", listener);
|
const override_redirect: *XwaylandOverrideRedirect = @fieldParentPtr("set_geometry", listener);
|
||||||
|
|
||||||
override_redirect.surface_tree.?.node.setPosition(
|
override_redirect.surface_tree.?.node.setPosition(
|
||||||
override_redirect.xwayland_surface.x,
|
override_redirect.xwayland_surface.x,
|
||||||
@ -189,7 +189,7 @@ fn handleSetGeometry(listener: *wl.Listener(void)) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleSetOverrideRedirect(listener: *wl.Listener(void)) void {
|
fn handleSetOverrideRedirect(listener: *wl.Listener(void)) void {
|
||||||
const override_redirect = @fieldParentPtr(XwaylandOverrideRedirect, "set_override_redirect", listener);
|
const override_redirect: *XwaylandOverrideRedirect = @fieldParentPtr("set_override_redirect", listener);
|
||||||
const xwayland_surface = override_redirect.xwayland_surface;
|
const xwayland_surface = override_redirect.xwayland_surface;
|
||||||
|
|
||||||
log.debug("xwayland surface unset override redirect", .{});
|
log.debug("xwayland surface unset override redirect", .{});
|
||||||
|
@ -106,10 +106,10 @@ pub fn configure(xwayland_view: XwaylandView) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
xwayland_view.xwayland_surface.configure(
|
xwayland_view.xwayland_surface.configure(
|
||||||
@intCast(inflight.box.x + output_box.x),
|
math.lossyCast(i16, inflight.box.x + output_box.x),
|
||||||
@intCast(inflight.box.y + output_box.y),
|
math.lossyCast(i16, inflight.box.y + output_box.y),
|
||||||
@intCast(inflight.box.width),
|
math.lossyCast(u16, inflight.box.width),
|
||||||
@intCast(inflight.box.height),
|
math.lossyCast(u16, inflight.box.height),
|
||||||
);
|
);
|
||||||
|
|
||||||
xwayland_view.setActivated(inflight.focus != 0);
|
xwayland_view.setActivated(inflight.focus != 0);
|
||||||
@ -131,7 +131,7 @@ fn setActivated(xwayland_view: XwaylandView, activated: bool) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleDestroy(listener: *wl.Listener(void)) void {
|
fn handleDestroy(listener: *wl.Listener(void)) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "destroy", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("destroy", listener);
|
||||||
|
|
||||||
// Remove listeners that are active for the entire lifetime of the view
|
// Remove listeners that are active for the entire lifetime of the view
|
||||||
xwayland_view.destroy.link.remove();
|
xwayland_view.destroy.link.remove();
|
||||||
@ -146,20 +146,20 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleAssociate(listener: *wl.Listener(void)) void {
|
fn handleAssociate(listener: *wl.Listener(void)) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "associate", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("associate", listener);
|
||||||
|
|
||||||
xwayland_view.xwayland_surface.surface.?.events.map.add(&xwayland_view.map);
|
xwayland_view.xwayland_surface.surface.?.events.map.add(&xwayland_view.map);
|
||||||
xwayland_view.xwayland_surface.surface.?.events.unmap.add(&xwayland_view.unmap);
|
xwayland_view.xwayland_surface.surface.?.events.unmap.add(&xwayland_view.unmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleDissociate(listener: *wl.Listener(void)) void {
|
fn handleDissociate(listener: *wl.Listener(void)) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "dissociate", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("dissociate", listener);
|
||||||
xwayland_view.map.link.remove();
|
xwayland_view.map.link.remove();
|
||||||
xwayland_view.unmap.link.remove();
|
xwayland_view.unmap.link.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handleMap(listener: *wl.Listener(void)) void {
|
pub fn handleMap(listener: *wl.Listener(void)) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "map", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("map", listener);
|
||||||
const view = xwayland_view.view;
|
const view = xwayland_view.view;
|
||||||
|
|
||||||
const xwayland_surface = xwayland_view.xwayland_surface;
|
const xwayland_surface = xwayland_view.xwayland_surface;
|
||||||
@ -213,7 +213,7 @@ pub fn handleMap(listener: *wl.Listener(void)) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleUnmap(listener: *wl.Listener(void)) void {
|
fn handleUnmap(listener: *wl.Listener(void)) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "unmap", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("unmap", listener);
|
||||||
|
|
||||||
xwayland_view.xwayland_surface.surface.?.data = 0;
|
xwayland_view.xwayland_surface.surface.?.data = 0;
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ fn handleRequestConfigure(
|
|||||||
listener: *wl.Listener(*wlr.XwaylandSurface.event.Configure),
|
listener: *wl.Listener(*wlr.XwaylandSurface.event.Configure),
|
||||||
event: *wlr.XwaylandSurface.event.Configure,
|
event: *wlr.XwaylandSurface.event.Configure,
|
||||||
) void {
|
) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "request_configure", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("request_configure", listener);
|
||||||
|
|
||||||
// If unmapped, let the client do whatever it wants
|
// If unmapped, let the client do whatever it wants
|
||||||
if (xwayland_view.xwayland_surface.surface == null or
|
if (xwayland_view.xwayland_surface.surface == null or
|
||||||
@ -254,7 +254,7 @@ fn handleRequestConfigure(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleSetOverrideRedirect(listener: *wl.Listener(void)) void {
|
fn handleSetOverrideRedirect(listener: *wl.Listener(void)) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "set_override_redirect", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("set_override_redirect", listener);
|
||||||
const xwayland_surface = xwayland_view.xwayland_surface;
|
const xwayland_surface = xwayland_view.xwayland_surface;
|
||||||
|
|
||||||
log.debug("xwayland surface set override redirect", .{});
|
log.debug("xwayland surface set override redirect", .{});
|
||||||
@ -276,17 +276,17 @@ fn handleSetOverrideRedirect(listener: *wl.Listener(void)) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleSetTitle(listener: *wl.Listener(void)) void {
|
fn handleSetTitle(listener: *wl.Listener(void)) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "set_title", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("set_title", listener);
|
||||||
xwayland_view.view.notifyState();
|
xwayland_view.view.notifyState();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleSetClass(listener: *wl.Listener(void)) void {
|
fn handleSetClass(listener: *wl.Listener(void)) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "set_class", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("set_class", listener);
|
||||||
xwayland_view.view.notifyAppId();
|
xwayland_view.view.notifyAppId();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleSetDecorations(listener: *wl.Listener(void)) void {
|
fn handleSetDecorations(listener: *wl.Listener(void)) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "set_decorations", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("set_decorations", listener);
|
||||||
const view = xwayland_view.view;
|
const view = xwayland_view.view;
|
||||||
|
|
||||||
const ssd = server.config.rules.ssd.match(view) orelse
|
const ssd = server.config.rules.ssd.match(view) orelse
|
||||||
@ -299,7 +299,7 @@ fn handleSetDecorations(listener: *wl.Listener(void)) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleRequestFullscreen(listener: *wl.Listener(void)) void {
|
fn handleRequestFullscreen(listener: *wl.Listener(void)) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "request_fullscreen", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("request_fullscreen", listener);
|
||||||
if (xwayland_view.view.pending.fullscreen != xwayland_view.xwayland_surface.fullscreen) {
|
if (xwayland_view.view.pending.fullscreen != xwayland_view.xwayland_surface.fullscreen) {
|
||||||
xwayland_view.view.pending.fullscreen = xwayland_view.xwayland_surface.fullscreen;
|
xwayland_view.view.pending.fullscreen = xwayland_view.xwayland_surface.fullscreen;
|
||||||
server.root.applyPending();
|
server.root.applyPending();
|
||||||
@ -314,6 +314,6 @@ fn handleRequestMinimize(
|
|||||||
listener: *wl.Listener(*wlr.XwaylandSurface.event.Minimize),
|
listener: *wl.Listener(*wlr.XwaylandSurface.event.Minimize),
|
||||||
event: *wlr.XwaylandSurface.event.Minimize,
|
event: *wlr.XwaylandSurface.event.Minimize,
|
||||||
) void {
|
) void {
|
||||||
const xwayland_view = @fieldParentPtr(XwaylandView, "request_minimize", listener);
|
const xwayland_view: *XwaylandView = @fieldParentPtr("request_minimize", listener);
|
||||||
xwayland_view.xwayland_surface.setMinimized(event.minimize);
|
xwayland_view.xwayland_surface.setMinimized(event.minimize);
|
||||||
}
|
}
|
||||||
|
@ -36,10 +36,12 @@ pub const Orientation = enum {
|
|||||||
vertical,
|
vertical,
|
||||||
};
|
};
|
||||||
|
|
||||||
// zig fmt: off
|
const command_impls = std.StaticStringMap(
|
||||||
const command_impls = std.ComptimeStringMap(
|
|
||||||
*const fn (*Seat, []const [:0]const u8, *?[]const u8) Error!void,
|
*const fn (*Seat, []const [:0]const u8, *?[]const u8) Error!void,
|
||||||
|
).initComptime(
|
||||||
.{
|
.{
|
||||||
|
// zig fmt: off
|
||||||
|
.{ "allow-tearing", @import("command/config.zig").allowTearing },
|
||||||
.{ "attach-mode", @import("command/attach_mode.zig").defaultAttachMode },
|
.{ "attach-mode", @import("command/attach_mode.zig").defaultAttachMode },
|
||||||
.{ "background-color", @import("command/config.zig").backgroundColor },
|
.{ "background-color", @import("command/config.zig").backgroundColor },
|
||||||
.{ "border-color-focused", @import("command/config.zig").borderColorFocused },
|
.{ "border-color-focused", @import("command/config.zig").borderColorFocused },
|
||||||
@ -96,9 +98,9 @@ const command_impls = std.ComptimeStringMap(
|
|||||||
.{ "unmap-switch", @import("command/map.zig").unmapSwitch },
|
.{ "unmap-switch", @import("command/map.zig").unmapSwitch },
|
||||||
.{ "xcursor-theme", @import("command/xcursor_theme.zig").xcursorTheme },
|
.{ "xcursor-theme", @import("command/xcursor_theme.zig").xcursorTheme },
|
||||||
.{ "zoom", @import("command/zoom.zig").zoom },
|
.{ "zoom", @import("command/zoom.zig").zoom },
|
||||||
|
// zig fmt: on
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// zig fmt: on
|
|
||||||
|
|
||||||
pub const Error = error{
|
pub const Error = error{
|
||||||
NoCommand,
|
NoCommand,
|
||||||
|
@ -24,6 +24,20 @@ const Error = @import("../command.zig").Error;
|
|||||||
const Seat = @import("../Seat.zig");
|
const Seat = @import("../Seat.zig");
|
||||||
const Config = @import("../Config.zig");
|
const Config = @import("../Config.zig");
|
||||||
|
|
||||||
|
pub fn allowTearing(
|
||||||
|
_: *Seat,
|
||||||
|
args: []const [:0]const u8,
|
||||||
|
_: *?[]const u8,
|
||||||
|
) Error!void {
|
||||||
|
if (args.len < 2) return Error.NotEnoughArguments;
|
||||||
|
if (args.len > 2) return Error.TooManyArguments;
|
||||||
|
|
||||||
|
const arg = std.meta.stringToEnum(enum { enabled, disabled }, args[1]) orelse
|
||||||
|
return Error.UnknownOption;
|
||||||
|
|
||||||
|
server.config.allow_tearing = arg == .enabled;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn borderWidth(
|
pub fn borderWidth(
|
||||||
_: *Seat,
|
_: *Seat,
|
||||||
args: []const [:0]const u8,
|
args: []const [:0]const u8,
|
||||||
|
@ -24,77 +24,13 @@ const util = @import("../util.zig");
|
|||||||
|
|
||||||
const Error = @import("../command.zig").Error;
|
const Error = @import("../command.zig").Error;
|
||||||
const Seat = @import("../Seat.zig");
|
const Seat = @import("../Seat.zig");
|
||||||
const KeyboardGroup = @import("../KeyboardGroup.zig");
|
|
||||||
|
|
||||||
pub fn keyboardGroupCreate(
|
pub const keyboardGroupCreate = keyboardGroupDeprecated;
|
||||||
seat: *Seat,
|
pub const keyboardGroupDestroy = keyboardGroupDeprecated;
|
||||||
args: []const [:0]const u8,
|
pub const keyboardGroupAdd = keyboardGroupDeprecated;
|
||||||
out: *?[]const u8,
|
pub const keyboardGroupRemove = keyboardGroupDeprecated;
|
||||||
) Error!void {
|
|
||||||
if (args.len < 2) return Error.NotEnoughArguments;
|
|
||||||
if (args.len > 2) return Error.TooManyArguments;
|
|
||||||
|
|
||||||
if (keyboardGroupFromName(seat, args[1]) != null) {
|
fn keyboardGroupDeprecated(_: *Seat, _: []const [:0]const u8, out: *?[]const u8) Error!void {
|
||||||
const msg = try util.gpa.dupe(u8, "error: failed to create keybaord group: group of same name already exists\n");
|
out.* = try util.gpa.dupe(u8, "warning: explicit keyboard groups are deprecated, " ++
|
||||||
out.* = msg;
|
"all keyboards are now automatically added to a single group\n");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try KeyboardGroup.create(seat, args[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn keyboardGroupDestroy(
|
|
||||||
seat: *Seat,
|
|
||||||
args: []const [:0]const u8,
|
|
||||||
out: *?[]const u8,
|
|
||||||
) Error!void {
|
|
||||||
if (args.len < 2) return Error.NotEnoughArguments;
|
|
||||||
if (args.len > 2) return Error.TooManyArguments;
|
|
||||||
const group = keyboardGroupFromName(seat, args[1]) orelse {
|
|
||||||
const msg = try util.gpa.dupe(u8, "error: no keyboard group with that name exists\n");
|
|
||||||
out.* = msg;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
group.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn keyboardGroupAdd(
|
|
||||||
seat: *Seat,
|
|
||||||
args: []const [:0]const u8,
|
|
||||||
out: *?[]const u8,
|
|
||||||
) Error!void {
|
|
||||||
if (args.len < 3) return Error.NotEnoughArguments;
|
|
||||||
if (args.len > 3) return Error.TooManyArguments;
|
|
||||||
|
|
||||||
const group = keyboardGroupFromName(seat, args[1]) orelse {
|
|
||||||
const msg = try util.gpa.dupe(u8, "error: no keyboard group with that name exists\n");
|
|
||||||
out.* = msg;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
try globber.validate(args[2]);
|
|
||||||
try group.addIdentifier(args[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn keyboardGroupRemove(
|
|
||||||
seat: *Seat,
|
|
||||||
args: []const [:0]const u8,
|
|
||||||
out: *?[]const u8,
|
|
||||||
) Error!void {
|
|
||||||
if (args.len < 3) return Error.NotEnoughArguments;
|
|
||||||
if (args.len > 3) return Error.TooManyArguments;
|
|
||||||
|
|
||||||
const group = keyboardGroupFromName(seat, args[1]) orelse {
|
|
||||||
const msg = try util.gpa.dupe(u8, "error: no keyboard group with that name exists\n");
|
|
||||||
out.* = msg;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
try group.removeIdentifier(args[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keyboardGroupFromName(seat: *Seat, name: []const u8) ?*KeyboardGroup {
|
|
||||||
var it = seat.keyboard_groups.first;
|
|
||||||
while (it) |node| : (it = node.next) {
|
|
||||||
if (mem.eql(u8, node.data.name, name)) return &node.data;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ fn getOutput(seat: *Seat, str: []const u8) !?*Output {
|
|||||||
.previous => link.prev.?,
|
.previous => link.prev.?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return @fieldParentPtr(Output, "active_link", link);
|
return @as(*Output, @fieldParentPtr("active_link", link));
|
||||||
} else if (std.meta.stringToEnum(wlr.OutputLayout.Direction, str)) |direction| { // Spacial direction
|
} else if (std.meta.stringToEnum(wlr.OutputLayout.Direction, str)) |direction| { // Spacial direction
|
||||||
var focus_box: wlr.Box = undefined;
|
var focus_box: wlr.Box = undefined;
|
||||||
server.root.output_layout.getBox(seat.focused_output.?.wlr_output, &focus_box);
|
server.root.output_layout.getBox(seat.focused_output.?.wlr_output, &focus_box);
|
||||||
|
@ -26,6 +26,7 @@ const util = @import("../util.zig");
|
|||||||
const Error = @import("../command.zig").Error;
|
const Error = @import("../command.zig").Error;
|
||||||
const Seat = @import("../Seat.zig");
|
const Seat = @import("../Seat.zig");
|
||||||
const View = @import("../View.zig");
|
const View = @import("../View.zig");
|
||||||
|
const Anchor = @import("../Config.zig").Anchor;
|
||||||
|
|
||||||
const Action = enum {
|
const Action = enum {
|
||||||
float,
|
float,
|
||||||
@ -35,9 +36,14 @@ const Action = enum {
|
|||||||
tags,
|
tags,
|
||||||
output,
|
output,
|
||||||
position,
|
position,
|
||||||
|
@"relative-position",
|
||||||
dimensions,
|
dimensions,
|
||||||
fullscreen,
|
fullscreen,
|
||||||
@"no-fullscreen",
|
@"no-fullscreen",
|
||||||
|
tearing,
|
||||||
|
@"no-tearing",
|
||||||
|
warp,
|
||||||
|
@"no-warp",
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
|
pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
|
||||||
@ -53,9 +59,10 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
|
|||||||
const action = std.meta.stringToEnum(Action, result.args[0]) orelse return Error.UnknownOption;
|
const action = std.meta.stringToEnum(Action, result.args[0]) orelse return Error.UnknownOption;
|
||||||
|
|
||||||
const positional_arguments_count: u8 = switch (action) {
|
const positional_arguments_count: u8 = switch (action) {
|
||||||
.float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen" => 1,
|
.float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen", .tearing, .@"no-tearing", .warp, .@"no-warp" => 1,
|
||||||
.tags, .output => 2,
|
.tags, .output => 2,
|
||||||
.position, .dimensions => 3,
|
.position, .dimensions => 3,
|
||||||
|
.@"relative-position" => 4,
|
||||||
};
|
};
|
||||||
if (result.args.len > positional_arguments_count) return Error.TooManyArguments;
|
if (result.args.len > positional_arguments_count) return Error.TooManyArguments;
|
||||||
if (result.args.len < positional_arguments_count) return Error.NotEnoughArguments;
|
if (result.args.len < positional_arguments_count) return Error.NotEnoughArguments;
|
||||||
@ -83,6 +90,14 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
|
|||||||
apply_ssd_rules();
|
apply_ssd_rules();
|
||||||
server.root.applyPending();
|
server.root.applyPending();
|
||||||
},
|
},
|
||||||
|
.tearing, .@"no-tearing" => {
|
||||||
|
try server.config.rules.tearing.add(.{
|
||||||
|
.app_id_glob = app_id_glob,
|
||||||
|
.title_glob = title_glob,
|
||||||
|
.value = (action == .tearing),
|
||||||
|
});
|
||||||
|
apply_tearing_rules();
|
||||||
|
},
|
||||||
.tags => {
|
.tags => {
|
||||||
const tags = try fmt.parseInt(u32, result.args[1], 10);
|
const tags = try fmt.parseInt(u32, result.args[1], 10);
|
||||||
try server.config.rules.tags.add(.{
|
try server.config.rules.tags.add(.{
|
||||||
@ -101,14 +116,32 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
.position => {
|
.position => {
|
||||||
const x = try fmt.parseInt(u31, result.args[1], 10);
|
const x = try fmt.parseInt(i31, result.args[1], 10);
|
||||||
const y = try fmt.parseInt(u31, result.args[2], 10);
|
const y = try fmt.parseInt(i31, result.args[2], 10);
|
||||||
|
if (x < 0 or y < 0) return Error.OutOfBounds;
|
||||||
try server.config.rules.position.add(.{
|
try server.config.rules.position.add(.{
|
||||||
.app_id_glob = app_id_glob,
|
.app_id_glob = app_id_glob,
|
||||||
.title_glob = title_glob,
|
.title_glob = title_glob,
|
||||||
.value = .{
|
.value = .{
|
||||||
.x = x,
|
.anchor = .absolute,
|
||||||
.y = y,
|
.x = @intCast(x),
|
||||||
|
.y = @intCast(y),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
.@"relative-position" => {
|
||||||
|
const anchor = std.meta.stringToEnum(Anchor, result.args[1]) orelse return Error.UnknownOption;
|
||||||
|
// force the use of the normal position command for absolute positions
|
||||||
|
if (anchor == .absolute) return Error.UnknownOption;
|
||||||
|
const x_off = try fmt.parseInt(i31, result.args[2], 10);
|
||||||
|
const y_off = try fmt.parseInt(i31, result.args[3], 10);
|
||||||
|
try server.config.rules.position.add(.{
|
||||||
|
.app_id_glob = app_id_glob,
|
||||||
|
.title_glob = title_glob,
|
||||||
|
.value = .{
|
||||||
|
.anchor = anchor,
|
||||||
|
.x = x_off,
|
||||||
|
.y = y_off,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -131,6 +164,13 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
|
|||||||
.value = (action == .fullscreen),
|
.value = (action == .fullscreen),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
.warp, .@"no-warp" => {
|
||||||
|
try server.config.rules.warp.add(.{
|
||||||
|
.app_id_glob = app_id_glob,
|
||||||
|
.title_glob = title_glob,
|
||||||
|
.value = (action == .warp),
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +208,7 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
|
|||||||
util.gpa.free(output_rule);
|
util.gpa.free(output_rule);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.position => {
|
.position, .@"relative-position" => {
|
||||||
_ = server.config.rules.position.del(rule);
|
_ = server.config.rules.position.del(rule);
|
||||||
},
|
},
|
||||||
.dimensions => {
|
.dimensions => {
|
||||||
@ -177,6 +217,13 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
|
|||||||
.fullscreen, .@"no-fullscreen" => {
|
.fullscreen, .@"no-fullscreen" => {
|
||||||
_ = server.config.rules.fullscreen.del(rule);
|
_ = server.config.rules.fullscreen.del(rule);
|
||||||
},
|
},
|
||||||
|
.tearing, .@"no-tearing" => {
|
||||||
|
_ = server.config.rules.tearing.del(rule);
|
||||||
|
apply_tearing_rules();
|
||||||
|
},
|
||||||
|
.warp, .@"no-warp" => {
|
||||||
|
_ = server.config.rules.warp.del(rule);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,6 +238,17 @@ fn apply_ssd_rules() void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_tearing_rules() void {
|
||||||
|
var it = server.root.views.iterator(.forward);
|
||||||
|
while (it.next()) |view| {
|
||||||
|
if (view.destroying) continue;
|
||||||
|
|
||||||
|
if (server.config.rules.tearing.match(view)) |tearing| {
|
||||||
|
view.tearing_mode = if (tearing) .tearing else .no_tearing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!void {
|
pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!void {
|
||||||
if (args.len < 2) return error.NotEnoughArguments;
|
if (args.len < 2) return error.NotEnoughArguments;
|
||||||
if (args.len > 2) return error.TooManyArguments;
|
if (args.len > 2) return error.TooManyArguments;
|
||||||
@ -203,6 +261,8 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
|
|||||||
position,
|
position,
|
||||||
dimensions,
|
dimensions,
|
||||||
fullscreen,
|
fullscreen,
|
||||||
|
tearing,
|
||||||
|
warp,
|
||||||
}, args[1]) orelse return Error.UnknownOption;
|
}, args[1]) orelse return Error.UnknownOption;
|
||||||
const max_glob_len = switch (rule_list) {
|
const max_glob_len = switch (rule_list) {
|
||||||
inline else => |list| @field(server.config.rules, @tagName(list)).getMaxGlobLen(),
|
inline else => |list| @field(server.config.rules, @tagName(list)).getMaxGlobLen(),
|
||||||
@ -218,12 +278,14 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
|
|||||||
try writer.writeAll("action\n");
|
try writer.writeAll("action\n");
|
||||||
|
|
||||||
switch (rule_list) {
|
switch (rule_list) {
|
||||||
inline .float, .ssd, .output, .fullscreen => |list| {
|
inline .float, .ssd, .output, .fullscreen, .tearing, .warp => |list| {
|
||||||
const rules = switch (list) {
|
const rules = switch (list) {
|
||||||
.float => server.config.rules.float.rules.items,
|
.float => server.config.rules.float.rules.items,
|
||||||
.ssd => server.config.rules.ssd.rules.items,
|
.ssd => server.config.rules.ssd.rules.items,
|
||||||
.output => server.config.rules.output.rules.items,
|
.output => server.config.rules.output.rules.items,
|
||||||
.fullscreen => server.config.rules.fullscreen.rules.items,
|
.fullscreen => server.config.rules.fullscreen.rules.items,
|
||||||
|
.tearing => server.config.rules.tearing.rules.items,
|
||||||
|
.warp => server.config.rules.warp.rules.items,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
for (rules) |rule| {
|
for (rules) |rule| {
|
||||||
@ -234,6 +296,8 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
|
|||||||
.ssd => if (rule.value) "ssd" else "csd",
|
.ssd => if (rule.value) "ssd" else "csd",
|
||||||
.output => rule.value,
|
.output => rule.value,
|
||||||
.fullscreen => if (rule.value) "fullscreen" else "no-fullscreen",
|
.fullscreen => if (rule.value) "fullscreen" else "no-fullscreen",
|
||||||
|
.tearing => if (rule.value) "tearing" else "no-tearing",
|
||||||
|
.warp => if (rule.value) "warp" else "no-warp",
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
@ -249,7 +313,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
|
|||||||
for (server.config.rules.position.rules.items) |rule| {
|
for (server.config.rules.position.rules.items) |rule| {
|
||||||
try fmt.formatBuf(rule.title_glob, .{ .width = title_column_max, .alignment = .left }, writer);
|
try fmt.formatBuf(rule.title_glob, .{ .width = title_column_max, .alignment = .left }, writer);
|
||||||
try fmt.formatBuf(rule.app_id_glob, .{ .width = app_id_column_max, .alignment = .left }, writer);
|
try fmt.formatBuf(rule.app_id_glob, .{ .width = app_id_column_max, .alignment = .left }, writer);
|
||||||
try writer.print("{d},{d}\n", .{ rule.value.x, rule.value.y });
|
try writer.print("{s},{d},{d}", .{ @tagName(rule.value.anchor), rule.value.x, rule.value.y });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.dimensions => {
|
.dimensions => {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const os = std.os;
|
const posix = std.posix;
|
||||||
|
|
||||||
const c = @import("../c.zig");
|
const c = @import("../c.zig");
|
||||||
const util = @import("../util.zig");
|
const util = @import("../util.zig");
|
||||||
@ -35,23 +35,26 @@ pub fn spawn(
|
|||||||
|
|
||||||
const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", args[1], null };
|
const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", args[1], null };
|
||||||
|
|
||||||
const pid = os.fork() catch {
|
const pid = posix.fork() catch {
|
||||||
out.* = try std.fmt.allocPrint(util.gpa, "fork/execve failed", .{});
|
out.* = try std.fmt.allocPrint(util.gpa, "fork/execve failed", .{});
|
||||||
return Error.Other;
|
return Error.Other;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
process.cleanupChild();
|
process.cleanupChild();
|
||||||
const pid2 = os.fork() catch c._exit(1);
|
|
||||||
if (pid2 == 0) os.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1);
|
const pid2 = posix.fork() catch c._exit(1);
|
||||||
|
if (pid2 == 0) {
|
||||||
|
posix.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
c._exit(0);
|
c._exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait the intermediate child.
|
// Wait the intermediate child.
|
||||||
const ret = os.waitpid(pid, 0);
|
const ret = posix.waitpid(pid, 0);
|
||||||
if (!os.W.IFEXITED(ret.status) or
|
if (!posix.W.IFEXITED(ret.status) or
|
||||||
(os.W.IFEXITED(ret.status) and os.W.EXITSTATUS(ret.status) != 0))
|
(posix.W.IFEXITED(ret.status) and posix.W.EXITSTATUS(ret.status) != 0))
|
||||||
{
|
{
|
||||||
out.* = try std.fmt.allocPrint(util.gpa, "fork/execve failed", .{});
|
out.* = try std.fmt.allocPrint(util.gpa, "fork/execve failed", .{});
|
||||||
return Error.Other;
|
return Error.Other;
|
||||||
|
@ -20,7 +20,7 @@ const mem = std.mem;
|
|||||||
const fs = std.fs;
|
const fs = std.fs;
|
||||||
const io = std.io;
|
const io = std.io;
|
||||||
const log = std.log;
|
const log = std.log;
|
||||||
const os = std.os;
|
const posix = std.posix;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const wlr = @import("wlroots");
|
const wlr = @import("wlroots");
|
||||||
const flags = @import("flags");
|
const flags = @import("flags");
|
||||||
@ -31,12 +31,6 @@ const process = @import("process.zig");
|
|||||||
|
|
||||||
const Server = @import("Server.zig");
|
const Server = @import("Server.zig");
|
||||||
|
|
||||||
comptime {
|
|
||||||
if (wlr.version.major != 0 or wlr.version.minor != 17 or wlr.version.micro < 2) {
|
|
||||||
@compileError("river requires at least wlroots version 0.17.2 due to bugs in wlroots 0.17.0/0.17.1");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const usage: []const u8 =
|
const usage: []const u8 =
|
||||||
\\usage: river [options]
|
\\usage: river [options]
|
||||||
\\
|
\\
|
||||||
@ -57,23 +51,23 @@ pub fn main() anyerror!void {
|
|||||||
.{ .name = "c", .kind = .arg },
|
.{ .name = "c", .kind = .arg },
|
||||||
.{ .name = "log-level", .kind = .arg },
|
.{ .name = "log-level", .kind = .arg },
|
||||||
.{ .name = "no-xwayland", .kind = .boolean },
|
.{ .name = "no-xwayland", .kind = .boolean },
|
||||||
}).parse(os.argv[1..]) catch {
|
}).parse(std.os.argv[1..]) catch {
|
||||||
try io.getStdErr().writeAll(usage);
|
try io.getStdErr().writeAll(usage);
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
};
|
};
|
||||||
if (result.flags.h) {
|
if (result.flags.h) {
|
||||||
try io.getStdOut().writeAll(usage);
|
try io.getStdOut().writeAll(usage);
|
||||||
os.exit(0);
|
posix.exit(0);
|
||||||
}
|
}
|
||||||
if (result.args.len != 0) {
|
if (result.args.len != 0) {
|
||||||
log.err("unknown option '{s}'", .{result.args[0]});
|
log.err("unknown option '{s}'", .{result.args[0]});
|
||||||
try io.getStdErr().writeAll(usage);
|
try io.getStdErr().writeAll(usage);
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.flags.version) {
|
if (result.flags.version) {
|
||||||
try io.getStdOut().writeAll(build_options.version ++ "\n");
|
try io.getStdOut().writeAll(build_options.version ++ "\n");
|
||||||
os.exit(0);
|
posix.exit(0);
|
||||||
}
|
}
|
||||||
if (result.flags.@"log-level") |level| {
|
if (result.flags.@"log-level") |level| {
|
||||||
if (mem.eql(u8, level, "error")) {
|
if (mem.eql(u8, level, "error")) {
|
||||||
@ -87,10 +81,10 @@ pub fn main() anyerror!void {
|
|||||||
} else {
|
} else {
|
||||||
log.err("invalid log level '{s}'", .{level});
|
log.err("invalid log level '{s}'", .{level});
|
||||||
try io.getStdErr().writeAll(usage);
|
try io.getStdErr().writeAll(usage);
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const enable_xwayland = !result.flags.@"no-xwayland";
|
const runtime_xwayland = !result.flags.@"no-xwayland";
|
||||||
const startup_command = blk: {
|
const startup_command = blk: {
|
||||||
if (result.flags.c) |command| {
|
if (result.flags.c) |command| {
|
||||||
break :blk try util.gpa.dupeZ(u8, command);
|
break :blk try util.gpa.dupeZ(u8, command);
|
||||||
@ -101,17 +95,25 @@ pub fn main() anyerror!void {
|
|||||||
|
|
||||||
log.info("river version {s}, initializing server", .{build_options.version});
|
log.info("river version {s}, initializing server", .{build_options.version});
|
||||||
|
|
||||||
process.setup();
|
|
||||||
|
|
||||||
river_init_wlroots_log(switch (runtime_log_level) {
|
river_init_wlroots_log(switch (runtime_log_level) {
|
||||||
.debug => .debug,
|
.debug => .debug,
|
||||||
.info => .info,
|
.info => .info,
|
||||||
.warn, .err => .err,
|
.warn, .err => .err,
|
||||||
});
|
});
|
||||||
|
|
||||||
try server.init(enable_xwayland);
|
try server.init(runtime_xwayland);
|
||||||
defer server.deinit();
|
defer server.deinit();
|
||||||
|
|
||||||
|
// wlroots starts the Xwayland process from an idle event source, the reasoning being that
|
||||||
|
// this gives the compositor time to set up event listeners before Xwayland is actually
|
||||||
|
// started. We want Xwayland to be started by wlroots before we modify our rlimits in
|
||||||
|
// process.setup() since wlroots does not offer a way for us to reset the rlimit post-fork.
|
||||||
|
if (build_options.xwayland and runtime_xwayland) {
|
||||||
|
server.wl_server.getEventLoop().dispatchIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
process.setup();
|
||||||
|
|
||||||
try server.start();
|
try server.start();
|
||||||
|
|
||||||
// Run the child in a new process group so that we can send SIGTERM to all
|
// Run the child in a new process group so that we can send SIGTERM to all
|
||||||
@ -119,16 +121,16 @@ pub fn main() anyerror!void {
|
|||||||
const child_pgid = if (startup_command) |cmd| blk: {
|
const child_pgid = if (startup_command) |cmd| blk: {
|
||||||
log.info("running init executable '{s}'", .{cmd});
|
log.info("running init executable '{s}'", .{cmd});
|
||||||
const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd, null };
|
const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd, null };
|
||||||
const pid = try os.fork();
|
const pid = try posix.fork();
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
process.cleanupChild();
|
process.cleanupChild();
|
||||||
os.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1);
|
posix.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1);
|
||||||
}
|
}
|
||||||
util.gpa.free(cmd);
|
util.gpa.free(cmd);
|
||||||
// Since the child has called setsid, the pid is the pgid
|
// Since the child has called setsid, the pid is the pgid
|
||||||
break :blk pid;
|
break :blk pid;
|
||||||
} else null;
|
} else null;
|
||||||
defer if (child_pgid) |pgid| os.kill(-pgid, os.SIG.TERM) catch |err| {
|
defer if (child_pgid) |pgid| posix.kill(-pgid, posix.SIG.TERM) catch |err| {
|
||||||
log.err("failed to kill init process group: {s}", .{@errorName(err)});
|
log.err("failed to kill init process group: {s}", .{@errorName(err)});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -141,20 +143,20 @@ pub fn main() anyerror!void {
|
|||||||
|
|
||||||
fn defaultInitPath() !?[:0]const u8 {
|
fn defaultInitPath() !?[:0]const u8 {
|
||||||
const path = blk: {
|
const path = blk: {
|
||||||
if (os.getenv("XDG_CONFIG_HOME")) |xdg_config_home| {
|
if (posix.getenv("XDG_CONFIG_HOME")) |xdg_config_home| {
|
||||||
break :blk try fs.path.joinZ(util.gpa, &[_][]const u8{ xdg_config_home, "river/init" });
|
break :blk try fs.path.joinZ(util.gpa, &[_][]const u8{ xdg_config_home, "river/init" });
|
||||||
} else if (os.getenv("HOME")) |home| {
|
} else if (posix.getenv("HOME")) |home| {
|
||||||
break :blk try fs.path.joinZ(util.gpa, &[_][]const u8{ home, ".config/river/init" });
|
break :blk try fs.path.joinZ(util.gpa, &[_][]const u8{ home, ".config/river/init" });
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
os.accessZ(path, os.X_OK) catch |err| {
|
posix.accessZ(path, posix.X_OK) catch |err| {
|
||||||
if (err == error.PermissionDenied) {
|
if (err == error.PermissionDenied) {
|
||||||
if (os.accessZ(path, os.R_OK)) {
|
if (posix.accessZ(path, posix.R_OK)) {
|
||||||
log.err("failed to run init executable {s}: the file is not executable", .{path});
|
log.err("failed to run init executable {s}: the file is not executable", .{path});
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
}
|
}
|
||||||
log.err("failed to run init executable {s}: {s}", .{ path, @errorName(err) });
|
log.err("failed to run init executable {s}: {s}", .{ path, @errorName(err) });
|
||||||
@ -171,25 +173,26 @@ var runtime_log_level: log.Level = switch (builtin.mode) {
|
|||||||
.ReleaseSafe, .ReleaseFast, .ReleaseSmall => .info,
|
.ReleaseSafe, .ReleaseFast, .ReleaseSmall => .info,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const std_options = struct {
|
pub const std_options: std.Options = .{
|
||||||
/// Tell std.log to leave all log level filtering to us.
|
// Tell std.log to leave all log level filtering to us.
|
||||||
pub const log_level: log.Level = .debug;
|
.log_level = .debug,
|
||||||
|
.logFn = logFn,
|
||||||
pub fn logFn(
|
|
||||||
comptime level: log.Level,
|
|
||||||
comptime scope: @TypeOf(.EnumLiteral),
|
|
||||||
comptime format: []const u8,
|
|
||||||
args: anytype,
|
|
||||||
) void {
|
|
||||||
if (@intFromEnum(level) > @intFromEnum(runtime_log_level)) return;
|
|
||||||
|
|
||||||
const scope_prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
|
|
||||||
|
|
||||||
const stderr = io.getStdErr().writer();
|
|
||||||
stderr.print(level.asText() ++ scope_prefix ++ format ++ "\n", args) catch {};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn logFn(
|
||||||
|
comptime level: log.Level,
|
||||||
|
comptime scope: @TypeOf(.EnumLiteral),
|
||||||
|
comptime format: []const u8,
|
||||||
|
args: anytype,
|
||||||
|
) void {
|
||||||
|
if (@intFromEnum(level) > @intFromEnum(runtime_log_level)) return;
|
||||||
|
|
||||||
|
const scope_prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
|
||||||
|
|
||||||
|
const stderr = io.getStdErr().writer();
|
||||||
|
stderr.print(level.asText() ++ scope_prefix ++ format ++ "\n", args) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
/// See wlroots_log_wrapper.c
|
/// See wlroots_log_wrapper.c
|
||||||
extern fn river_init_wlroots_log(importance: wlr.log.Importance) void;
|
extern fn river_init_wlroots_log(importance: wlr.log.Importance) void;
|
||||||
export fn river_wlroots_log_callback(importance: wlr.log.Importance, ptr: [*:0]const u8, len: usize) void {
|
export fn river_wlroots_log_callback(importance: wlr.log.Importance, ptr: [*:0]const u8, len: usize) void {
|
||||||
|
@ -15,21 +15,21 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const os = std.os;
|
const posix = std.posix;
|
||||||
|
|
||||||
const c = @import("c.zig");
|
const c = @import("c.zig");
|
||||||
|
|
||||||
var original_rlimit: ?os.rlimit = null;
|
var original_rlimit: ?posix.rlimit = null;
|
||||||
|
|
||||||
pub fn setup() void {
|
pub fn setup() void {
|
||||||
// Ignore SIGPIPE so we don't get killed when writing to a socket that
|
// Ignore SIGPIPE so we don't get killed when writing to a socket that
|
||||||
// has had its read end closed by another process.
|
// has had its read end closed by another process.
|
||||||
const sig_ign = os.Sigaction{
|
const sig_ign = posix.Sigaction{
|
||||||
.handler = .{ .handler = os.SIG.IGN },
|
.handler = .{ .handler = posix.SIG.IGN },
|
||||||
.mask = os.empty_sigset,
|
.mask = posix.empty_sigset,
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
};
|
};
|
||||||
os.sigaction(os.SIG.PIPE, &sig_ign, null) catch unreachable;
|
posix.sigaction(posix.SIG.PIPE, &sig_ign, null) catch unreachable;
|
||||||
|
|
||||||
// Most unix systems have a default limit of 1024 file descriptors and it
|
// Most unix systems have a default limit of 1024 file descriptors and it
|
||||||
// seems unlikely for this default to be universally raised due to the
|
// seems unlikely for this default to be universally raised due to the
|
||||||
@ -41,13 +41,13 @@ pub fn setup() void {
|
|||||||
// to catch any fd leaks. Therefore, don't use some crazy high limit that
|
// to catch any fd leaks. Therefore, don't use some crazy high limit that
|
||||||
// can never be reached before the system runs out of memory. This can be
|
// can never be reached before the system runs out of memory. This can be
|
||||||
// raised further if anyone reaches it in practice.
|
// raised further if anyone reaches it in practice.
|
||||||
if (os.getrlimit(.NOFILE)) |original| {
|
if (posix.getrlimit(.NOFILE)) |original| {
|
||||||
original_rlimit = original;
|
original_rlimit = original;
|
||||||
const new: os.rlimit = .{
|
const new: posix.rlimit = .{
|
||||||
.cur = @min(4096, original.max),
|
.cur = @min(4096, original.max),
|
||||||
.max = original.max,
|
.max = original.max,
|
||||||
};
|
};
|
||||||
if (os.setrlimit(.NOFILE, new)) {
|
if (posix.setrlimit(.NOFILE, new)) {
|
||||||
std.log.info("raised file descriptor limit of the river process to {d}", .{new.cur});
|
std.log.info("raised file descriptor limit of the river process to {d}", .{new.cur});
|
||||||
} else |_| {
|
} else |_| {
|
||||||
std.log.err("setrlimit failed, using system default file descriptor limit of {d}", .{
|
std.log.err("setrlimit failed, using system default file descriptor limit of {d}", .{
|
||||||
@ -61,17 +61,17 @@ pub fn setup() void {
|
|||||||
|
|
||||||
pub fn cleanupChild() void {
|
pub fn cleanupChild() void {
|
||||||
if (c.setsid() < 0) unreachable;
|
if (c.setsid() < 0) unreachable;
|
||||||
if (os.system.sigprocmask(os.SIG.SETMASK, &os.empty_sigset, null) < 0) unreachable;
|
if (posix.system.sigprocmask(posix.SIG.SETMASK, &posix.empty_sigset, null) < 0) unreachable;
|
||||||
|
|
||||||
const sig_dfl = os.Sigaction{
|
const sig_dfl = posix.Sigaction{
|
||||||
.handler = .{ .handler = os.SIG.DFL },
|
.handler = .{ .handler = posix.SIG.DFL },
|
||||||
.mask = os.empty_sigset,
|
.mask = posix.empty_sigset,
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
};
|
};
|
||||||
os.sigaction(os.SIG.PIPE, &sig_dfl, null) catch unreachable;
|
posix.sigaction(posix.SIG.PIPE, &sig_dfl, null) catch unreachable;
|
||||||
|
|
||||||
if (original_rlimit) |original| {
|
if (original_rlimit) |original| {
|
||||||
os.setrlimit(.NOFILE, original) catch {
|
posix.setrlimit(.NOFILE, original) catch {
|
||||||
std.log.err("failed to restore original file descriptor limit for " ++
|
std.log.err("failed to restore original file descriptor limit for " ++
|
||||||
"child process, setrlimit failed", .{});
|
"child process, setrlimit failed", .{});
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const io = std.io;
|
const io = std.io;
|
||||||
const os = std.os;
|
const posix = std.posix;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ pub fn main() !void {
|
|||||||
, .{}),
|
, .{}),
|
||||||
error.ConnectFailed => {
|
error.ConnectFailed => {
|
||||||
std.log.err("Unable to connect to the Wayland server.", .{});
|
std.log.err("Unable to connect to the Wayland server.", .{});
|
||||||
if (os.getenvZ("WAYLAND_DISPLAY") == null) {
|
if (posix.getenvZ("WAYLAND_DISPLAY") == null) {
|
||||||
fatal("WAYLAND_DISPLAY is not set.", .{});
|
fatal("WAYLAND_DISPLAY is not set.", .{});
|
||||||
} else {
|
} else {
|
||||||
fatal("Does WAYLAND_DISPLAY contain the socket name of a running server?", .{});
|
fatal("Does WAYLAND_DISPLAY contain the socket name of a running server?", .{});
|
||||||
@ -72,17 +72,17 @@ fn _main() !void {
|
|||||||
const result = flags.parser([*:0]const u8, &.{
|
const result = flags.parser([*:0]const u8, &.{
|
||||||
.{ .name = "h", .kind = .boolean },
|
.{ .name = "h", .kind = .boolean },
|
||||||
.{ .name = "version", .kind = .boolean },
|
.{ .name = "version", .kind = .boolean },
|
||||||
}).parse(os.argv[1..]) catch {
|
}).parse(std.os.argv[1..]) catch {
|
||||||
try io.getStdErr().writeAll(usage);
|
try io.getStdErr().writeAll(usage);
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
};
|
};
|
||||||
if (result.flags.h) {
|
if (result.flags.h) {
|
||||||
try io.getStdOut().writeAll(usage);
|
try io.getStdOut().writeAll(usage);
|
||||||
os.exit(0);
|
posix.exit(0);
|
||||||
}
|
}
|
||||||
if (result.flags.version) {
|
if (result.flags.version) {
|
||||||
try io.getStdOut().writeAll(@import("build_options").version ++ "\n");
|
try io.getStdOut().writeAll(@import("build_options").version ++ "\n");
|
||||||
os.exit(0);
|
posix.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const display = try wl.Display.connect(null);
|
const display = try wl.Display.connect(null);
|
||||||
@ -110,10 +110,10 @@ fn _main() !void {
|
|||||||
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
|
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.global => |global| {
|
.global => |global| {
|
||||||
if (mem.orderZ(u8, global.interface, wl.Seat.getInterface().name) == .eq) {
|
if (mem.orderZ(u8, global.interface, wl.Seat.interface.name) == .eq) {
|
||||||
assert(globals.seat == null); // TODO: support multiple seats
|
assert(globals.seat == null); // TODO: support multiple seats
|
||||||
globals.seat = registry.bind(global.name, wl.Seat, 1) catch @panic("out of memory");
|
globals.seat = registry.bind(global.name, wl.Seat, 1) catch @panic("out of memory");
|
||||||
} else if (mem.orderZ(u8, global.interface, zriver.ControlV1.getInterface().name) == .eq) {
|
} else if (mem.orderZ(u8, global.interface, zriver.ControlV1.interface.name) == .eq) {
|
||||||
globals.control = registry.bind(global.name, zriver.ControlV1, 1) catch @panic("out of memory");
|
globals.control = registry.bind(global.name, zriver.ControlV1, 1) catch @panic("out of memory");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -128,14 +128,14 @@ fn callbackListener(_: *zriver.CommandCallbackV1, event: zriver.CommandCallbackV
|
|||||||
const stdout = io.getStdOut().writer();
|
const stdout = io.getStdOut().writer();
|
||||||
stdout.print("{s}\n", .{success.output}) catch @panic("failed to write to stdout");
|
stdout.print("{s}\n", .{success.output}) catch @panic("failed to write to stdout");
|
||||||
}
|
}
|
||||||
os.exit(0);
|
posix.exit(0);
|
||||||
},
|
},
|
||||||
.failure => |failure| {
|
.failure => |failure| {
|
||||||
// A small hack to provide usage text when river reports an unknown command.
|
// A small hack to provide usage text when river reports an unknown command.
|
||||||
if (mem.orderZ(u8, failure.failure_message, "unknown command") == .eq) {
|
if (mem.orderZ(u8, failure.failure_message, "unknown command") == .eq) {
|
||||||
std.log.err("unknown command", .{});
|
std.log.err("unknown command", .{});
|
||||||
io.getStdErr().writeAll(usage) catch {};
|
io.getStdErr().writeAll(usage) catch {};
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
}
|
}
|
||||||
fatal("{s}", .{failure.failure_message});
|
fatal("{s}", .{failure.failure_message});
|
||||||
},
|
},
|
||||||
@ -144,5 +144,5 @@ fn callbackListener(_: *zriver.CommandCallbackV1, event: zriver.CommandCallbackV
|
|||||||
|
|
||||||
fn fatal(comptime format: []const u8, args: anytype) noreturn {
|
fn fatal(comptime format: []const u8, args: anytype) noreturn {
|
||||||
std.log.err(format, args);
|
std.log.err(format, args);
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ const std = @import("std");
|
|||||||
const fmt = std.fmt;
|
const fmt = std.fmt;
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const math = std.math;
|
const math = std.math;
|
||||||
const os = std.os;
|
const posix = std.posix;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
const wayland = @import("wayland");
|
const wayland = @import("wayland");
|
||||||
@ -311,19 +311,19 @@ pub fn main() !void {
|
|||||||
.{ .name = "main-location", .kind = .arg },
|
.{ .name = "main-location", .kind = .arg },
|
||||||
.{ .name = "main-count", .kind = .arg },
|
.{ .name = "main-count", .kind = .arg },
|
||||||
.{ .name = "main-ratio", .kind = .arg },
|
.{ .name = "main-ratio", .kind = .arg },
|
||||||
}).parse(os.argv[1..]) catch {
|
}).parse(std.os.argv[1..]) catch {
|
||||||
try std.io.getStdErr().writeAll(usage);
|
try std.io.getStdErr().writeAll(usage);
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
};
|
};
|
||||||
if (result.flags.h) {
|
if (result.flags.h) {
|
||||||
try std.io.getStdOut().writeAll(usage);
|
try std.io.getStdOut().writeAll(usage);
|
||||||
os.exit(0);
|
posix.exit(0);
|
||||||
}
|
}
|
||||||
if (result.args.len != 0) fatalPrintUsage("unknown option '{s}'", .{result.args[0]});
|
if (result.args.len != 0) fatalPrintUsage("unknown option '{s}'", .{result.args[0]});
|
||||||
|
|
||||||
if (result.flags.version) {
|
if (result.flags.version) {
|
||||||
try std.io.getStdOut().writeAll(@import("build_options").version ++ "\n");
|
try std.io.getStdOut().writeAll(@import("build_options").version ++ "\n");
|
||||||
os.exit(0);
|
posix.exit(0);
|
||||||
}
|
}
|
||||||
if (result.flags.@"view-padding") |raw| {
|
if (result.flags.@"view-padding") |raw| {
|
||||||
view_padding = fmt.parseUnsigned(u31, raw, 10) catch
|
view_padding = fmt.parseUnsigned(u31, raw, 10) catch
|
||||||
@ -352,7 +352,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const display = wl.Display.connect(null) catch {
|
const display = wl.Display.connect(null) catch {
|
||||||
std.debug.print("Unable to connect to Wayland server.\n", .{});
|
std.debug.print("Unable to connect to Wayland server.\n", .{});
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
};
|
};
|
||||||
defer display.disconnect();
|
defer display.disconnect();
|
||||||
|
|
||||||
@ -382,9 +382,9 @@ pub fn main() !void {
|
|||||||
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
|
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.global => |global| {
|
.global => |global| {
|
||||||
if (mem.orderZ(u8, global.interface, river.LayoutManagerV3.getInterface().name) == .eq) {
|
if (mem.orderZ(u8, global.interface, river.LayoutManagerV3.interface.name) == .eq) {
|
||||||
context.layout_manager = registry.bind(global.name, river.LayoutManagerV3, 1) catch return;
|
context.layout_manager = registry.bind(global.name, river.LayoutManagerV3, 1) catch return;
|
||||||
} else if (mem.orderZ(u8, global.interface, wl.Output.getInterface().name) == .eq) {
|
} else if (mem.orderZ(u8, global.interface, wl.Output.interface.name) == .eq) {
|
||||||
context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err});
|
context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -405,13 +405,13 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *
|
|||||||
|
|
||||||
fn fatal(comptime format: []const u8, args: anytype) noreturn {
|
fn fatal(comptime format: []const u8, args: anytype) noreturn {
|
||||||
std.log.err(format, args);
|
std.log.err(format, args);
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fatalPrintUsage(comptime format: []const u8, args: anytype) noreturn {
|
fn fatalPrintUsage(comptime format: []const u8, args: anytype) noreturn {
|
||||||
std.log.err(format, args);
|
std.log.err(format, args);
|
||||||
std.io.getStdErr().writeAll(usage) catch {};
|
std.io.getStdErr().writeAll(usage) catch {};
|
||||||
os.exit(1);
|
posix.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn saturatingCast(comptime T: type, x: anytype) T {
|
fn saturatingCast(comptime T: type, x: anytype) T {
|
||||||
|
Reference in New Issue
Block a user