aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/c.zig30
-rw-r--r--src/main.zig603
2 files changed, 484 insertions, 149 deletions
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,
+ if (prev_surface == surface) {
+ // Don't re-focus an already focused surface.
+ return;
+ }
- 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,
+ 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);
+ }
- 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,
+ // Move the view to the front
+ c.wl_list_remove(&view.*.link);
+ c.wl_list_insert(&server.*.views, &view.*.link);
- output_layout: ?*c.wlr_output_layout,
- outputs: c.wl_list,
- new_output: c.wl_listener,
+ // Activate the new surface
+ _ = c.wlr_xdg_toplevel_set_activated(view.*.xdg_surface, true);
- 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(),
- };
+ // 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;
}
-};
-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,
-};
+ // Allocate a View for this surface
+ var view = std.heap.c_allocator.create(View) catch unreachable;
+ view.*.server = server;
+ view.*.xdg_surface = xdg_surface;
-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,
-};
+ // Listen to the various events it can emit
+ view.*.map.notify = xdg_surface_map;
+ c.wl_signal_add(&xdg_surface.*.events.map, &view.*.map);
-const Keyboard = struct {
- link: c.wl_list,
- server: *Server,
- device: ?*c.wlr_input_device,
+ view.*.unmap.notify = xdg_surface_unmap;
+ c.wl_signal_add(&xdg_surface.*.events.unmap, &view.*.unmap);
- modifiers: c.wl_listener,
- key: c.wl_listener,
+ 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(&current_view.*.link);
+ c.wl_list_insert(server.*.views.prev, &current_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);
}