diff --git a/src/c.zig b/src/c.zig new file mode 100644 index 0000000..57302a9 --- /dev/null +++ b/src/c.zig @@ -0,0 +1,30 @@ +// Functions that couldn't be automatically translated + +pub const c = @cImport({ + @cDefine("WLR_USE_UNSTABLE", {}); + @cInclude("time.h"); + @cInclude("stdlib.h"); + @cInclude("wayland-server-core.h"); + @cInclude("wlr/backend.h"); + @cInclude("wlr/render/wlr_renderer.h"); + @cInclude("wlr/types/wlr_cursor.h"); + @cInclude("wlr/types/wlr_compositor.h"); + @cInclude("wlr/types/wlr_data_device.h"); + @cInclude("wlr/types/wlr_input_device.h"); + @cInclude("wlr/types/wlr_keyboard.h"); + @cInclude("wlr/types/wlr_matrix.h"); + @cInclude("wlr/types/wlr_output.h"); + @cInclude("wlr/types/wlr_output_layout.h"); + @cInclude("wlr/types/wlr_pointer.h"); + @cInclude("wlr/types/wlr_seat.h"); + @cInclude("wlr/types/wlr_xcursor_manager.h"); + @cInclude("wlr/types/wlr_xdg_shell.h"); + @cInclude("wlr/util/log.h"); + @cInclude("xkbcommon/xkbcommon.h"); +}); + +pub const manual = struct { + pub inline fn xkb_map_new_from_names(context: var, names: var, flags: var) ?*c.struct_xkb_keymap { + return c.xkb_keymap_new_from_names(context, names, flags); + } +}; diff --git a/src/main.zig b/src/main.zig index ae41e6b..c9073af 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,24 +1,70 @@ const std = @import("std"); +const c = @import("c.zig").c; +const man_c = @import("c.zig").manual; -const c = @cImport({ - @cDefine("WLR_USE_UNSTABLE", {}); - @cInclude("wayland-server-core.h"); - @cInclude("wlr/render/wlr_renderer.h"); - @cInclude("wlr/types/wlr_cursor.h"); - @cInclude("wlr/types/wlr_compositor.h"); - @cInclude("wlr/types/wlr_data_device.h"); - @cInclude("wlr/types/wlr_input_device.h"); - @cInclude("wlr/types/wlr_keyboard.h"); - @cInclude("wlr/types/wlr_matrix.h"); - @cInclude("wlr/types/wlr_output.h"); - @cInclude("wlr/types/wlr_output_layout.h"); - @cInclude("wlr/types/wlr_pointer.h"); - @cInclude("wlr/types/wlr_seat.h"); - @cInclude("wlr/types/wlr_xcursor_manager.h"); - @cInclude("wlr/types/wlr_xdg_shell.h"); - @cInclude("wlr/util/log.h"); - @cInclude("xkbcommon/xkbcommon.h"); -}); +const Server = struct { + wl_display: *c.wl_display, + backend: *c.wlr_backend, + renderer: *c.wlr_renderer, + + xdg_shell: *c.wlr_xdg_shell, + new_xdg_surface: c.wl_listener, + views: c.wl_list, + + cursor: ?*c.wlr_cursor, + cursor_mgr: ?*c.wlr_xcursor_manager, + cursor_motion: c.wl_listener, + cursor_motion_absolute: c.wl_listener, + cursor_button: c.wl_listener, + cursor_axis: c.wl_listener, + cursor_frame: c.wl_listener, + + seat: *c.wlr_seat, + new_input: c.wl_listener, + request_cursor: c.wl_listener, + keyboards: c.wl_list, + cursor_mode: CursorMode, + grabbed_view: ?*View, + grab_x: f64, + grab_y: f64, + grab_width: c_int, + grab_height: c_int, + resize_edges: u32, + + output_layout: ?*c.wlr_output_layout, + outputs: c.wl_list, + new_output: c.wl_listener, +}; + +const Output = struct { + link: c.wl_list, + server: *Server, + wlr_output: *c.wlr_output, + frame: c.wl_listener, +}; + +const View = struct { + link: c.wl_list, + server: *Server, + xdg_surface: *c.wlr_xdg_surface, + map: c.wl_listener, + unmap: c.wl_listener, + destroy: c.wl_listener, + request_move: c.wl_listener, + request_resize: c.wl_listener, + mapped: bool, + x: c_int, + y: c_int, +}; + +const Keyboard = struct { + link: c.wl_list, + server: *Server, + device: *c.wlr_input_device, + + modifiers: c.wl_listener, + key: c.wl_listener, +}; const CursorMode = enum { Passthrough, @@ -26,16 +72,16 @@ const CursorMode = enum { Resize, }; -fn create_list() c.wl_list { +fn new_list() c.wl_list { return c.wl_list{ .prev = null, .next = null, }; } -fn create_listener() c.wl_listener { +fn new_listener() c.wl_listener { return c.wl_listener{ - .link = create_list(), + .link = new_list(), .notify = null, }; } @@ -44,37 +90,94 @@ const RenderData = struct { output: *c.wlr_output, renderer: *c.wlr_renderer, view: *View, - when: *std.os.timespec, + when: *c.struct_timespec, }; -fn output_frame(listener: *c.wl_listener, data: *c_void) void { +fn render_surface(surface: [*c]c.wlr_surface, sx: c_int, sy: c_int, data: ?*c_void) callconv(.C) void { + // This function is called for every surface that needs to be rendered. + var rdata = @ptrCast(*RenderData, @alignCast(@alignOf(RenderData), data)); + var view = rdata.*.view; + var output = rdata.*.output; + + // We first obtain a wlr_texture, which is a GPU resource. wlroots + // automatically handles negotiating these with the client. The underlying + // resource could be an opaque handle passed from the client, or the client + // could have sent a pixel buffer which we copied to the GPU, or a few other + // means. You don't have to worry about this, wlroots takes care of it. + var texture = c.wlr_surface_get_texture(surface); + if (texture == null) { + return; + } + + // The view has a position in layout coordinates. If you have two displays, + // one next to the other, both 1080p, a view on the rightmost display might + // have layout coordinates of 2000,100. We need to translate that to + // output-local coordinates, or (2000 - 1920). + var ox: f64 = 0.0; + var oy: f64 = 0.0; + c.wlr_output_layout_output_coords(view.*.server.*.output_layout, output, &ox, &oy); + ox += @intToFloat(f64, view.*.x + sx); + oy += @intToFloat(f64, view.*.y + sy); + + // We also have to apply the scale factor for HiDPI outputs. This is only + // part of the puzzle, TinyWL does not fully support HiDPI. + var box = c.wlr_box{ + .x = @floatToInt(c_int, ox * output.*.scale), + .y = @floatToInt(c_int, oy * output.*.scale), + .width = @floatToInt(c_int, @intToFloat(f32, surface.*.current.width) * output.*.scale), + .height = @floatToInt(c_int, @intToFloat(f32, surface.*.current.height) * output.*.scale), + }; + + // Those familiar with OpenGL are also familiar with the role of matricies + // in graphics programming. We need to prepare a matrix to render the view + // with. wlr_matrix_project_box is a helper which takes a box with a desired + // x, y coordinates, width and height, and an output geometry, then + // prepares an orthographic projection and multiplies the necessary + // transforms to produce a model-view-projection matrix. + // + // Naturally you can do this any way you like, for example to make a 3D + // compositor. + var matrix: [9]f32 = undefined; + var transform = c.wlr_output_transform_invert(surface.*.current.transform); + c.wlr_matrix_project_box(&matrix, &box, transform, 0.0, &output.*.transform_matrix); + + // This takes our matrix, the texture, and an alpha, and performs the actual + // rendering on the GPU. + _ = c.wlr_render_texture_with_matrix(rdata.*.renderer, texture, &matrix, 1.0); + + // This lets the client know that we've displayed that frame and it can + // prepare another one now if it likes. + c.wlr_surface_send_frame_done(surface, rdata.*.when); +} + +fn output_frame(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { // This function is called every time an output is ready to display a frame, - // generally at the output's refresh rate (e.g. 60Hz). */ + // generally at the output's refresh rate (e.g. 60Hz). var output = @fieldParentPtr(Output, "frame", listener); var renderer = output.*.server.*.renderer; - var now = undefined; - std.os.linux.clock_gettime(std.os.CLOCK_MONOTONIC, &now); + var now: c.struct_timespec = undefined; + _ = c.clock_gettime(c.CLOCK_MONOTONIC, &now); // wlr_output_attach_render makes the OpenGL context current. if (!c.wlr_output_attach_render(output.*.wlr_output, null)) { return; } // The "effective" resolution can change if you rotate your outputs. - var width = undefined; - var height = undefined; + var width: c_int = undefined; + var height: c_int = undefined; c.wlr_output_effective_resolution(output.*.wlr_output, &width, &height); // Begin the renderer (calls glViewport and some other GL sanity checks) c.wlr_renderer_begin(renderer, width, height); const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 }; - c.wlr_renderer_clear(renderer, color); + c.wlr_renderer_clear(renderer, &color); // Each subsequent window we render is rendered on top of the last. Because // our view list is ordered front-to-back, we iterate over it backwards. // wl_list_for_each_reverse(view, &output.*.server.*.views, link) { - var view = @fieldParentPtr(View, "link", &output.*.server.*.views.*.prev); + var view = @fieldParentPtr(View, "link", output.*.server.*.views.prev); while (&view.*.link != &output.*.server.*.views) { if (!view.*.mapped) { @@ -92,7 +195,7 @@ fn output_frame(listener: *c.wl_listener, data: *c_void) void { c.wlr_xdg_surface_for_each_surface(view.*.xdg_surface, render_surface, &rdata); // Move to next item in list - view = @fieldParentPtr(View, "link", view.*.link.*.prev); + view = @fieldParentPtr(View, "link", view.*.link.prev); } // Hardware cursors are rendered by the GPU on a separate plane, and can be @@ -106,20 +209,23 @@ fn output_frame(listener: *c.wl_listener, data: *c_void) void { // Conclude rendering and swap the buffers, showing the final frame // on-screen. c.wlr_renderer_end(renderer); - c.wlr_output_commit(output.*.wlr_output); + // TODO: handle failure + _ = c.wlr_output_commit(output.*.wlr_output); } -fn server_new_output(listener: *c.wl_listener, data: *c_void) void { +fn server_new_output(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { var server = @fieldParentPtr(Server, "new_output", listener); - var wlr_output = @ptrCast(c.wlr_output, data); + var wlr_output = @ptrCast(*c.wlr_output, @alignCast(@alignOf(*c.wlr_output), data)); // Some backends don't have modes. DRM+KMS does, and we need to set a mode // before we can use the output. The mode is a tuple of (width, height, // refresh rate), and each monitor supports only a specific set of modes. We // just pick the monitor's preferred mode, a more sophisticated compositor // would let the user configure it. - if (!c.wl_list_empty(&wlr_output.*.modes)) { - var mode = wlr_output_preferred_mode(wlr_output); + + // if not empty + if (c.wl_list_empty(&wlr_output.*.modes) == 0) { + var mode = c.wlr_output_preferred_mode(wlr_output); c.wlr_output_set_mode(wlr_output, mode); c.wlr_output_enable(wlr_output, true); if (!c.wlr_output_commit(wlr_output)) { @@ -148,127 +254,234 @@ fn server_new_output(listener: *c.wl_listener, data: *c_void) void { c.wlr_output_create_global(wlr_output); } -const Server = struct { - wl_display: *c.wl_display, - backend: *c.wlr_backend, - renderer: *c.wlr_renderer, +fn focus_view(view: *View, surface: *c.wlr_surface) void { + const server = view.server; + const seat = server.*.seat; + const prev_surface = seat.*.keyboard_state.focused_surface; - xdg_shell: ?*c.wlr_xdg_shell, - new_xdg_surface: c.wl_listener, - views: c.wl_list, - - cursor: ?*c.wlr_cursor, - cursor_mgr: ?*c.wlr_xcursor_manager, - cursor_motion: c.wl_listener, - cursor_motion_absolute: c.wl_listener, - cursor_button: c.wl_listener, - cursor_axis: c.wl_listener, - cursor_frame: c.wl_listener, - - seat: ?*c.wlr_seat, - new_input: c.wl_listener, - request_cursor: c.wl_listener, - keyboards: c.wl_list, - cursor_mode: CursorMode, - grabbed_view: ?*View, - grab_x: f64, - grab_y: f64, - grab_width: c_int, - grab_height: c_int, - resize_edges: u32, - - output_layout: ?*c.wlr_output_layout, - outputs: c.wl_list, - new_output: c.wl_listener, - - fn create() Server { - const wl_display = c.wl_display_create(); - const backend = c.wlr_backend_autocreate(wl_display, null); - const renderer = c.wlr_backend_get_renderer(server.backend); - wlr_renderer_init_wl_display(renderer, wl_display); - - wlr_compositor_create(wl_display, renderer); - wlr_data_device_manager_create(wl_display); - - const output_layout = wlr_output_layout_create(); - var outputs = create_list(); - wl_list_init(&outputs); - - new_output = create_listener(); - server.new_output.notify = server_new_output; - wl_signal_add(&server.backend.*.events.new_output, &server.new_output); - - return Server{ - .wl_display = wl_display, - .backend = backend, - .renderer = null, - - .xdg_shell = null, - .new_xdg_surface = create_listener(), - .views = create_list(), - - .cursor = null, - .cursor_mgr = null, - .cursor_motion = create_listener(), - .cursor_motion_absolute = create_listener(), - .cursor_button = create_listener(), - .cursor_axis = create_listener(), - .cursor_frame = create_listener(), - - .seat = null, - .new_input = create_listener(), - .request_cursor = create_listener(), - .keyboards = create_list(), - .cursor_mode = CursorMode.Passthrough, - .grabbed_view = null, - .grab_x = 0.0, - .grab_y = 0.0, - .grab_width = 0, - .grab_height = 0, - .resize_edges = 0, - - .output_layout = null, - .outputs = c.wl_list{ .prev = null, .next = null }, - .new_output = create_listener(), - }; + if (prev_surface == surface) { + // Don't re-focus an already focused surface. + return; } -}; -const Output = struct { - link: c.wl_list, - server: *c.tinywl_server, - xdg_surface: ?*c.wlr_xdg_surface, - map: c.wl_listener, - unmap: c.wl_listener, - destroy: c.wl_listener, - request_move: c.wl_listener, - request_resize: c.wl_listener, - mapped: bool, - x: c_int, - y: c_int, -}; + if (prev_surface != null) { + // Deactivate the previously focused surface. This lets the client know + // it no longer has focus and the client will repaint accordingly, e.g. + // stop displaying a caret. + var prev_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(prev_surface); + _ = c.wlr_xdg_toplevel_set_activated(prev_xdg_surface, false); + } -const View = struct { - link: c.wl_list, - server: *Server, - xdg_surface: ?*c.wlr_xdg_surface, - map: c.wl_listener, - unmap: c.wl_listener, - destroy: c.wl_listener, - request_move: c.wl_listener, - request_resize: c.wl_listener, - mapped: bool, - x: c_int, - y: c_int, -}; + // Move the view to the front + c.wl_list_remove(&view.*.link); + c.wl_list_insert(&server.*.views, &view.*.link); -const Keyboard = struct { - link: c.wl_list, - server: *Server, - device: ?*c.wlr_input_device, + // Activate the new surface + _ = c.wlr_xdg_toplevel_set_activated(view.*.xdg_surface, true); - modifiers: c.wl_listener, - key: c.wl_listener, + // Tell the seat to have the keyboard enter this surface. wlroots will keep + // track of this and automatically send key events to the appropriate + // clients without additional work on your part. + var keyboard = c.wlr_seat_get_keyboard(seat); + c.wlr_seat_keyboard_notify_enter(seat, view.*.xdg_surface.*.surface, &keyboard.*.keycodes, keyboard.*.num_keycodes, &keyboard.*.modifiers); +} + +fn xdg_surface_map(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // Called when the surface is mapped, or ready to display on-screen. + var view = @fieldParentPtr(View, "map", listener); + view.*.mapped = true; + focus_view(view, view.*.xdg_surface.*.surface); +} + +fn xdg_surface_unmap(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + var view = @fieldParentPtr(View, "map", listener); + view.*.mapped = false; +} + +fn xdg_surface_destroy(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + var view = @fieldParentPtr(View, "map", listener); + c.wl_list_remove(&view.*.link); + // TODO: free the memory +} + +fn xdg_toplevel_request_move(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // ignore for now +} + +fn xdg_toplevel_request_resize(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // ignore for now +} + +fn server_new_xdg_surface(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is raised when wlr_xdg_shell receives a new xdg surface from a + // client, either a toplevel (application window) or popup. + var server = @fieldParentPtr(Server, "new_xdg_surface", listener); + var xdg_surface = @ptrCast(*c.wlr_xdg_surface, @alignCast(@alignOf(*c.wlr_xdg_surface), data)); + + if (xdg_surface.*.role != c.enum_wlr_xdg_surface_role.WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + + // Allocate a View for this surface + var view = std.heap.c_allocator.create(View) catch unreachable; + view.*.server = server; + view.*.xdg_surface = xdg_surface; + + // Listen to the various events it can emit + view.*.map.notify = xdg_surface_map; + c.wl_signal_add(&xdg_surface.*.events.map, &view.*.map); + + view.*.unmap.notify = xdg_surface_unmap; + c.wl_signal_add(&xdg_surface.*.events.unmap, &view.*.unmap); + + view.*.destroy.notify = xdg_surface_destroy; + c.wl_signal_add(&xdg_surface.*.events.destroy, &view.*.destroy); + + var toplevel = xdg_surface.*.unnamed_161.toplevel; + view.*.request_move.notify = xdg_toplevel_request_move; + c.wl_signal_add(&toplevel.*.events.request_move, &view.*.request_move); + + view.*.request_resize.notify = xdg_toplevel_request_resize; + c.wl_signal_add(&toplevel.*.events.request_resize, &view.*.request_resize); + + // Add it to the list of views. + c.wl_list_insert(&server.*.views, &view.*.link); +} + +fn keyboard_handle_modifiers(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is raised when a modifier key, such as shift or alt, is + // pressed. We simply communicate this to the client. */ + var keyboard = @fieldParentPtr(Keyboard, "modifiers", listener); + + // A seat can only have one keyboard, but this is a limitation of the + // Wayland protocol - not wlroots. We assign all connected keyboards to the + // same seat. You can swap out the underlying wlr_keyboard like this and + // wlr_seat handles this transparently. + c.wlr_seat_set_keyboard(keyboard.*.server.*.seat, keyboard.*.device); + + // Send modifiers to the client. + c.wlr_seat_keyboard_notify_modifiers(keyboard.*.server.*.seat, &keyboard.*.device.*.unnamed_132.keyboard.*.modifiers); +} + +fn handle_keybinding(server: *Server, sym: c.xkb_keysym_t) bool { + // Here we handle compositor keybindings. This is when the compositor is + // processing keys, rather than passing them on to the client for its own + // processing. + // + // This function assumes the proper modifier is held down. + switch (sym) { + c.XKB_KEY_Escape => c.wl_display_terminate(server.*.wl_display), + c.XKB_KEY_F1 => { + // Cycle to the next view + if (c.wl_list_length(&server.*.views) > 1) { + const current_view = @fieldParentPtr(View, "link", server.*.views.next); + const next_view = @fieldParentPtr(View, "link", current_view.*.link.next); + focus_view(next_view, next_view.*.xdg_surface.*.surface); + // Move the previous view to the end of the list + c.wl_list_remove(¤t_view.*.link); + c.wl_list_insert(server.*.views.prev, ¤t_view.*.link); + } + }, + else => return false, + } + return true; +} + +fn keyboard_handle_key(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is raised when a key is pressed or released. + const keyboard = @fieldParentPtr(Keyboard, "key", listener); + const event = @ptrCast(*c.wlr_event_keyboard_key, @alignCast(@alignOf(*c.wlr_event_keyboard_key), data)); + + const server = keyboard.*.server; + const seat = server.*.seat; + const keyboard_device = keyboard.*.device.*.unnamed_132.keyboard; + + // Translate libinput keycode -> xkbcommon + const keycode = event.*.keycode + 8; + // Get a list of keysyms based on the keymap for this keyboard + var syms: [*c]c.xkb_keysym_t = undefined; + const nsyms = c.xkb_state_key_get_syms(keyboard_device.*.xkb_state, keycode, &syms); + + var handled = false; + const modifiers = c.wlr_keyboard_get_modifiers(keyboard_device); + if (modifiers & @intCast(u32, c.WLR_MODIFIER_LOGO) != 0 and event.*.state == c.enum_wlr_key_state.WLR_KEY_PRESSED) { + // If mod is held down and this button was _pressed_, we attempt to + // process it as a compositor keybinding. + var i: usize = 0; + while (i < nsyms) { + handled = handle_keybinding(server, syms[i]); + if (handled) { + break; + } + i += 1; + } + } + + if (!handled) { + // Otherwise, we pass it along to the client. + c.wlr_seat_set_keyboard(seat, keyboard.*.device); + c.wlr_seat_keyboard_notify_key(seat, event.*.time_msec, event.*.keycode, @intCast(u32, @enumToInt(event.*.state))); + } +} + +fn server_new_keyboard(server: *Server, device: *c.wlr_input_device) void { + var keyboard = std.heap.c_allocator.create(Keyboard) catch unreachable; + keyboard.*.server = server; + keyboard.*.device = device; + + // We need to prepare an XKB keymap and assign it to the keyboard. This + // assumes the defaults (e.g. layout = "us"). + const rules = c.xkb_rule_names{ + .rules = null, + .model = null, + .layout = null, + .variant = null, + .options = null, + }; + const context = c.xkb_context_new(c.enum_xkb_context_flags.XKB_CONTEXT_NO_FLAGS); + defer c.xkb_context_unref(context); + + const keymap = man_c.xkb_map_new_from_names(context, &rules, c.enum_xkb_keymap_compile_flags.XKB_KEYMAP_COMPILE_NO_FLAGS); + defer c.xkb_keymap_unref(keymap); + + var keyboard_device = device.*.unnamed_132.keyboard; + c.wlr_keyboard_set_keymap(keyboard_device, keymap); + c.wlr_keyboard_set_repeat_info(keyboard_device, 25, 600); + + // Setup listeners for keyboard events + keyboard.*.modifiers.notify = keyboard_handle_modifiers; + c.wl_signal_add(&keyboard_device.*.events.modifiers, &keyboard.*.modifiers); + keyboard.*.key.notify = keyboard_handle_key; + c.wl_signal_add(&keyboard_device.*.events.key, &keyboard.*.key); + + c.wlr_seat_set_keyboard(server.*.seat, device); + + // And add the keyboard to our list of keyboards + c.wl_list_insert(&server.*.keyboards, &keyboard.*.link); +} + +fn server_new_input(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is raised by the backend when a new input device becomes available. + var server = @fieldParentPtr(Server, "new_input", listener); + var device = @ptrCast(*c.wlr_input_device, @alignCast(@alignOf(*c.wlr_input_device), data)); + + switch (device.*.type) { + .WLR_INPUT_DEVICE_KEYBOARD => server_new_keyboard(server, device), + else => {}, + } + + var caps: u32 = 0; + // if list not empty + if (c.wl_list_empty(&server.*.keyboards) == 0) { + caps |= @intCast(u32, c.WL_SEAT_CAPABILITY_KEYBOARD); + } + c.wlr_seat_set_capabilities(server.*.seat, caps); +} + +const ZagError = error{ + CantAddSocket, + CantStartBackend, + CantSetEnv, }; pub fn main() !void { @@ -276,5 +489,97 @@ pub fn main() !void { c.wlr_log_init(c.enum_wlr_log_importance.WLR_DEBUG, null); - var server = Server.create(); + var server: Server = undefined; + // The Wayland display is managed by libwayland. It handles accepting + // clients from the Unix socket, manging Wayland globals, and so on. + server.wl_display = c.wl_display_create().?; + + // The backend is a wlroots feature which abstracts the underlying input and + // output hardware. The autocreate option will choose the most suitable + // backend based on the current environment, such as opening an X11 window + // if an X11 server is running. The NULL argument here optionally allows you + // to pass in a custom renderer if wlr_renderer doesn't meet your needs. The + // backend uses the renderer, for example, to fall back to software cursors + // if the backend does not support hardware cursors (some older GPUs + // don't). + server.backend = c.wlr_backend_autocreate(server.wl_display, null); + + // If we don't provide a renderer, autocreate makes a GLES2 renderer for us. + // The renderer is responsible for defining the various pixel formats it + // supports for shared memory, this configures that for clients. + server.renderer = c.wlr_backend_get_renderer(server.backend); + c.wlr_renderer_init_wl_display(server.renderer, server.wl_display); + + // This creates some hands-off wlroots interfaces. The compositor is + // necessary for clients to allocate surfaces and the data device manager + // handles the clipboard. Each of these wlroots interfaces has room for you + // to dig your fingers in and play with their behavior if you want. + _ = c.wlr_compositor_create(server.wl_display, server.renderer); + _ = c.wlr_data_device_manager_create(server.wl_display); + + // Creates an output layout, which a wlroots utility for working with an + // arrangement of screens in a physical layout. + server.output_layout = c.wlr_output_layout_create(); + c.wl_list_init(&server.outputs); + + // Configure a listener to be notified when new outputs are available on the + // backend. + server.new_output.notify = server_new_output; + c.wl_signal_add(&server.backend.*.events.new_output, &server.new_output); + + // Set up our list of views and the xdg-shell. The xdg-shell is a Wayland + // protocol which is used for application windows. + // https://drewdevault.com/2018/07/29/Wayland-shells.html + c.wl_list_init(&server.views); + server.xdg_shell = c.wlr_xdg_shell_create(server.wl_display); + server.new_xdg_surface.notify = server_new_xdg_surface; + c.wl_signal_add(&server.xdg_shell.*.events.new_surface, &server.new_xdg_surface); + + // Configures a seat, which is a single "seat" at which a user sits and + // operates the computer. This conceptually includes up to one keyboard, + // pointer, touch, and drawing tablet device. We also rig up a listener to + // let us know when new input devices are available on the backend. + c.wl_list_init(&server.keyboards); + server.new_input.notify = server_new_input; + c.wl_signal_add(&server.backend.*.events.new_input, &server.new_input); + server.seat = c.wlr_seat_create(server.wl_display, "seat0"); + // server.request_cursor.notify = seat_request_cursor; + // c.wl_signal_add(&server.seat.*.events.request_set_cursor, &server.request_cursor); + + // Add a Unix socket to the Wayland display. + const socket = c.wl_display_add_socket_auto(server.wl_display); + if (socket == null) { + c.wlr_backend_destroy(server.backend); + return ZagError.CantAddSocket; + } + + // Start the backend. This will enumerate outputs and inputs, become the DRM + // master, etc + if (!c.wlr_backend_start(server.backend)) { + c.wlr_backend_destroy(server.backend); + c.wl_display_destroy(server.wl_display); + return ZagError.CantStartBackend; + } + + // Set the WAYLAND_DISPLAY environment variable to our socket and run the + // startup command if requested. */ + if (c.setenv("WAYLAND_DISPLAY", socket, 1) == -1) { + return ZagError.CantSetEnv; + } + //if (startup_cmd) { + //if (std.os.linux.fork() == 0) { + //execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL); + //} + //} + + // Run the Wayland event loop. This does not return until you exit the + // compositor. Starting the backend rigged up all of the necessary event + // loop configuration to listen to libinput events, DRM events, generate + // frame events at the refresh rate, and so on. + //c.wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket); + c.wl_display_run(server.wl_display); + + // Once wl_display_run returns, we shut down the server. + c.wl_display_destroy_clients(server.wl_display); + c.wl_display_destroy(server.wl_display); }