From 293a9e2d3ec5212b39e757005a5546529640385c Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 16 Oct 2025 17:37:19 -0400 Subject: [PATCH 1/8] fix indentation --- src/output.zig | 80 +++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/output.zig b/src/output.zig index 8001cb9..e31ec76 100644 --- a/src/output.zig +++ b/src/output.zig @@ -8,57 +8,57 @@ const wlr = @import("wlroots"); const Server = @import("server.zig").Server; pub const Output = struct { - server: *Server, - wlr_output: *wlr.Output, + server: *Server, + wlr_output: *wlr.Output, - frame: wl.Listener(*wlr.Output) = .init(handleFrame), - request_state: wl.Listener(*wlr.Output.event.RequestState) = .init(handleRequestState), - destroy: wl.Listener(*wlr.Output) = .init(handleDestroy), + frame: wl.Listener(*wlr.Output) = .init(handleFrame), + request_state: wl.Listener(*wlr.Output.event.RequestState) = .init(handleRequestState), + destroy: wl.Listener(*wlr.Output) = .init(handleDestroy), - // The wlr.Output should be destroyed by the caller on failure to trigger cleanup. - pub fn create(server: *Server, wlr_output: *wlr.Output) !void { - const output = try gpa.create(Output); + // The wlr.Output should be destroyed by the caller on failure to trigger cleanup. + pub fn create(server: *Server, wlr_output: *wlr.Output) !void { + const output = try gpa.create(Output); - output.* = .{ - .server = server, - .wlr_output = wlr_output, - }; - wlr_output.events.frame.add(&output.frame); - wlr_output.events.request_state.add(&output.request_state); - wlr_output.events.destroy.add(&output.destroy); + output.* = .{ + .server = server, + .wlr_output = wlr_output, + }; + wlr_output.events.frame.add(&output.frame); + wlr_output.events.request_state.add(&output.request_state); + wlr_output.events.destroy.add(&output.destroy); - const layout_output = try server.output_layout.addAuto(wlr_output); + const layout_output = try server.output_layout.addAuto(wlr_output); - const scene_output = try server.scene.createSceneOutput(wlr_output); - server.scene_output_layout.addOutput(layout_output, scene_output); - } + const scene_output = try server.scene.createSceneOutput(wlr_output); + server.scene_output_layout.addOutput(layout_output, scene_output); + } - pub fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { - const output: *Output = @fieldParentPtr("frame", listener); + pub fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { + const output: *Output = @fieldParentPtr("frame", listener); - const scene_output = output.server.scene.getSceneOutput(output.wlr_output).?; - _ = scene_output.commit(null); + const scene_output = output.server.scene.getSceneOutput(output.wlr_output).?; + _ = scene_output.commit(null); - var now = posix.clock_gettime(posix.CLOCK.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported"); - scene_output.sendFrameDone(&now); - } + var now = posix.clock_gettime(posix.CLOCK.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported"); + scene_output.sendFrameDone(&now); + } - pub fn handleRequestState( - listener: *wl.Listener(*wlr.Output.event.RequestState), - event: *wlr.Output.event.RequestState, - ) void { - const output: *Output = @fieldParentPtr("request_state", listener); + pub fn handleRequestState( + listener: *wl.Listener(*wlr.Output.event.RequestState), + event: *wlr.Output.event.RequestState, + ) void { + const output: *Output = @fieldParentPtr("request_state", listener); - _ = output.wlr_output.commitState(event.state); - } + _ = output.wlr_output.commitState(event.state); + } - pub fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { - const output: *Output = @fieldParentPtr("destroy", listener); + pub fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { + const output: *Output = @fieldParentPtr("destroy", listener); - output.frame.link.remove(); - output.request_state.link.remove(); - output.destroy.link.remove(); + output.frame.link.remove(); + output.request_state.link.remove(); + output.destroy.link.remove(); - gpa.destroy(output); - } + gpa.destroy(output); + } }; From 0f13973300257aabdd20bf7780fe7d8e5b885430 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 16 Oct 2025 18:05:27 -0400 Subject: [PATCH 2/8] add logging to output.zig --- src/output.zig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/output.zig b/src/output.zig index e31ec76..57c2456 100644 --- a/src/output.zig +++ b/src/output.zig @@ -27,6 +27,8 @@ pub const Output = struct { wlr_output.events.request_state.add(&output.request_state); wlr_output.events.destroy.add(&output.destroy); + std.log.debug("adding output: {s}", .{output.*.wlr_output.*.name}); + const layout_output = try server.output_layout.addAuto(wlr_output); const scene_output = try server.scene.createSceneOutput(wlr_output); @@ -49,12 +51,16 @@ pub const Output = struct { ) void { const output: *Output = @fieldParentPtr("request_state", listener); - _ = output.wlr_output.commitState(event.state); + if (!output.wlr_output.commitState(event.state)) { + std.log.warn("failed to set output state {}", .{event.state}); + } } pub fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { const output: *Output = @fieldParentPtr("destroy", listener); + std.log.debug("removing output: {s}", .{output.*.wlr_output.*.name}); + output.frame.link.remove(); output.request_state.link.remove(); output.destroy.link.remove(); From d4efb73d5cef04833457bfc8def23c2756a56d26 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 16 Oct 2025 18:15:21 -0400 Subject: [PATCH 3/8] switch to @This(); for output and server as they don't really have anything else --- src/main.zig | 2 +- src/output.zig | 90 +++++++++++++++++----------------- src/server.zig | 130 ++++++++++++++++++++++++------------------------- 3 files changed, 110 insertions(+), 112 deletions(-) diff --git a/src/main.zig b/src/main.zig index e142957..8f05fa2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,7 +1,7 @@ const std = @import("std"); const wlr = @import("wlroots"); -const Server = @import("server.zig").Server; +const Server = @import("server.zig"); const gpa = std.heap.c_allocator; diff --git a/src/output.zig b/src/output.zig index 57c2456..1a271d3 100644 --- a/src/output.zig +++ b/src/output.zig @@ -1,3 +1,5 @@ +const Output = @This(); + const std = @import("std"); const posix = std.posix; const gpa = std.heap.c_allocator; @@ -5,66 +7,64 @@ const gpa = std.heap.c_allocator; const wl = @import("wayland").server.wl; const wlr = @import("wlroots"); -const Server = @import("server.zig").Server; +const Server = @import("server.zig"); -pub const Output = struct { - server: *Server, - wlr_output: *wlr.Output, +server: *Server, +wlr_output: *wlr.Output, - frame: wl.Listener(*wlr.Output) = .init(handleFrame), - request_state: wl.Listener(*wlr.Output.event.RequestState) = .init(handleRequestState), - destroy: wl.Listener(*wlr.Output) = .init(handleDestroy), +frame: wl.Listener(*wlr.Output) = .init(handleFrame), +request_state: wl.Listener(*wlr.Output.event.RequestState) = .init(handleRequestState), +destroy: wl.Listener(*wlr.Output) = .init(handleDestroy), - // The wlr.Output should be destroyed by the caller on failure to trigger cleanup. - pub fn create(server: *Server, wlr_output: *wlr.Output) !void { - const output = try gpa.create(Output); +// The wlr.Output should be destroyed by the caller on failure to trigger cleanup. +pub fn create(server: *Server, wlr_output: *wlr.Output) !void { + const output = try gpa.create(Output); - output.* = .{ - .server = server, - .wlr_output = wlr_output, - }; - wlr_output.events.frame.add(&output.frame); - wlr_output.events.request_state.add(&output.request_state); - wlr_output.events.destroy.add(&output.destroy); + output.* = .{ + .server = server, + .wlr_output = wlr_output, + }; + wlr_output.events.frame.add(&output.frame); + wlr_output.events.request_state.add(&output.request_state); + wlr_output.events.destroy.add(&output.destroy); - std.log.debug("adding output: {s}", .{output.*.wlr_output.*.name}); + std.log.debug("adding output: {s}", .{output.*.wlr_output.*.name}); - const layout_output = try server.output_layout.addAuto(wlr_output); + const layout_output = try server.output_layout.addAuto(wlr_output); - const scene_output = try server.scene.createSceneOutput(wlr_output); - server.scene_output_layout.addOutput(layout_output, scene_output); - } + const scene_output = try server.scene.createSceneOutput(wlr_output); + server.scene_output_layout.addOutput(layout_output, scene_output); +} - pub fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { - const output: *Output = @fieldParentPtr("frame", listener); +pub fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { + const output: *Output = @fieldParentPtr("frame", listener); - const scene_output = output.server.scene.getSceneOutput(output.wlr_output).?; - _ = scene_output.commit(null); + const scene_output = output.server.scene.getSceneOutput(output.wlr_output).?; + _ = scene_output.commit(null); - var now = posix.clock_gettime(posix.CLOCK.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported"); - scene_output.sendFrameDone(&now); - } + var now = posix.clock_gettime(posix.CLOCK.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported"); + scene_output.sendFrameDone(&now); +} - pub fn handleRequestState( - listener: *wl.Listener(*wlr.Output.event.RequestState), - event: *wlr.Output.event.RequestState, +pub fn handleRequestState( +listener: *wl.Listener(*wlr.Output.event.RequestState), +event: *wlr.Output.event.RequestState, ) void { - const output: *Output = @fieldParentPtr("request_state", listener); + const output: *Output = @fieldParentPtr("request_state", listener); - if (!output.wlr_output.commitState(event.state)) { - std.log.warn("failed to set output state {}", .{event.state}); - } + if (!output.wlr_output.commitState(event.state)) { + std.log.warn("failed to set output state {}", .{event.state}); } +} - pub fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { - const output: *Output = @fieldParentPtr("destroy", listener); +pub fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { + const output: *Output = @fieldParentPtr("destroy", listener); - std.log.debug("removing output: {s}", .{output.*.wlr_output.*.name}); + std.log.debug("removing output: {s}", .{output.*.wlr_output.*.name}); - output.frame.link.remove(); - output.request_state.link.remove(); - output.destroy.link.remove(); + output.frame.link.remove(); + output.request_state.link.remove(); + output.destroy.link.remove(); - gpa.destroy(output); - } -}; + gpa.destroy(output); +} diff --git a/src/server.zig b/src/server.zig index 85b5e76..bf3fbbc 100644 --- a/src/server.zig +++ b/src/server.zig @@ -1,86 +1,84 @@ +const Server = @This(); + const std = @import("std"); const gpa = std.heap.c_allocator; const wl = @import("wayland").server.wl; const wlr = @import("wlroots"); -const Output = @import("output.zig").Output; +const Output = @import("output.zig"); -pub const Server = struct { - allocator: *wlr.Allocator, +allocator: *wlr.Allocator, +backend: *wlr.Backend, +compositor: *wlr.Compositor, +event_loop: *wl.EventLoop, +output_layout: *wlr.OutputLayout, +renderer: *wlr.Renderer, +scene: *wlr.Scene, +scene_output_layout: *wlr.SceneOutputLayout, +seat: *wlr.Seat, +session: ?*wlr.Session, +shm: *wlr.Shm, +wl_server: *wl.Server, +xdg_shell: *wlr.XdgShell, - wl_server: *wl.Server, - event_loop: *wl.EventLoop, - shm: *wlr.Shm, - scene: *wlr.Scene, - output_layout: *wlr.OutputLayout, - scene_output_layout: *wlr.SceneOutputLayout, - xdg_shell: *wlr.XdgShell, - seat: *wlr.Seat, +// Listeners +new_output: wl.Listener(*wlr.Output) = .init(newOutput), - session: ?*wlr.Session, - backend: *wlr.Backend, - renderer: *wlr.Renderer, +pub fn init(server: *Server) !void { + const wl_server = try wl.Server.create(); + const event_loop = wl_server.getEventLoop(); - compositor: *wlr.Compositor, + var session: ?*wlr.Session = undefined; + const backend = try wlr.Backend.autocreate(event_loop, &session); + const renderer = try wlr.Renderer.autocreate(backend); + const output_layout = try wlr.OutputLayout.create(wl_server); + const scene = try wlr.Scene.create(); - new_output: wl.Listener(*wlr.Output) = .init(newOutput), + // Do we need to fail if session is NULL - pub fn init(server: *Server) !void { - const wl_server = try wl.Server.create(); - const event_loop = wl_server.getEventLoop(); + server.* = .{ + .wl_server = wl_server, + .backend = backend, + .renderer = renderer, + .allocator = try wlr.Allocator.autocreate(backend, renderer), + .scene = scene, + .output_layout = output_layout, + .scene_output_layout = try scene.attachOutputLayout(output_layout), + .xdg_shell = try wlr.XdgShell.create(wl_server, 2), + .event_loop = event_loop, + .session = session, + .compositor = try wlr.Compositor.create(wl_server, 6, renderer), + .shm = try wlr.Shm.createWithRenderer(wl_server, 1, renderer), + .seat = try wlr.Seat.create(wl_server, "default"), + }; - var session: ?*wlr.Session = undefined; - const backend = try wlr.Backend.autocreate(event_loop, &session); - const renderer = try wlr.Renderer.autocreate(backend); - const output_layout = try wlr.OutputLayout.create(wl_server); - const scene = try wlr.Scene.create(); + try server.renderer.initServer(wl_server); - // Do we need to fail if session is NULL + _ = try wlr.Compositor.create(server.wl_server, 6, server.renderer); + _ = try wlr.Subcompositor.create(server.wl_server); + _ = try wlr.DataDeviceManager.create(server.wl_server); - server.* = .{ - .wl_server = wl_server, - .backend = backend, - .renderer = renderer, - .allocator = try wlr.Allocator.autocreate(backend, renderer), - .scene = scene, - .output_layout = output_layout, - .scene_output_layout = try scene.attachOutputLayout(output_layout), - .xdg_shell = try wlr.XdgShell.create(wl_server, 2), - .event_loop = event_loop, - .session = session, - .compositor = try wlr.Compositor.create(wl_server, 6, renderer), - .shm = try wlr.Shm.createWithRenderer(wl_server, 1, renderer), - .seat = try wlr.Seat.create(wl_server, "default"), - }; + server.backend.events.new_output.add(&server.new_output); +} - try server.renderer.initServer(wl_server); +fn newOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { + const server: *Server = @fieldParentPtr("new_output", listener); - _ = try wlr.Compositor.create(server.wl_server, 6, server.renderer); - _ = try wlr.Subcompositor.create(server.wl_server); - _ = try wlr.DataDeviceManager.create(server.wl_server); + if (!wlr_output.initRender(server.allocator, server.renderer)) return; - server.backend.events.new_output.add(&server.new_output); + var state = wlr.Output.State.init(); + defer state.finish(); + + state.setEnabled(true); + if (wlr_output.preferredMode()) |mode| { + state.setMode(mode); } + if (!wlr_output.commitState(&state)) return; - fn newOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { - const server: *Server = @fieldParentPtr("new_output", listener); - - if (!wlr_output.initRender(server.allocator, server.renderer)) return; - - var state = wlr.Output.State.init(); - defer state.finish(); - - state.setEnabled(true); - if (wlr_output.preferredMode()) |mode| { - state.setMode(mode); - } - if (!wlr_output.commitState(&state)) return; - - Output.create(server, wlr_output) catch { - std.log.err("failed to allocate new output", .{}); - wlr_output.destroy(); - return; - }; - } -}; + Output.create(server, wlr_output) catch { + std.log.err("failed to allocate new output", .{}); + wlr_output.destroy(); + return; + }; +} From 1bc006cec7d7a70be9945fa9eec11e7456a6040c Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 16 Oct 2025 22:21:42 -0400 Subject: [PATCH 4/8] add keyboard support --- src/keyboard.zig | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ src/server.zig | 32 ++++++++++++++--- 2 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 src/keyboard.zig diff --git a/src/keyboard.zig b/src/keyboard.zig new file mode 100644 index 0000000..49cd989 --- /dev/null +++ b/src/keyboard.zig @@ -0,0 +1,91 @@ +const Keyboard = @This(); + +const std = @import("std"); +const gpa = std.heap.c_allocator; + +const wl = @import("wayland").server.wl; +const wlr = @import("wlroots"); +const xkb = @import("xkbcommon"); + +const Server = @import("server.zig"); + +server: *Server, +link: wl.list.Link = undefined, +device: *wlr.InputDevice, + +modifiers: wl.Listener(*wlr.Keyboard) = .init(handleModifiers), +key: wl.Listener(*wlr.Keyboard.event.Key) = .init(handleKey), +destroy: wl.Listener(*wlr.InputDevice) = .init(handleDestroy), + +pub fn create(server: *Server, device: *wlr.InputDevice) !void { + const keyboard = try gpa.create(Keyboard); + errdefer gpa.destroy(keyboard); + + keyboard.* = .{ + .server = server, + .device = device, + }; + + const context = xkb.Context.new(.no_flags) orelse return error.ContextFailed; + defer context.unref(); + const keymap = xkb.Keymap.newFromNames(context, null, .no_flags) orelse return error.KeymapFailed; + defer keymap.unref(); + + const wlr_keyboard = device.toKeyboard(); + // TODO: configure this via lua later + if (!wlr_keyboard.setKeymap(keymap)) return error.SetKeymapFailed; + wlr_keyboard.setRepeatInfo(25, 600); + + wlr_keyboard.events.modifiers.add(&keyboard.modifiers); + wlr_keyboard.events.key.add(&keyboard.key); + device.events.destroy.add(&keyboard.destroy); + + std.log.debug("adding keyboard: {s}", .{keyboard.*.device.*.name orelse "(null)"}); + + server.seat.setKeyboard(wlr_keyboard); + server.keyboards.append(keyboard); +} + +pub fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), wlr_keyboard: *wlr.Keyboard) void { + const keyboard: *Keyboard = @fieldParentPtr("modifiers", listener); + keyboard.server.seat.setKeyboard(wlr_keyboard); + keyboard.server.seat.keyboardNotifyModifiers(&wlr_keyboard.modifiers); +} + +pub fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboard.event.Key) void { + const keyboard: *Keyboard = @fieldParentPtr("key", listener); + const wlr_keyboard = keyboard.device.toKeyboard(); + + // Translate libinput keycode -> xkbcommon + // const keycode = event.keycode + 8; + + // TODO: lua handle keybinds here + const handled = false; + if (wlr_keyboard.getModifiers().alt and event.state == .pressed) { + // for (wlr_keyboard.xkb_state.?.keyGetSyms(keycode)) |sym| { + // if (keyboard.server.handleKeybind(sym)) { + // handled = true; + // break; + // } + // } + } + + if (!handled) { + keyboard.server.seat.setKeyboard(wlr_keyboard); + keyboard.server.seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state); + } +} + +pub fn handleDestroy(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice) void { + const keyboard: *Keyboard = @fieldParentPtr("destroy", listener); + + std.log.debug("removing keyboard: {s}", .{keyboard.*.device.*.name orelse "(null)"}); + + keyboard.link.remove(); + + keyboard.modifiers.link.remove(); + keyboard.key.link.remove(); + keyboard.destroy.link.remove(); + + gpa.destroy(keyboard); +} diff --git a/src/server.zig b/src/server.zig index bf3fbbc..5939a08 100644 --- a/src/server.zig +++ b/src/server.zig @@ -7,6 +7,7 @@ const wl = @import("wayland").server.wl; const wlr = @import("wlroots"); const Output = @import("output.zig"); +const Keyboard = @import("keyboard.zig"); allocator: *wlr.Allocator, backend: *wlr.Backend, @@ -16,14 +17,18 @@ output_layout: *wlr.OutputLayout, renderer: *wlr.Renderer, scene: *wlr.Scene, scene_output_layout: *wlr.SceneOutputLayout, -seat: *wlr.Seat, session: ?*wlr.Session, shm: *wlr.Shm, wl_server: *wl.Server, xdg_shell: *wlr.XdgShell, +// Input things +seat: *wlr.Seat, +keyboards: wl.list.Head(Keyboard, .link) = undefined, + // Listeners new_output: wl.Listener(*wlr.Output) = .init(newOutput), +new_input: wl.Listener(*wlr.InputDevice) = .init(newInput), pub fn init(server: *Server) !void { const wl_server = try wl.Server.create(); @@ -55,11 +60,10 @@ pub fn init(server: *Server) !void { try server.renderer.initServer(wl_server); - _ = try wlr.Compositor.create(server.wl_server, 6, server.renderer); - _ = try wlr.Subcompositor.create(server.wl_server); - _ = try wlr.DataDeviceManager.create(server.wl_server); - server.backend.events.new_output.add(&server.new_output); + server.backend.events.new_input.add(&server.new_input); + + server.keyboards.init(); } fn newOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { @@ -82,3 +86,21 @@ fn newOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void return; }; } + +fn newInput(listener: *wl.Listener(*wlr.InputDevice), device: *wlr.InputDevice) void { + const server: *Server = @fieldParentPtr("new_input", listener); + switch (device.type) { + .keyboard => Keyboard.create(server, device) catch |err| { + std.log.err("failed to create keyboard: {}", .{err}); + return; + }, + // TODO: impl cursor + // .pointer => server.cursor.attachInputDevice(device), + else => {}, + } + + server.seat.setCapabilities(.{ + .pointer = true, + .keyboard = server.keyboards.length() > 0, + }); +} From 7fa759fd2944b21a5d9c4f7a9769e379626d4bf1 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 17 Oct 2025 20:37:36 -0400 Subject: [PATCH 5/8] free the memory we allocate --- src/main.zig | 1 + src/server.zig | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/main.zig b/src/main.zig index 8f05fa2..c0aa561 100644 --- a/src/main.zig +++ b/src/main.zig @@ -11,6 +11,7 @@ pub fn main() !void { var server: Server = undefined; try server.init(); + defer server.deinit(); var buf: [11]u8 = undefined; const socket = try server.wl_server.addSocketAuto(&buf); diff --git a/src/server.zig b/src/server.zig index 5939a08..da7e028 100644 --- a/src/server.zig +++ b/src/server.zig @@ -66,6 +66,16 @@ pub fn init(server: *Server) !void { server.keyboards.init(); } +pub fn deinit(server: *Server) void { + server.wl_server.destroyClients(); + + server.new_input.link.remove(); + server.new_output.link.remove(); + + server.backend.destroy(); + server.wl_server.destroy(); +} + fn newOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { const server: *Server = @fieldParentPtr("new_output", listener); From 8360569d4cc3a180d2faf0c214f9bd6086d20cf2 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 17 Oct 2025 21:12:02 -0400 Subject: [PATCH 6/8] we have a cursor! --- src/server.zig | 139 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 3 deletions(-) diff --git a/src/server.zig b/src/server.zig index da7e028..4c6cad1 100644 --- a/src/server.zig +++ b/src/server.zig @@ -25,10 +25,19 @@ xdg_shell: *wlr.XdgShell, // Input things seat: *wlr.Seat, keyboards: wl.list.Head(Keyboard, .link) = undefined, +cursor: *wlr.Cursor, +cursor_mgr: *wlr.XcursorManager, // Listeners new_output: wl.Listener(*wlr.Output) = .init(newOutput), new_input: wl.Listener(*wlr.InputDevice) = .init(newInput), +cursor_motion: wl.Listener(*wlr.Pointer.event.Motion) = .init(cursorMotion), +cursor_motion_absolute: wl.Listener(*wlr.Pointer.event.MotionAbsolute) = .init(cursorMotionAbsolute), +cursor_button: wl.Listener(*wlr.Pointer.event.Button) = .init(cursorButton), +cursor_axis: wl.Listener(*wlr.Pointer.event.Axis) = .init(cursorAxis), +cursor_frame: wl.Listener(*wlr.Cursor) = .init(cursorFrame), +request_set_cursor: wl.Listener(*wlr.Seat.event.RequestSetCursor) = .init(requestSetCursor), +request_set_selection: wl.Listener(*wlr.Seat.event.RequestSetSelection) = .init(requestSetSelection), pub fn init(server: *Server) !void { const wl_server = try wl.Server.create(); @@ -56,14 +65,27 @@ pub fn init(server: *Server) !void { .compositor = try wlr.Compositor.create(wl_server, 6, renderer), .shm = try wlr.Shm.createWithRenderer(wl_server, 1, renderer), .seat = try wlr.Seat.create(wl_server, "default"), + .cursor = try wlr.Cursor.create(), + // TODO: let the user configure a cursor theme and side lua + .cursor_mgr = try wlr.XcursorManager.create(null, 24), }; try server.renderer.initServer(wl_server); server.backend.events.new_output.add(&server.new_output); - server.backend.events.new_input.add(&server.new_input); + server.backend.events.new_input.add(&server.new_input); + server.seat.events.request_set_cursor.add(&server.request_set_cursor); + server.seat.events.request_set_selection.add(&server.request_set_selection); server.keyboards.init(); + + server.cursor.attachOutputLayout(server.output_layout); + try server.cursor_mgr.load(1); + server.cursor.events.motion.add(&server.cursor_motion); + server.cursor.events.motion_absolute.add(&server.cursor_motion_absolute); + server.cursor.events.button.add(&server.cursor_button); + server.cursor.events.axis.add(&server.cursor_axis); + server.cursor.events.frame.add(&server.cursor_frame); } pub fn deinit(server: *Server) void { @@ -104,8 +126,7 @@ fn newInput(listener: *wl.Listener(*wlr.InputDevice), device: *wlr.InputDevice) std.log.err("failed to create keyboard: {}", .{err}); return; }, - // TODO: impl cursor - // .pointer => server.cursor.attachInputDevice(device), + .pointer => server.cursor.attachInputDevice(device), else => {}, } @@ -114,3 +135,115 @@ fn newInput(listener: *wl.Listener(*wlr.InputDevice), device: *wlr.InputDevice) .keyboard = server.keyboards.length() > 0, }); } + +fn cursorMotion( + listener: *wl.Listener(*wlr.Pointer.event.Motion), + event: *wlr.Pointer.event.Motion, +) void { + const server: *Server = @fieldParentPtr("cursor_motion", listener); + server.cursor.move(event.device, event.delta_x, event.delta_y); + server.processCursorMotion(event.time_msec); +} + +fn cursorMotionAbsolute( + listener: *wl.Listener(*wlr.Pointer.event.MotionAbsolute), + event: *wlr.Pointer.event.MotionAbsolute, +) void { + const server: *Server = @fieldParentPtr("cursor_motion_absolute", listener); + server.cursor.warpAbsolute(event.device, event.x, event.y); + server.processCursorMotion(event.time_msec); +} + +fn processCursorMotion(server: *Server, time_msec: u32) void { + if (server.viewAt(server.cursor.x, server.cursor.y)) |res| { + server.seat.pointerNotifyEnter(res.surface, res.sx, res.sy); + server.seat.pointerNotifyMotion(time_msec, res.sx, res.sy); + } else { + server.cursor.setXcursor(server.cursor_mgr, "default"); + server.seat.pointerClearFocus(); + } +} + +const ViewAtResult = struct { + // TODO: uncomment when we have toplevels + // toplevel: *Toplevel, + surface: *wlr.Surface, + sx: f64, + sy: f64, +}; + +fn viewAt(server: *Server, lx: f64, ly: f64) ?ViewAtResult { + var sx: f64 = undefined; + var sy: f64 = undefined; + if (server.scene.tree.node.at(lx, ly, &sx, &sy)) |node| { + if (node.type != .buffer) return null; + // TODO: uncomment when we have toplevels + // const scene_buffer = wlr.SceneBuffer.fromNode(node); + // const scene_surface = wlr.SceneSurface.tryFromBuffer(scene_buffer) orelse return null; + + var it: ?*wlr.SceneTree = node.parent; + while (it) |n| : (it = n.node.parent) { + // if (@as(?*Toplevel, @ptrCast(@alignCast(n.node.data)))) |toplevel| { + // return ViewAtResult{ + // .toplevel = toplevel, + // .surface = scene_surface.surface, + // .sx = sx, + // .sy = sy, + // }; + // } + } + } + return null; +} + +fn cursorButton( + listener: *wl.Listener(*wlr.Pointer.event.Button), + event: *wlr.Pointer.event.Button, +) void { + const server: *Server = @fieldParentPtr("cursor_button", listener); + _ = server.seat.pointerNotifyButton(event.time_msec, event.button, event.state); + // TODO: figure out what this listener is supposed to do + + // if (event.state == .released) { + // server.cursor_mode = .passthrough; + // } else if (server.viewAt(server.cursor.x, server.cursor.y)) |res| { + // server.focusView(res.toplevel, res.surface); + // } +} + +fn cursorAxis( + listener: *wl.Listener(*wlr.Pointer.event.Axis), + event: *wlr.Pointer.event.Axis, +) void { + const server: *Server = @fieldParentPtr("cursor_axis", listener); + server.seat.pointerNotifyAxis( + event.time_msec, + event.orientation, + event.delta, + event.delta_discrete, + event.source, + event.relative_direction, + ); +} + +fn cursorFrame(listener: *wl.Listener(*wlr.Cursor), _: *wlr.Cursor) void { + const server: *Server = @fieldParentPtr("cursor_frame", listener); + server.seat.pointerNotifyFrame(); +} + +fn requestSetCursor( + listener: *wl.Listener(*wlr.Seat.event.RequestSetCursor), + event: *wlr.Seat.event.RequestSetCursor, +) void { + const server: *Server = @fieldParentPtr("request_set_cursor", listener); + if (event.seat_client == server.seat.pointer_state.focused_client) + server.cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y); +} + +fn requestSetSelection( + listener: *wl.Listener(*wlr.Seat.event.RequestSetSelection), + event: *wlr.Seat.event.RequestSetSelection, +) void { + const server: *Server = @fieldParentPtr("request_set_selection", listener); + server.seat.setSelection(event.source, event.serial); +} From e06c076c06ebb2975a3dc65e839aa80a34214adb Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 17 Oct 2025 21:14:57 -0400 Subject: [PATCH 7/8] free more memory --- src/server.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/server.zig b/src/server.zig index 4c6cad1..f81a198 100644 --- a/src/server.zig +++ b/src/server.zig @@ -91,10 +91,21 @@ pub fn init(server: *Server) !void { pub fn deinit(server: *Server) void { server.wl_server.destroyClients(); + server.cursor.destroy(); + server.cursor_mgr.destroy(); + server.new_input.link.remove(); server.new_output.link.remove(); + server.cursor_motion.link.remove(); + server.cursor_motion_absolute.link.remove(); + server.cursor_button.link.remove(); + server.cursor_axis.link.remove(); + server.cursor_frame.link.remove(); + server.request_set_cursor.link.remove(); + server.request_set_selection.link.remove(); server.backend.destroy(); + server.seat.destroy(); server.wl_server.destroy(); } From 32cfffb996c1b776d972748b5a12f29f95a27553 Mon Sep 17 00:00:00 2001 From: Squibid Date: Sat, 18 Oct 2025 17:43:08 -0400 Subject: [PATCH 8/8] disable the wlroots debugging --- src/main.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.zig b/src/main.zig index dc0a6d0..86cbc12 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,7 +9,7 @@ const gpa = std.heap.c_allocator; pub var server: Server = undefined; pub fn main() !void { - wlr.log.init(.debug, null); + // wlr.log.init(.debug, null); std.log.info("Starting mezzaluna", .{});