4
40
Fork
You've already forked tinyrwm
16

Zig implementation #11

Open
vladkhard wants to merge 2 commits from vladkhard/tinyrwm:main into main
pull from: vladkhard/tinyrwm:main
merge into: river:main
river:main
First-time contributor
Copy link
No description provided.
vladkhard force-pushed main from 5a0d2b14b0
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
to 5be8cadf3d
All checks were successful
builds.sr.ht/alpine Job completed
builds.sr.ht/archlinux Job completed
2026年05月17日 10:43:23 +02:00
Compare
ifreund left a comment
Copy link

This code needs a fair bit of cleanup before it's ready to merge.

This code needs a fair bit of cleanup before it's ready to merge.
zig/src/main.zig Outdated
@ -0,0 +5,4 @@
constwayland=@import("wayland");
constriver=wayland.client.river;
constwl=wayland.client.wl;
constLogger=std.log.scoped(.tinyrwm);
Owner
Copy link

There's no need for scoping here, this is a single file just use the default scope. (e.g. std.log.err())

There's no need for scoping here, this is a single file just use the default scope. (e.g. `std.log.err()`)
zig/src/main.zig Outdated
@ -0,0 +10,4 @@
fnpanic(comptimeformat:[]constu8,args:anytype)void{
Logger.err(format,args);
std.process.exit(1);
}
Owner
Copy link

Use std.process.fatal() instead.

