Down to 1 translate c error

This commit is contained in:
Isaac Freund 2020-03-20 22:44:08 +01:00
parent 7b735f690c
commit ba6b8350b1
No known key found for this signature in database
GPG key ID: 86DED400DDFD7A11
2 changed files with 488 additions and 153 deletions

30
src/c.zig Normal file
View file

@ -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);
}
};

View file

@ -1,24 +1,70 @@
const std = @import("std"); const std = @import("std");
const c = @import("c.zig").c;
const man_c = @import("c.zig").manual;
const c = @cImport({ const Server = struct {
@cDefine("WLR_USE_UNSTABLE", {}); wl_display: *c.wl_display,
@cInclude("wayland-server-core.h"); backend: *c.wlr_backend,
@cInclude("wlr/render/wlr_renderer.h"); renderer: *c.wlr_renderer,
@cInclude("wlr/types/wlr_cursor.h");
@cInclude("wlr/types/wlr_compositor.h"); xdg_shell: *c.wlr_xdg_shell,
@cInclude("wlr/types/wlr_data_device.h"); new_xdg_surface: c.wl_listener,
@cInclude("wlr/types/wlr_input_device.h"); views: c.wl_list,
@cInclude("wlr/types/wlr_keyboard.h");
@cInclude("wlr/types/wlr_matrix.h"); cursor: ?*c.wlr_cursor,
@cInclude("wlr/types/wlr_output.h"); cursor_mgr: ?*c.wlr_xcursor_manager,
@cInclude("wlr/types/wlr_output_layout.h"); cursor_motion: c.wl_listener,
@cInclude("wlr/types/wlr_pointer.h"); cursor_motion_absolute: c.wl_listener,
@cInclude("wlr/types/wlr_seat.h"); cursor_button: c.wl_listener,
@cInclude("wlr/types/wlr_xcursor_manager.h"); cursor_axis: c.wl_listener,
@cInclude("wlr/types/wlr_xdg_shell.h"); cursor_frame: c.wl_listener,
@cInclude("wlr/util/log.h");
@cInclude("xkbcommon/xkbcommon.h"); 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 { const CursorMode = enum {
Passthrough, Passthrough,
@ -26,16 +72,16 @@ const CursorMode = enum {
Resize, Resize,
}; };
fn create_list() c.wl_list { fn new_list() c.wl_list {
return c.wl_list{ return c.wl_list{
.prev = null, .prev = null,
.next = null, .next = null,
}; };
} }
fn create_listener() c.wl_listener { fn new_listener() c.wl_listener {
return c.wl_listener{ return c.wl_listener{
.link = create_list(), .link = new_list(),
.notify = null, .notify = null,
}; };
} }
@ -44,37 +90,94 @@ const RenderData = struct {
output: *c.wlr_output, output: *c.wlr_output,
renderer: *c.wlr_renderer, renderer: *c.wlr_renderer,
view: *View, 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, // 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 output = @fieldParentPtr(Output, "frame", listener);
var renderer = output.*.server.*.renderer; var renderer = output.*.server.*.renderer;
var now = undefined; var now: c.struct_timespec = undefined;
std.os.linux.clock_gettime(std.os.CLOCK_MONOTONIC, &now); _ = c.clock_gettime(c.CLOCK_MONOTONIC, &now);
// wlr_output_attach_render makes the OpenGL context current. // wlr_output_attach_render makes the OpenGL context current.
if (!c.wlr_output_attach_render(output.*.wlr_output, null)) { if (!c.wlr_output_attach_render(output.*.wlr_output, null)) {
return; return;
} }
// The "effective" resolution can change if you rotate your outputs. // The "effective" resolution can change if you rotate your outputs.
var width = undefined; var width: c_int = undefined;
var height = undefined; var height: c_int = undefined;
c.wlr_output_effective_resolution(output.*.wlr_output, &width, &height); c.wlr_output_effective_resolution(output.*.wlr_output, &width, &height);
// Begin the renderer (calls glViewport and some other GL sanity checks) // Begin the renderer (calls glViewport and some other GL sanity checks)
c.wlr_renderer_begin(renderer, width, height); c.wlr_renderer_begin(renderer, width, height);
const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 }; 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 // 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. // our view list is ordered front-to-back, we iterate over it backwards.
// wl_list_for_each_reverse(view, &output.*.server.*.views, link) { // 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) { while (&view.*.link != &output.*.server.*.views) {
if (!view.*.mapped) { 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); c.wlr_xdg_surface_for_each_surface(view.*.xdg_surface, render_surface, &rdata);
// Move to next item in list // 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 // 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 // Conclude rendering and swap the buffers, showing the final frame
// on-screen. // on-screen.
c.wlr_renderer_end(renderer); 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 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 // 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, // 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 // refresh rate), and each monitor supports only a specific set of modes. We
// just pick the monitor's preferred mode, a more sophisticated compositor // just pick the monitor's preferred mode, a more sophisticated compositor
// would let the user configure it. // 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_set_mode(wlr_output, mode);
c.wlr_output_enable(wlr_output, true); c.wlr_output_enable(wlr_output, true);
if (!c.wlr_output_commit(wlr_output)) { 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); c.wlr_output_create_global(wlr_output);
} }
const Server = struct { fn focus_view(view: *View, surface: *c.wlr_surface) void {
wl_display: *c.wl_display, const server = view.server;
backend: *c.wlr_backend, const seat = server.*.seat;
renderer: *c.wlr_renderer, const prev_surface = seat.*.keyboard_state.focused_surface;
xdg_shell: ?*c.wlr_xdg_shell, if (prev_surface == surface) {
new_xdg_surface: c.wl_listener, // Don't re-focus an already focused surface.
views: c.wl_list, 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,
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(),
};
} }
};
const Output = struct { if (prev_surface != null) {
link: c.wl_list, // Deactivate the previously focused surface. This lets the client know
server: *c.tinywl_server, // it no longer has focus and the client will repaint accordingly, e.g.
xdg_surface: ?*c.wlr_xdg_surface, // stop displaying a caret.
map: c.wl_listener, var prev_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(prev_surface);
unmap: c.wl_listener, _ = c.wlr_xdg_toplevel_set_activated(prev_xdg_surface, false);
destroy: c.wl_listener, }
request_move: c.wl_listener,
request_resize: c.wl_listener,
mapped: bool,
x: c_int,
y: c_int,
};
const View = struct { // Move the view to the front
link: c.wl_list, c.wl_list_remove(&view.*.link);
server: *Server, c.wl_list_insert(&server.*.views, &view.*.link);
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 { // Activate the new surface
link: c.wl_list, _ = c.wlr_xdg_toplevel_set_activated(view.*.xdg_surface, true);
server: *Server,
device: ?*c.wlr_input_device,
modifiers: c.wl_listener, // Tell the seat to have the keyboard enter this surface. wlroots will keep
key: c.wl_listener, // 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(&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 { pub fn main() !void {
@ -276,5 +489,97 @@ pub fn main() !void {
c.wlr_log_init(c.enum_wlr_log_importance.WLR_DEBUG, null); 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);
} }