Zig implementation #11
vladkhard/tinyrwm:main into main
5a0d2b14b0
5be8cadf3d
This code needs a fair bit of cleanup before it's ready to merge.
@ -0,0 +5,4 @@
constwayland=@import("wayland");
constriver=wayland.client.river;
constwl=wayland.client.wl;
constLogger=std.log.scoped(.tinyrwm);
There's no need for scoping here, this is a single file just use the default scope. (e.g. std.log.err())
@ -0,0 +10,4 @@
fnpanic(comptimeformat:[]constu8,args:anytype)void{
Logger.err(format,args);
std.process.exit(1);
}
Use std.process.fatal() instead.
@ -0,0 +15,4 @@
constOutput=struct{
obj:*river.OutputV1,
removed:bool=false,
link:wl.list.Link=undefined,
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.
@ -0,0 +17,4 @@
removed:bool=false,
link:wl.list.Link=undefined,
fnhandleRemoved(output:*Output)void{
This function is pointless, inline it. Same goes for all the other trivial functions (handleClosed(), handleDimensions(), etc.)
@ -0,0 +26,4 @@
output.link.remove();
gpa.destroy(output);
}
fnlistener(_:*river.OutputV1,event:river.OutputV1.Event,output:*Output)void{
Please add a blank line between function definitions.
@ -0,0 +48,4 @@
x:i32=undefined,
y:i32=undefined,
width:i32=undefined,
height:i32=undefined,
Code smell, don't use undefined as a default field value.
@ -0,0 +52,4 @@
pointer_move_requested:?*Seat=null,
pointer_resize_requested:?*Seat=null,
pointer_resize_requested_edges:river.WindowV1.Edges=undefined,
Use a tagged union for requested move/resize.
@ -0,0 +502,4 @@
else=>{},
}
}
fnsetListener(window_manager:*WindowManager)void{
Function is pointless, inline it.
@ -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)){
Skip the redundant mem.span() calls and use mem.orderZ()
@ -0,0 +541,4 @@
vario:std.Io=undefined;
varwm:WindowManager=undefined;
varxkb_bindings_v1:*river.XkbBindingsV1=undefined;
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?
@ -0,0 +490,4 @@
seat.setListener();
window_manager.seats.append(seat);
}
fnlistener(_:*river.WindowManagerV1,event:river.WindowManagerV1.Event,window_manager:*WindowManager)void{
Use the global wm rather than passing a *WindowManager pointer to this function to avoid confusion.
5be8cadf3d
12a4e3792c
Looking a bit better, here's another review pass.
@ -0,0 +47,4 @@
fnhandlePointerMoveRequested(window:*Window,river_seat:*river.SeatV1)void{
variter=wm.seats.iterator(.forward);
while(iter.next())|seat|{
There's no need for a loop here, use river_seat.getUserData() and cast the pointer instead.
Also please inline handlePointerMoveRequested and handlePointerResizeRequested
@ -0,0 +110,4 @@
window.setPosition(0,0);
window.obj.proposeDimensions(0,0);
}
if(window.pointer_request==.move){
Use a switch on window.pointer_request rather than consecutive if statements.
@ -0,0 +148,4 @@
}
fncreate(seat:*Seat,mods:river.SeatV1.Modifiers,keysym:comptime_int,action:Action)void{
varbinding=wm.gpa.create(XkbBinding)catch
should be const
@ -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.",.{});
I would write this as catch fatal("OOM"); and define const fatal = std.process.fatal; at the top of the file.
@ -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
This should likewise be catch fatal("OOM");, the current error message is nonsense.
@ -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.",.{});
See comments on the equivalent XkbBinding code above.
@ -0,0 +239,4 @@
op_release:bool=false,
op_start_width:i32,
op_start_height:i32,
op_edges:river.WindowV1.Edges=.{},
Most if not all of this op-related data would be better represented as a tagged union.
@ -0,0 +246,4 @@
.removed=>seat.removed=true,
.pointer_enter=>|args|{
if(args.window)|window|
seat.hovered=@ptrCast(@alignCast(window.getUserData()));
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.
@ -0,0 +251,4 @@
.pointer_leave=>seat.hovered=null,
.window_interaction=>|args|{
if(args.window)|window|
seat.interacted=@ptrCast(@alignCast(window.getUserData()));
Same as above, eliminate the if statement.
@ -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();
This can be simplified:
while(seat.xkb_bindings.first())|binding|binding.destroy();Same goes for the other instances of this pattern.
@ -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;
This is code smell. Please refactor this to eliminate the need for the orelse unreachable.
@ -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}),
This should not be a fatal error.
@ -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.?),
This .? assertion is unnecessary, please refactor this to avoid the need (i.e. use a second if statement).
Same below.
@ -0,0 +340,4 @@
constxkb_keysym_space=0x20;
constxkb_keysym_q=0x0071;
constxkb_keysym_n=0x006E;
constxkb_keysym_escape=0xFF1B;
Use zig-xkbcommon rather than hardcoding these.
@ -0,0 +342,4 @@
constxkb_keysym_n=0x006E;
constxkb_keysym_escape=0xFF1B;
constbutton_left=0x110;
constbutton_right=0x111;
I'd rather translate-c and import the linux/input-event-codes.h header than hardcode these. It would serve better as an example.
@ -0,0 +443,4 @@
}
fnhandleWindow(window_manager:*WindowManager,river_window:*river.WindowV1)void{
varwindow=wm.gpa.create(Window)catch
should be const
@ -0,0 +458,4 @@
}
fnhandleOutput(window_manager:*WindowManager,river_output:*river.OutputV1)void{
varoutput=wm.gpa.create(Output)catch
should be const
@ -0,0 +466,4 @@
}
fnhandleSeat(window_manager:*WindowManager,river_seat:*river.SeatV1)void{
varseat=wm.gpa.create(Seat)catch
should be const
@ -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.",.{}),
the return is redundant.
@ -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(),
The "handle" prefix is superfluous, rename these to just manageStart()/renderStart()
@ -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),
Please rename these to e.g. Window.create(ev.id).
@ -0,0 +496,4 @@
}
fninit(obj:*river.WindowManagerV1)void{
wm.obj=obj;
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.
@ -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.",.{});
"OOM"
@ -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);
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.
This illegal behavior has not been resolved.
12a4e3792c
1fd741756e
1fd741756e
a4e0a4179a
@ -0,0 +9,4 @@
constwl=wayland.client.wl;
constfatal=std.process.fatal;
constriver_window_manager_v1=4;
constriver_xkb_bindings_v1=3;
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
@ -0,0 +88,4 @@
.node=river_window.getNode()catchfatal("Unable to obtain Window's Node.",.{}),
.link=undefined,
.width=undefined,
.height=undefined,
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.
@ -0,0 +231,4 @@
constSeatOp=union(enum){
none,
move:struct{
window:?*Window,
Why is this an optional pointer? Same question for resize.
@ -0,0 +442,4 @@
io:std.Io,
obj:?*river.WindowManagerV1=null,
xkb_bindings:?*river.XkbBindingsV1=null,
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.
@ -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);
This is not idiomatic Zig, prefer imperative style.
@ -0,0 +531,4 @@
while(true)
if(display.dispatch()!=.SUCCESS)
fatal("Dispatch failed.",.{});
I quite dislike multi-line while/if statements without curlies, https://codeberg.org/river/river/src/branch/main/CONTRIBUTING.md#coding-style
a4e0a4179a
b20b1732eb
b20b1732eb
f9876238be
f9876238be
1044370196
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 :)
@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 :)
1044370196
94b1cf0b8b
94b1cf0b8b
12482f59a1
12482f59a1
7947427880
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.No due date set.
No dependencies set.
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?