Use `std.process.fatal()` instead.
zig/src/main.zig Outdated
@ -0,0 +15,4 @@
constOutput=struct{
obj:*river.OutputV1,
removed:bool=false,
link:wl.list.Link=undefined,
Owner
Copy link

IMO setting using undefined as the default value for a struct field is poor style, it makes it too easy to forget to initialize the field. Please don't use it as a default struct field value here and elsewhere.

IMO setting using `undefined` as the default value for a struct field is poor style, it makes it too easy to forget to initialize the field. Please don't use it as a default struct field value here and elsewhere.
zig/src/main.zig Outdated
@ -0,0 +17,4 @@
removed:bool=false,
link:wl.list.Link=undefined,
fnhandleRemoved(output:*Output)void{
Owner
Copy link

This function is pointless, inline it. Same goes for all the other trivial functions (handleClosed(), handleDimensions(), etc.)

This function is pointless, inline it. Same goes for all the other trivial functions (`handleClosed()`, `handleDimensions()`, etc.)
zig/src/main.zig Outdated
@ -0,0 +26,4 @@
output.link.remove();
gpa.destroy(output);
}
fnlistener(_:*river.OutputV1,event:river.OutputV1.Event,output:*Output)void{
Owner
Copy link

Please add a blank line between function definitions.

Please add a blank line between function definitions.
zig/src/main.zig Outdated
@ -0,0 +48,4 @@
x:i32=undefined,
y:i32=undefined,
width:i32=undefined,
height:i32=undefined,
Owner
Copy link

Code smell, don't use undefined as a default field value.

Code smell, don't use undefined as a default field value.
zig/src/main.zig Outdated
@ -0,0 +52,4 @@
pointer_move_requested:?*Seat=null,
pointer_resize_requested:?*Seat=null,
pointer_resize_requested_edges:river.WindowV1.Edges=undefined,
Owner
Copy link

Use a tagged union for requested move/resize.

Use a tagged union for requested move/resize.
zig/src/main.zig Outdated
@ -0,0 +502,4 @@
else=>{},
}
}
fnsetListener(window_manager:*WindowManager)void{
Owner
Copy link

Function is pointless, inline it.

Function is pointless, inline it.
zig/src/main.zig Outdated
@ -0,0 +522,4 @@
switch(event){
.global=>|ev|{
constinterface_name=std.mem.span(ev.interface);
if(std.mem.eql(u8,interface_name,river_wm_interface_name)){
Owner
Copy link

Skip the redundant mem.span() calls and use mem.orderZ()

Skip the redundant mem.span() calls and use `mem.orderZ()`
zig/src/main.zig Outdated
@ -0,0 +541,4 @@
vario:std.Io=undefined;
varwm:WindowManager=undefined;
varxkb_bindings_v1:*river.XkbBindingsV1=undefined;
Owner
Copy link

I don't particularly like having so many global variables. I'd rather have only wm and use e.g. wm.gpa instead. Is the io global even used anywhere currently?

I don't particularly like having so many global variables. I'd rather have only `wm` and use e.g. `wm.gpa` instead. Is the `io` global even used anywhere currently?
zig/src/main.zig Outdated
@ -0,0 +490,4 @@
seat.setListener();
window_manager.seats.append(seat);
}
fnlistener(_:*river.WindowManagerV1,event:river.WindowManagerV1.Event,window_manager:*WindowManager)void{
Owner
Copy link

Use the global wm rather than passing a *WindowManager pointer to this function to avoid confusion.

Use the global `wm` rather than passing a `*WindowManager` pointer to this function to avoid confusion.
vladkhard force-pushed main from 5be8cadf3d
All checks were successful
builds.sr.ht/alpine Job completed
builds.sr.ht/archlinux Job completed
to 12a4e3792c
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
2026年05月18日 13:00:57 +02:00
Compare
ifreund left a comment
Copy link

Looking a bit better, here's another review pass.

Looking a bit better, here's another review pass.
zig/src/main.zig Outdated
@ -0,0 +47,4 @@
fnhandlePointerMoveRequested(window:*Window,river_seat:*river.SeatV1)void{
variter=wm.seats.iterator(.forward);
while(iter.next())|seat|{
Owner
Copy link

There's no need for a loop here, use river_seat.getUserData() and cast the pointer instead.

Also please inline handlePointerMoveRequested and handlePointerResizeRequested

There's no need for a loop here, use `river_seat.getUserData()` and cast the pointer instead. Also please inline `handlePointerMoveRequested` and `handlePointerResizeRequested`
zig/src/main.zig Outdated
@ -0,0 +110,4 @@
window.setPosition(0,0);
window.obj.proposeDimensions(0,0);
}
if(window.pointer_request==.move){
Owner
Copy link

Use a switch on window.pointer_request rather than consecutive if statements.

Use a switch on `window.pointer_request` rather than consecutive if statements.
zig/src/main.zig Outdated
@ -0,0 +148,4 @@
}
fncreate(seat:*Seat,mods:river.SeatV1.Modifiers,keysym:comptime_int,action:Action)void{
varbinding=wm.gpa.create(XkbBinding)catch
Owner
Copy link

should be const

should be `const`
zig/src/main.zig Outdated
@ -0,0 +149,4 @@
fncreate(seat:*Seat,mods:river.SeatV1.Modifiers,keysym:comptime_int,action:Action)void{
varbinding=wm.gpa.create(XkbBinding)catch
returnstd.process.fatal("Binding allocation error.",.{});
Owner
Copy link

I would write this as catch fatal("OOM"); and define const fatal = std.process.fatal; at the top of the file.

I would write this as `catch fatal("OOM");` and define `const fatal = std.process.fatal;` at the top of the file.
zig/src/main.zig Outdated
@ -0,0 +150,4 @@
fncreate(seat:*Seat,mods:river.SeatV1.Modifiers,keysym:comptime_int,action:Action)void{
varbinding=wm.gpa.create(XkbBinding)catch
returnstd.process.fatal("Binding allocation error.",.{});
constobj=wm.xkb_bindings_v1.getXkbBinding(seat.obj,keysym,mods)catch
Owner
Copy link

This should likewise be catch fatal("OOM");, the current error message is nonsense.

This should likewise be `catch fatal("OOM");`, the current error message is nonsense.
zig/src/main.zig Outdated
@ -0,0 +189,4 @@
varbinding=wm.gpa.create(PointerBinding)catch
returnstd.process.fatal("Binding allocation error.",.{});
constobj=seat.obj.getPointerBinding(button,mods)catch
returnstd.process.fatal("Unknown binding.",.{});
Owner
Copy link

See comments on the equivalent XkbBinding code above.

See comments on the equivalent XkbBinding code above.
zig/src/main.zig Outdated
@ -0,0 +239,4 @@
op_release:bool=false,
op_start_width:i32,
op_start_height:i32,
op_edges:river.WindowV1.Edges=.{},
Owner
Copy link

Most if not all of this op-related data would be better represented as a tagged union.

Most if not all of this op-related data would be better represented as a tagged union.
zig/src/main.zig Outdated
@ -0,0 +246,4 @@
.removed=>seat.removed=true,
.pointer_enter=>|args|{
if(args.window)|window|
seat.hovered=@ptrCast(@alignCast(window.getUserData()));
Owner
Copy link

seat.hovered is an optional pointer and we want to set seat.hovered to null even if the window has been destroyed. The if statement should be eliminated.

`seat.hovered` is an optional pointer and we want to set `seat.hovered` to null even if the window has been destroyed. The if statement should be eliminated.
zig/src/main.zig Outdated
@ -0,0 +251,4 @@
.pointer_leave=>seat.hovered=null,
.window_interaction=>|args|{
if(args.window)|window|
seat.interacted=@ptrCast(@alignCast(window.getUserData()));
Owner
Copy link

Same as above, eliminate the if statement.

Same as above, eliminate the if statement.
zig/src/main.zig Outdated
@ -0,0 +265,4 @@
fnmaybeDestroy(seat:*Seat)void{
if(!seat.removed)return;
varxkb_iter=seat.xkb_bindings.safeIterator(.forward);
while(xkb_iter.next())|binding|binding.destroy();
Owner
Copy link

This can be simplified:

while(seat.xkb_bindings.first())|binding|binding.destroy();

Same goes for the other instances of this pattern.

This can be simplified: ```zig while (seat.xkb_bindings.first()) |binding| binding.destroy(); ``` Same goes for the other instances of this pattern.
zig/src/main.zig Outdated
@ -0,0 +276,4 @@
fnfocus(seat:*Seat,window:?*Window)void{
constfocused_window=windoworelseblk:{
if(wm.windows.empty())returnseat.obj.clearFocus();
break:blkwm.windows.last()orelseunreachable;
Owner
Copy link

This is code smell. Please refactor this to eliminate the need for the orelse unreachable.

This is code smell. Please refactor this to eliminate the need for the `orelse unreachable`.
zig/src/main.zig Outdated
@ -0,0 +318,4 @@
switch(action){
.none=>{},
.spawn_foot=>_=std.process.spawn(wm.io,.{.argv=&[_][]constu8{"foot"}})catch|err|
returnstd.process.fatal("Failed to spawn foot: {}.",.{err}),
Owner
Copy link

This should not be a fatal error.

This should not be a fatal error.
zig/src/main.zig Outdated
@ -0,0 +321,4 @@
returnstd.process.fatal("Failed to spawn foot: {}.",.{err}),
.close=>if(seat.focused)|window|window.obj.close(),
.focus_next=>if(!wm.windows.empty())seat.focus(wm.windows.first()),
.move=>if(seat.op==.noneandseat.hovered!=null)seat.pointerMove(seat.hovered.?),
Owner
Copy link

This .? assertion is unnecessary, please refactor this to avoid the need (i.e. use a second if statement).

Same below.

This `.?` assertion is unnecessary, please refactor this to avoid the need (i.e. use a second if statement). Same below.
zig/src/main.zig Outdated
@ -0,0 +340,4 @@
constxkb_keysym_space=0x20;
constxkb_keysym_q=0x0071;
constxkb_keysym_n=0x006E;
constxkb_keysym_escape=0xFF1B;
Owner
Copy link

Use zig-xkbcommon rather than hardcoding these.

Use zig-xkbcommon rather than hardcoding these.
zig/src/main.zig Outdated
@ -0,0 +342,4 @@
constxkb_keysym_n=0x006E;
constxkb_keysym_escape=0xFF1B;
constbutton_left=0x110;
constbutton_right=0x111;
Owner
Copy link

I'd rather translate-c and import the linux/input-event-codes.h header than hardcode these. It would serve better as an example.

I'd rather translate-c and import the `linux/input-event-codes.h` header than hardcode these. It would serve better as an example.
zig/src/main.zig Outdated
@ -0,0 +443,4 @@
}
fnhandleWindow(window_manager:*WindowManager,river_window:*river.WindowV1)void{
varwindow=wm.gpa.create(Window)catch
Owner
Copy link

should be const

should be const
zig/src/main.zig Outdated
@ -0,0 +458,4 @@
}
fnhandleOutput(window_manager:*WindowManager,river_output:*river.OutputV1)void{
varoutput=wm.gpa.create(Output)catch
Owner
Copy link

should be const

should be const
zig/src/main.zig Outdated
@ -0,0 +466,4 @@
}
fnhandleSeat(window_manager:*WindowManager,river_seat:*river.SeatV1)void{
varseat=wm.gpa.create(Seat)catch
Owner
Copy link

should be const

should be const
zig/src/main.zig Outdated
@ -0,0 +484,4 @@
fnlistener(_:*river.WindowManagerV1,event:river.WindowManagerV1.Event,_:?*anyopaque)void{
switch(event){
.unavailable=>returnstd.process.fatal("Another window manager is already running.",.{}),
Owner
Copy link

the return is redundant.

the `return` is redundant.
zig/src/main.zig Outdated
@ -0,0 +487,4 @@
.unavailable=>returnstd.process.fatal("Another window manager is already running.",.{}),
.finished=>std.process.exit(0),
.manage_start=>wm.handleManageStart(),
.render_start=>wm.handleRenderStart(),
Owner
Copy link

The "handle" prefix is superfluous, rename these to just manageStart()/renderStart()

The "handle" prefix is superfluous, rename these to just `manageStart()`/`renderStart()`
zig/src/main.zig Outdated
@ -0,0 +490,4 @@
.render_start=>wm.handleRenderStart(),
.window=>|ev|wm.handleWindow(ev.id),
.output=>|ev|wm.handleOutput(ev.id),
.seat=>|ev|wm.handleSeat(ev.id),
Owner
Copy link

Please rename these to e.g. Window.create(ev.id).

Please rename these to e.g. `Window.create(ev.id)`.
zig/src/main.zig Outdated
@ -0,0 +496,4 @@
}
fninit(obj:*river.WindowManagerV1)void{
wm.obj=obj;
Owner
Copy link

The wm variable should be initialized using wm = .{ } syntax to benefit from compile errors when a field is forgotten. It might make sense to inline this init() function.

The `wm` variable should be initialized using `wm = .{ }` syntax to benefit from compile errors when a field is forgotten. It might make sense to inline this `init()` function.
zig/src/main.zig Outdated
@ -0,0 +510,4 @@
if(std.mem.orderZ(u8,river.WindowManagerV1.interface.name,ev.interface)==.eq){
if(ev.version<4)returnstd.process.fatal("Expected river wm version to be a least 4.",.{});
constriver_wm=registry.bind(ev.name,river.WindowManagerV1,4)catch
returnstd.process.fatal("Failed to bind river window manager.",.{});
Owner
Copy link

"OOM"

"OOM"
zig/src/main.zig Outdated
@ -0,0 +511,4 @@
if(ev.version<4)returnstd.process.fatal("Expected river wm version to be a least 4.",.{});
constriver_wm=registry.bind(ev.name,river.WindowManagerV1,4)catch
returnstd.process.fatal("Failed to bind river window manager.",.{});
WindowManager.init(river_wm);
Owner
Copy link

There is currently Illegal behavior if the server does not advertise the river_window_manager_v1 global.

If the server does not advertise either of the required globals tinyrwm should exit with an error message.

There is currently Illegal behavior if the server does not advertise the `river_window_manager_v1` global. If the server does not advertise either of the required globals tinyrwm should exit with an error message.
Owner
Copy link

This illegal behavior has not been resolved.

This illegal behavior has not been resolved.
vladkhard force-pushed main from 12a4e3792c
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
to 1fd741756e
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
2026年05月19日 15:25:18 +02:00
Compare
vladkhard force-pushed main from 1fd741756e
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
to a4e0a4179a
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
2026年05月19日 16:18:00 +02:00
Compare
zig/src/main.zig Outdated
@ -0,0 +9,4 @@
constwl=wayland.client.wl;
constfatal=std.process.fatal;
constriver_window_manager_v1=4;
constriver_xkb_bindings_v1=3;
Owner
Copy link

This is a misleading variable name, also river_xkb_bindings_v1 version 3 is technically not required. I'd personally drop these variable names and inline like tinyrwm.c

This is a misleading variable name, also `river_xkb_bindings_v1` version 3 is technically not required. I'd personally drop these variable names and inline like tinyrwm.c
zig/src/main.zig Outdated
@ -0,0 +88,4 @@
.node=river_window.getNode()catchfatal("Unable to obtain Window's Node.",.{}),
.link=undefined,
.width=undefined,
.height=undefined,
Owner
Copy link

Leaving width/height undefined beyond the create() function requires some justification IMO. Explain why it is impossible for illegal behavior to happen in a comment or choose a different approach.

Leaving width/height undefined beyond the `create()` function requires some justification IMO. Explain why it is impossible for illegal behavior to happen in a comment or choose a different approach.
zig/src/main.zig Outdated
@ -0,0 +231,4 @@
constSeatOp=union(enum){
none,
move:struct{
window:?*Window,
Owner
Copy link

Why is this an optional pointer? Same question for resize.

Why is this an optional pointer? Same question for resize.
zig/src/main.zig Outdated
@ -0,0 +442,4 @@
io:std.Io,
obj:?*river.WindowManagerV1=null,
xkb_bindings:?*river.XkbBindingsV1=null,
Owner
Copy link

It would be cleaner if these fields were not optional pointers. The wm.obj.? assertions strewn around the code are ugly, and very much avoidable if the code were restructured to only initialize the wm struct after the initial roundtrip is completed for example.

It would be cleaner if these fields were not optional pointers. The `wm.obj.?` assertions strewn around the code are ugly, and very much avoidable if the code were restructured to only initialize the `wm` struct after the initial roundtrip is completed for example.
zig/src/main.zig Outdated
@ -0,0 +452,4 @@
map(&window_manager.windows,Window.maybeDestroy);
map(&window_manager.outputs,Output.maybeDestroy);
map(&window_manager.seats,Seat.maybeDestroy);
map(&window_manager.seats,Seat.manage);
Owner
Copy link

This is not idiomatic Zig, prefer imperative style.

This is not idiomatic Zig, prefer imperative style.
zig/src/main.zig Outdated
@ -0,0 +531,4 @@
while(true)
if(display.dispatch()!=.SUCCESS)
fatal("Dispatch failed.",.{});
Owner
Copy link

I quite dislike multi-line while/if statements without curlies, https://codeberg.org/river/river/src/branch/main/CONTRIBUTING.md#coding-style

I quite dislike multi-line while/if statements without curlies, https://codeberg.org/river/river/src/branch/main/CONTRIBUTING.md#coding-style
vladkhard force-pushed main from a4e0a4179a
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
to b20b1732eb
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
2026年05月21日 12:41:20 +02:00
Compare
vladkhard force-pushed main from b20b1732eb
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
to f9876238be
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
2026年05月26日 17:26:33 +02:00
Compare
vladkhard force-pushed main from f9876238be
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
to 1044370196
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
2026年06月02日 15:07:07 +02:00
Compare
Author
First-time contributor
Copy link

Hi, @ifreund ! Is there anything else I can improve in this PR?

Hi, @ifreund ! Is there anything else I can improve in this PR?

This is only blocked on me finding time to test locally and perhaps make some style tweaks. Choosing to implement tinyrwm in the language I'm most intimately familiar with is both a blessing and a curse :)

This is only blocked on me finding time to test locally and perhaps make some style tweaks. Choosing to implement tinyrwm in the language I'm most intimately familiar with is both a blessing and a curse :)
Author
First-time contributor
Copy link

@ifreund wrote in #11 (comment):

This is only blocked on me finding time to test locally and perhaps make some style tweaks. Choosing to implement tinyrwm in the language I'm most intimately familiar with is both a blessing and a curse :)

Thank you for your time! Appreciate the learning opportunity :)

@ifreund wrote in https://codeberg.org/river/tinyrwm/pulls/11#issuecomment-17492843: > This is only blocked on me finding time to test locally and perhaps make some style tweaks. Choosing to implement tinyrwm in the language I'm most intimately familiar with is both a blessing and a curse :) Thank you for your time! Appreciate the learning opportunity :)
vladkhard force-pushed main from 1044370196
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
to 94b1cf0b8b
All checks were successful
builds.sr.ht/alpine Job completed
builds.sr.ht/archlinux Job completed
2026年06月22日 13:59:49 +02:00
Compare
vladkhard force-pushed main from 94b1cf0b8b
All checks were successful
builds.sr.ht/alpine Job completed
builds.sr.ht/archlinux Job completed
to 12482f59a1
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
2026年06月22日 14:04:14 +02:00
Compare
vladkhard force-pushed main from 12482f59a1
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
to 7947427880
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
2026年07月08日 11:09:34 +02:00
Compare
All checks were successful
builds.sr.ht/archlinux Job completed
builds.sr.ht/alpine Job completed
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u main:vladkhard-main
git switch vladkhard-main

Merge

Merge the changes and update on Forgejo.
git switch main
git merge --no-ff vladkhard-main
git switch vladkhard-main
git rebase main
git switch main
git merge --ff-only vladkhard-main
git switch vladkhard-main
git rebase main
git switch main
git merge --no-ff vladkhard-main
git switch main
git merge --squash vladkhard-main
git switch main
git merge --ff-only vladkhard-main
git switch main
git merge vladkhard-main
git push origin main
Sign in to join this conversation.
No reviewers
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
river/tinyrwm!11
Reference in a new issue
river/tinyrwm
No description provided.
Delete branch "vladkhard/tinyrwm:main"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?