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/main.zig b/src/main.zig index 6ebd8cf..86cbc12 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; @@ -9,11 +9,12 @@ 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", .{}); try server.init(); + defer server.deinit(); var buf: [11]u8 = undefined; const socket = try server.wl_server.addSocketAuto(&buf); diff --git a/src/output.zig b/src/output.zig index 915a551..63b1ac4 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,60 +7,69 @@ const gpa = std.heap.c_allocator; const wl = @import("wayland").server.wl; const wlr = @import("wlroots"); -const server = &@import("main.zig").server; +const Server = @import("server.zig"); -pub const Output = struct { - wlr_output: *wlr.Output, - scene_output: *wlr.SceneOutput, +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(wlr_output: *wlr.Output) !*Output { - 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) !*Output { + const output = try gpa.create(Output); - output.* = .{ - .wlr_output = wlr_output, - .scene_output = try server.root.scene.createSceneOutput(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); - return output; + 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); + server.scene_output_layout.addOutput(layout_output, scene_output); + + return output; +} + +pub fn handleRequestState( +listener: *wl.Listener(*wlr.Output.event.RequestState), +event: *wlr.Output.event.RequestState, + ) void { + const output: *Output = @fieldParentPtr("request_state", listener); + + if (!output.wlr_output.commitState(event.state)) { + std.log.warn("failed to set output state {}", .{event.state}); + } +} + +pub fn handleFrame(_: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { + const scene_output = server.scene.getSceneOutput(wlr_output); + + if(scene_output) |so| { + std.log.info("Rendering commitin scene output\n", .{}); + _ = so.commit(null); + + var now = posix.clock_gettime(posix.CLOCK.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported"); + so.sendFrameDone(&now); } - pub fn handleFrame(_: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { - const scene_output = server.scene.getSceneOutput(wlr_output); +} - if(scene_output) |so| { - std.log.info("Rendering commitin scene output\n", .{}); - _ = so.commit(null); +pub fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { + const output: *Output = @fieldParentPtr("destroy", listener); - var now = posix.clock_gettime(posix.CLOCK.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported"); - so.sendFrameDone(&now); - } + std.log.debug("removing output: {s}", .{output.*.wlr_output.*.name}); - } + output.frame.link.remove(); + output.request_state.link.remove(); + output.destroy.link.remove(); - 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); - } - - 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(); - - gpa.destroy(output); - } -}; + gpa.destroy(output); +} diff --git a/src/root.zig b/src/root.zig index 160a4cf..be6b7f1 100644 --- a/src/root.zig +++ b/src/root.zig @@ -3,7 +3,7 @@ const std = @import("std"); const wl = @import("wayland").server.wl; const wlr = @import("wlroots"); -const Output = @import("output.zig").Output; +const Output = @import("output.zig"); const server = &@import("main.zig").server; @@ -61,7 +61,7 @@ fn handleNewOutput(_: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { } if (!wlr_output.commitState(&state)) return; - const new_output = Output.create(wlr_output) catch { + const new_output = Output.create(server, wlr_output) catch { std.log.err("failed to allocate new output", .{}); wlr_output.destroy(); return; diff --git a/src/server.zig b/src/server.zig index 263eb09..13b917d 100644 --- a/src/server.zig +++ b/src/server.zig @@ -1,66 +1,239 @@ +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"); +const Keyboard = @import("keyboard.zig"); const Root = @import("root.zig").Root; -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, +session: ?*wlr.Session, +shm: *wlr.Shm, +wl_server: *wl.Server, +xdg_shell: *wlr.XdgShell, +root: Root, - wl_server: *wl.Server, - event_loop: *wl.EventLoop, - shm: *wlr.Shm, - scene: *wlr.Scene, - output_layout: *wlr.OutputLayout, - xdg_shell: *wlr.XdgShell, - seat: *wlr.Seat, +// Input things +seat: *wlr.Seat, +keyboards: wl.list.Head(Keyboard, .link) = undefined, +cursor: *wlr.Cursor, +cursor_mgr: *wlr.XcursorManager, - session: ?*wlr.Session, - backend: *wlr.Backend, - renderer: *wlr.Renderer, +// Listeners +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), - compositor: *wlr.Compositor, +pub fn init(server: *Server) !void { + const wl_server = try wl.Server.create(); + const event_loop = wl_server.getEventLoop(); - root: Root, + 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(); - pub fn init(server: *Server) !void { - const wl_server = try wl.Server.create(); - const event_loop = wl_server.getEventLoop(); + // Do we need to fail if session is NULL - 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(); + 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"), + .cursor = try wlr.Cursor.create(), + // TODO: let the user configure a cursor theme and side lua + .cursor_mgr = try wlr.XcursorManager.create(null, 24), + .root = undefined, + }; - // Do we need to fail if session is NULL + try server.renderer.initServer(wl_server); + try Root.init(&server.root); - server.* = .{ - .wl_server = wl_server, - .backend = backend, - .renderer = renderer, - .allocator = try wlr.Allocator.autocreate(backend, renderer), - .scene = scene, - .output_layout = 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_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(); - .root = undefined, - }; + 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); +} - try server.renderer.initServer(wl_server); +pub fn deinit(server: *Server) void { + server.wl_server.destroyClients(); - _ = 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.cursor.destroy(); + server.cursor_mgr.destroy(); - try Root.init(&server.root); + server.new_input.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(); +} + +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; + }, + .pointer => server.cursor.attachInputDevice(device), + else => {}, } + + server.seat.setCapabilities(.{ + .pointer = true, + .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); +}