Compare commits

..

15 Commits

Author SHA1 Message Date
242b62a9d4 Merge new river-classic 2025-12-14 08:12:14 -08:00
be722b2a3f docs: fix typo in README 2025-12-10 20:24:22 +01:00
9c42ed788a build: bump version to 0.3.14-dev 2025-10-03 11:36:58 +02:00
3ef7b69112 build: bump version to 0.3.13 2025-10-03 11:35:50 +02:00
165871e0fc command: fix possible crash on focus
If the currently focused view has been moved to a different output but
the transaction has not yet been completed, we can hit unreachable code
in this function.

thread 1073 panic: reached unreachable code
/home/pkaplan/Software/river/river/command/view_operations.zig💯21: 0x11752d6 in getTarget (river)
                    unreachable;
                    ^
/home/pkaplan/Software/river/river/command/view_operations.zig:46:22: 0x1146f36 in focusView (river)
    if (try getTarget(
                     ^
/home/pkaplan/Software/river/river/command.zig:144:16: 0x11124e3 in run (river)
    try impl_fn(seat, args, out);
               ^
/home/pkaplan/Software/river/river/Seat.zig:447:16: 0x10eb00f in runCommand (river)
    command.run(seat, args, &out) catch |err| {
               ^
/home/pkaplan/Software/river/river/Seat.zig:423:24: 0x1110350 in handleMapping (river)
        seat.runCommand(mapping.command_args);
                       ^
/home/pkaplan/Software/river/river/Keyboard.zig:213:47: 0x10e83b8 in wrapper (river)
        if (keyboard.device.seat.handleMapping(keycode, modifiers, released, xkb_state)) {
                                              ^
???:?:?: 0x7d8f73aa051d in ??? (libwayland-server.so.0)
Unwind information for `libwayland-server.so.0:0x7d8f73aa051d` was not available, trace may be incomplete

???:?:?: 0x7d8f739fecc7 in ??? (libwlroots-0.18.so)
???:?:?: 0x7d8f739d0dbb in ??? (libwlroots-0.18.so)
???:?:?: 0x7d8f73aa2111 in ??? (libwayland-server.so.0)
???:?:?: 0x7d8f73aa41f6 in ??? (libwayland-server.so.0)
/home/pkaplan/Software/river/river/main.zig:139:25: 0x10715dd in main (river)
    server.wl_server.run();
                        ^
/usr/lib/zig/std/start.zig:524:37: 0x107083e in main (river)
            const result = root.main() catch |err| {
                                    ^
???:?:?: 0x7d8f736b06b4 in ??? (libc.so.6)
???:?:?: 0x7d8f736b0768 in ??? (libc.so.6)
???:?:?: 0x1070354 in ??? (???)
2025-10-03 11:28:19 +02:00
633f4e2733 river: remove dead code 2025-10-03 11:28:01 +02:00
170cac310d linux-dmabuf: disable fine-grained feedback again
Despite the better debouncing in wlroots 0.19, this is still causing
problems. In particular, certain clients (mostly Xwayland games it
seems) have a bad interaction with this additional feedback, possibly
related to the cursor buffer.

The reported symptoms are significant framerate drops and stuttering on
pointer input.

Probably there's a wlroots bug here at fault but I don't have the
resources to fix it myself currently and would rather not force river
users to suffer.

Setting WLR_SCENE_DISABLE_DIRECT_SCANOUT=1 isn't sufficient to solve
these problems either, as wlr_scene still sends enough extra feedback
to trigger the problems.

Closes: https://codeberg.org/river/river/issues/1277
2025-09-02 10:52:57 +02:00
c18ed4aae1 river: fix logging to non-pipe
Since the Zig 0.15 upgrade, if stderr is redirected to a file, river
writes every log message to the beginning of the file, overwriting the
previous message. This commit fixes that and also switches to the nicer
new std.debug API for stderr logging.
2025-09-02 09:46:46 +02:00
f0c7f57d36 LayerSurface: fix crash on bad exclusive zone
River closes layer surfaces with an unreasonably large exclusive zone.
However, due to unfortunate/awkward code structure, this currently
may cause a use-after-free on mapping layer surface.
2025-09-01 09:55:49 +02:00
d412c9cdd1 doc: tweak README formatting 2025-08-30 19:23:45 +02:00
e5c7d75dcc build: bump version to 0.3.13-dev 2025-08-30 19:01:32 +02:00
8e4f3f6800 build: bump version to 0.3.12 2025-08-30 19:01:32 +02:00
50d5108d1f ci: rename to river-classic 2025-08-30 19:01:32 +02:00
1168a3f47a doc: rename to river-classic 2025-08-30 18:56:31 +02:00
265461162f build: rename to river-classic 2025-08-30 18:56:31 +02:00
10 changed files with 127 additions and 130 deletions

View File

@ -23,7 +23,7 @@ packages:
- wget
- xz
sources:
- https://codeberg.org/river/river
- https://codeberg.org/river/river-classic
- https://gitlab.freedesktop.org/wlroots/wlroots.git
tasks:
- install_deps: |
@ -42,13 +42,13 @@ tasks:
sudo mv zig-x86_64-linux-0.15.1/zig /usr/bin/
sudo mv zig-x86_64-linux-0.15.1/lib /usr/lib/zig
- build: |
cd river
cd river-classic
zig build --summary all
- build_xwayland: |
cd river
cd river-classic
zig build --summary all -Dxwayland
- fmt: |
cd river
cd river-classic
zig fmt --check river/
zig fmt --check riverctl/
zig fmt --check rivertile/

View File

@ -20,7 +20,7 @@ packages:
- wget
- xz
sources:
- https://codeberg.org/river/river
- https://codeberg.org/river/river-classic
- https://gitlab.freedesktop.org/wlroots/wlroots.git
tasks:
- install_deps: |
@ -39,13 +39,13 @@ tasks:
sudo mv zig-x86_64-linux-0.15.1/zig /usr/bin/
sudo mv zig-x86_64-linux-0.15.1/lib /usr/lib/zig
- build: |
cd river
cd river-classic
zig build --summary all
- build_xwayland: |
cd river
cd river-classic
zig build --summary all -Dxwayland
- fmt: |
cd river
cd river-classic
zig fmt --check river/
zig fmt --check riverctl/
zig fmt --check rivertile/

View File

@ -26,7 +26,7 @@ packages:
- scdoc
- wget
sources:
- https://codeberg.org/river/river
- https://codeberg.org/river/river-classic
- https://gitlab.freedesktop.org/wlroots/wlroots.git
tasks:
- install_deps: |
@ -46,13 +46,13 @@ tasks:
sudo mv zig-x86_64-freebsd-0.15.1/zig /usr/bin/
sudo mv zig-x86_64-freebsd-0.15.1/lib /usr/lib/zig
- build: |
cd river
cd river-classic
zig build --summary all
- build_xwayland: |
cd river
cd river-classic
zig build --summary all -Dxwayland
- fmt: |
cd river
cd river-classic
zig fmt --check river/
zig fmt --check riverctl/
zig fmt --check rivertile/

View File

@ -1,39 +1,39 @@
# Packaging river for distribution
# Packaging river-classic for distribution
First of all, I apologize for writing river in Zig. It will likely make
First of all, I apologize for writing river-classic in Zig. It will likely make
your job harder until Zig is more mature/stable. I do however believe that
writing my software in Zig allows me to deliver the best quality I can
despite the drawbacks of depending on a relatively immature language/toolchain.
writing my software in Zig allows me to deliver the best quality I can despite
the drawbacks of depending on a relatively immature language/toolchain.
## Source tarballs
Source tarballs with stable checksums and git submodule sources included may
be found on the [codeberg releases page](https://codeberg.org/river/river/releases).
Source tarballs with stable checksums may be found on the
[codeberg releases page](https://codeberg.org/river/river-classic/releases).
These tarballs are signed with the PGP key available on my website at
<https://isaacfreund.com/public_key.txt>.
For the 0.1.3 release for example, the tarball and signature URLs are:
For the 0.3.12 release for example, the tarball and signature URLs are:
```
https://codeberg.org/river/river/releases/download/v0.1.3/river-0.1.3.tar.gz
https://codeberg.org/river/river/releases/download/v0.1.3/river-0.1.3.tar.gz.sig
https://codeberg.org/river/river-classic/releases/download/v0.1.3/river-classic-0.3.12.tar.gz
https://codeberg.org/river/river-classic/releases/download/v0.1.3/river-classic-0.3.12.tar.gz.sig
```
## Zig version
Until Zig 1.0, Zig releases will often have breaking changes that prevent
river from building. River tracks the latest minor version Zig release
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
not Zig 0.8.0 or 0.10.0.
river-classic from building. river-classic tracks the latest minor version Zig
release 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 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:
river-classic uses the built-in Zig package manager for its (few) Zig
dependencies. By default, running `zig build` will fetch river-classic's Zig
dependencies from the internet and store them in the global zig cache before
building river-classic. 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-classic:
1. Fetch step with internet access:
@ -114,10 +114,10 @@ in the first place. For greatest effect, both may be used.
- `-Doptimize=ReleaseSmall`: Optimize for binary size,
disable all assertions and runtime safety checks.
Please use `-Doptimize=ReleaseSafe` when building river for general
use. CPU execution speed is not the performance bottleneck for river, the
GPU is. Additionally, the increased safety is more than worth the binary
size trade-off in my opinion.
Please use `-Doptimize=ReleaseSafe` when building river-classic for general use.
CPU execution speed is not the performance bottleneck for river-classic, the GPU
is. Additionally, the increased safety is more than worth the binary size
trade-off in my opinion.
## Build prefix and DESTDIR
@ -126,7 +126,7 @@ environment variable. For example
```bash
DESTDIR="/foo/bar" zig build --prefix /usr install
```
will install river to `/foo/bar/usr/bin/river`.
will install river-classic to `/foo/bar/usr/bin/river`.
The Zig build system only has a single install step, there is no way to build
artifacts for a given prefix and then install those artifacts to that prefix
@ -147,7 +147,7 @@ install() {
## River specific suggestions
I recommend installing the example init file found at `example/init` to
`/usr/share/examples/river/init` or similar if your distribution has such
`/usr/share/examples/river-classic/init` or similar if your distribution has such
a convention.
## Examples

View File

@ -1,59 +1,52 @@
<div align="center">
<img src="logo/logo_text_adaptive_color.svg" width="600em">
</div>
# river-classic
## Overview
River is a dynamic tiling Wayland compositor with flexible runtime
river-classic is a dynamic tiling Wayland compositor with flexible runtime
configuration.
Check [packaging status](https://repology.org/project/river-compositor/versions) —
It is a fork of [river](https://codeberg.org/river/river) 0.3 intended for users
that are happy with how river 0.3 works and do not wish to deal with the majorly
breaking changes planned for the river 0.4.0 release.
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
[Code of Conduct](CODE_OF_CONDUCT.md)
The main repository is on [codeberg](https://codeberg.org/river/river),
The main repository is on [codeberg](https://codeberg.org/river/river-classic),
which is where the issue tracker may be found and where contributions are accepted.
Read-only mirrors exist on [sourcehut](https://git.sr.ht/~ifreund/river)
and [github](https://github.com/riverwm/river).
*Note: river has not yet seen a stable 1.0 release and it will be necessary to
make significant breaking changes before 1.0 to realize my longer term plans.
That said, I do my best to avoid gratuitous breaking changes and bugs/crashes
should be rare. If you find a bug don't hesitate to
[open an issue](https://codeberg.org/river/river/issues/new/choose).*
Read-only mirrors exist on [sourcehut](https://git.sr.ht/~ifreund/river-classic)
and [github](https://github.com/riverwm/river-classic).
## Features
Currently river's window management style is quite similar to
river-classic's window management style is quite similar to
[dwm](http://dwm.suckless.org), [xmonad](https://xmonad.org), and other classic
dynamic tiling X11 window managers. Windows are automatically arranged in a tiled
layout and shifted around as windows are opened/closed.
Rather than having the tiled layout logic built into the compositor process,
river uses a [custom Wayland
protocol](https://codeberg.org/river/river/src/branch/master/protocol/river-layout-v3.xml)
river-classic uses a [custom Wayland
protocol](https://codeberg.org/river/river-classic/src/branch/master/protocol/river-layout-v3.xml)
and separate "layout generator" process. A basic layout generator, `rivertile`,
is provided but users are encouraged to use community-developed [layout
generators](https://codeberg.org/river/wiki/src/branch/master/pages/Community-Layouts.md)
or write their own. Examples in C and Python may be found
[here](https://codeberg.org/river/river/src/branch/master/contrib).
[here](https://codeberg.org/river/river-classic/src/branch/master/contrib).
Tags are used to organize windows rather than workspaces. A window may be
assigned to one or more tags. Likewise, one or more tags may be displayed on a
monitor at a time.
River is configured at runtime using the `riverctl` tool. It can define
river-classic is configured at runtime using the `riverctl` tool. It can define
keybindings, set the active layout generator, configure input devices, and more.
On startup, river runs a user-defined init script which usually runs `riverctl`
commands to set up the user's configuration.
On startup, river-classic runs a user-defined init script which usually runs
`riverctl` commands to set up the user's configuration.
## Building
Note: If you are packaging river for distribution, see [PACKAGING.md](PACKAGING.md).
Note: If you are packaging river-classic for distribution, see [PACKAGING.md](PACKAGING.md).
To compile river first ensure that you have the following dependencies
To compile river-classic first ensure that you have the following dependencies
installed. The "development" versions are required if applicable to your
distribution.
@ -79,7 +72,7 @@ Run `zig build -h` to see a list of all options.
River can either be run nested in an X11/Wayland session or directly
from a tty using KMS/DRM. Simply run the `river` command.
On startup river will run an executable file at `$XDG_CONFIG_HOME/river/init`
On startup river-classic will run an executable file at `$XDG_CONFIG_HOME/river/init`
if such an executable exists. If `$XDG_CONFIG_HOME` is not set,
`~/.config/river/init` will be used instead.
@ -93,34 +86,14 @@ in the example directory.
For complete documentation see the `river(1)`, `riverctl(1)`, and
`rivertile(1)` man pages.
## Future Plans
Currently details such as how tags work across multiple monitors are not
possible for users to configure. It would be possible to extend river's source
code to allow more flexibility here but this comes at the cost of complexity and
there will always be someone who prefers something slightly different.
My long term plan to address this is to move as much window management policy as
possible out of the river compositor process and into the "layout generator"
process which will need to be renamed to "window manager." This will give users
much more power and control over river's behavior and also enable some really
cool workflows. For example, it would be possible to write a window manager in
lisp and use hot code reloading to edit its behavior it while it is running.
This is a non-trivial architectural change and will take a while to implement. I
plan to focus on this change for the 0.4.0 release cycle. Unfortunately, it will
almost certainly break existing river configurations as well. I think the
benefits outweigh that downside though and I will do my best to offer a
reasonable upgrade path.
## Donate
If my work on river adds value to your life and you'd like to support me
If my work on river-classic adds value to your life and you'd like to support me
financially you can find donation information [here](https://isaacfreund.com/donate/).
## Licensing
River is released under the GNU General Public License v3.0 only.
river-classic is released under the GNU General Public License v3.0 only.
The protocols in the `protocol` directory are released under various licenses by
various parties. You should refer to the copyright block of each protocol for

View File

@ -1,11 +1,11 @@
.{
.name = .river,
.name = .river_classic,
// While a river release is in development, this string should contain
// the version in development with the "-dev" suffix.
// When a release is tagged, the "-dev" suffix should be removed for the
// commit that gets tagged. Directly after the tagged commit, the version
// should be bumped and the "-dev" suffix added.
.version = "0.3.12",
.version = "0.3.14-dev",
.paths = .{""},
.dependencies = .{
.pixman = .{
@ -25,5 +25,5 @@
.hash = "xkbcommon-0.3.0-VDqIe3K9AQB2fG5ZeRcMC9i7kfrp5m2rWgLrmdNn9azr",
},
},
.fingerprint = 0xf5e3672b8e8d6efc,
.fingerprint = 0x3dae7aba2ea52a3b,
}

View File

@ -36,6 +36,8 @@ wlr_layer_surface: *wlr.LayerSurfaceV1,
scene_layer_surface: *wlr.SceneLayerSurfaceV1,
popup_tree: *wlr.SceneTree,
newly_mapped: bool = false,
destroy: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleDestroy),
map: wl.Listener(void) = wl.Listener(void).init(handleMap),
unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap),
@ -96,19 +98,19 @@ fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), _: *wlr.LayerSurfa
fn handleMap(listener: *wl.Listener(void)) void {
const layer_surface: *LayerSurface = @fieldParentPtr("map", listener);
const wlr_surface = layer_surface.wlr_layer_surface;
const wlr_layer_surface = layer_surface.wlr_layer_surface;
log.debug("layer surface '{s}' mapped", .{wlr_surface.namespace});
log.debug("layer surface '{s}' mapped", .{wlr_layer_surface.namespace});
layer_surface.output.arrangeLayers();
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,
);
// This is a bit of a hack, but layer surfaces are not part of the
// transaction system in river 0.3 so there's not a significantly cleaner
// way I see to do this.
layer_surface.newly_mapped = true;
// Beware: it is possible for arrangeLayers() to destroy this LayerSurface!
const output = layer_surface.output;
output.arrangeLayers();
handleKeyboardInteractiveExclusive(output);
server.root.applyPending();
}
@ -117,8 +119,10 @@ fn handleUnmap(listener: *wl.Listener(void)) void {
log.debug("layer surface '{s}' unmapped", .{layer_surface.wlr_layer_surface.namespace});
layer_surface.output.arrangeLayers();
handleKeyboardInteractiveExclusive(layer_surface.output, null);
// Beware: it is possible for arrangeLayers() to destroy this LayerSurface!
const output = layer_surface.output;
output.arrangeLayers();
handleKeyboardInteractiveExclusive(output);
server.root.applyPending();
}
@ -137,37 +141,61 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
if (wlr_layer_surface.initial_commit or
@as(u32, @bitCast(wlr_layer_surface.current.committed)) != 0)
{
layer_surface.output.arrangeLayers();
handleKeyboardInteractiveExclusive(layer_surface.output, null);
// Beware: it is possible for arrangeLayers() to destroy this LayerSurface!
const output = layer_surface.output;
output.arrangeLayers();
handleKeyboardInteractiveExclusive(output);
server.root.applyPending();
}
layer_surface.newly_mapped = false;
}
/// 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()
fn handleKeyboardInteractiveExclusive(output: *Output, consider: ?*LayerSurface) void {
fn handleKeyboardInteractiveExclusive(output: *Output) void {
if (server.lock_manager.state != .unlocked) return;
// Find the topmost layer surface (if any) in the top or overlay layers which
// requests exclusive keyboard interactivity.
const to_focus = outer: for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top }) |layer| {
const tree = output.layerSurfaceTree(layer);
// Iterate in reverse to match rendering order.
var it = tree.children.iterator(.reverse);
while (it.next()) |node| {
assert(node.type == .tree);
if (@as(?*SceneNodeData, @ptrCast(@alignCast(node.data)))) |node_data| {
const layer_surface = node_data.data.layer_surface;
const wlr_layer_surface = layer_surface.wlr_layer_surface;
if (wlr_layer_surface.surface.mapped and
wlr_layer_surface.current.keyboard_interactive == .exclusive)
{
break :outer layer_surface;
// If none is found, check for a newly mapped surface in the same layers with
// on demand keyboard interactivity.
const to_focus = blk: {
for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top }) |layer| {
const tree = output.layerSurfaceTree(layer);
// Iterate in reverse to match rendering order.
var it = tree.children.iterator(.reverse);
while (it.next()) |node| {
assert(node.type == .tree);
if (@as(?*SceneNodeData, @ptrCast(@alignCast(node.data)))) |node_data| {
const layer_surface = node_data.data.layer_surface;
const wlr_layer_surface = layer_surface.wlr_layer_surface;
if (wlr_layer_surface.surface.mapped and
wlr_layer_surface.current.keyboard_interactive == .exclusive)
{
break :blk layer_surface;
}
}
}
}
} else consider;
for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top }) |layer| {
const tree = output.layerSurfaceTree(layer);
// Iterate in reverse to match rendering order.
var it = tree.children.iterator(.reverse);
while (it.next()) |node| {
assert(node.type == .tree);
if (@as(?*SceneNodeData, @ptrCast(@alignCast(node.data)))) |node_data| {
const layer_surface = node_data.data.layer_surface;
const wlr_layer_surface = layer_surface.wlr_layer_surface;
if (layer_surface.newly_mapped and
wlr_layer_surface.surface.mapped and
wlr_layer_surface.current.keyboard_interactive == .on_demand)
{
break :blk layer_surface;
}
}
}
}
break :blk null;
};
if (to_focus) |s| {
assert(s.wlr_layer_surface.current.keyboard_interactive != .none);

View File

@ -124,8 +124,6 @@ pub fn init(root: *Root) !void {
const gamma_control_manager = try wlr.GammaControlManagerV1.create(server.wl_server);
scene.setGammaControlManagerV1(gamma_control_manager);
if (server.linux_dmabuf) |linux_dmabuf| scene.setLinuxDmabufV1(linux_dmabuf);
const interactive_content = try scene.tree.createSceneTree();
const drag_icons = try scene.tree.createSceneTree();
const hidden_tree = try scene.tree.createSceneTree();

View File

@ -78,12 +78,7 @@ fn getTarget(seat: *Seat, direction_str: []const u8, target_mode: TargetMode) !?
if (seat.focused.view.pending.fullscreen) return null;
if (target_mode == .skip_float and seat.focused.view.pending.float) return null;
const output = seat.focused_output orelse return null;
// If no currently view is focused, focus the first in the stack.
if (seat.focused != .view) {
var it = output.pending.wm_stack.iterator(.forward);
return it.next();
}
if (seat.focused.view.pending.output != output) return null;
// Logical direction, based on the view stack.
if (std.meta.stringToEnum(Direction, direction_str)) |direction| {

View File

@ -188,8 +188,11 @@ pub fn logFn(
const scope_prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
var stderr = fs.File.stderr().writer(&.{});
stderr.interface.print(level.asText() ++ scope_prefix ++ format ++ "\n", args) catch {};
var buffer: [256]u8 = undefined;
const stderr = std.debug.lockStderrWriter(&buffer);
defer std.debug.unlockStderrWriter();
stderr.print(level.asText() ++ scope_prefix ++ format ++ "\n", args) catch {};
}
/// See wlroots_log_wrapper.c