From 57ab110f43efd2681fd61872820ac942b8f5bcd3 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 21 Mar 2020 16:55:42 +0100 Subject: [PATCH] Hack around @cImport() not handling flexible arrays --- build.zig | 2 + include/render.c | 20 +++ include/render.h | 27 ++++ src/c.zig | 6 +- src/main.zig | 312 +++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 342 insertions(+), 25 deletions(-) create mode 100644 include/render.c create mode 100644 include/render.h diff --git a/build.zig b/build.zig index 48eb612..f4303ce 100644 --- a/build.zig +++ b/build.zig @@ -17,6 +17,8 @@ pub fn build(b: *Builder) void { exe.addIncludeDir("protocol"); exe.linkLibC(); exe.addIncludeDir("/usr/include/pixman-1"); + exe.addCSourceFile("include/render.c", &[_][]const u8{"-std=c99"}); + exe.addIncludeDir("."); //exe.linkSystemLibrary("pixman"); exe.linkSystemLibrary("wayland-server"); exe.linkSystemLibrary("wlroots"); diff --git a/include/render.c b/include/render.c new file mode 100644 index 0000000..2a85fb7 --- /dev/null +++ b/include/render.c @@ -0,0 +1,20 @@ +#define WLR_USE_UNSTABLE +#include +#include + +struct wlr_backend *zag_wlr_backend_autocreate(struct wl_display *display) { + return wlr_backend_autocreate(display, NULL); +} + +struct wlr_renderer *zag_wlr_backend_get_renderer(struct wlr_backend *backend) { + return wlr_backend_get_renderer(backend); +} + +bool zag_wlr_backend_start(struct wlr_backend *backend) { + return wlr_backend_start(backend); +} + +void zag_wlr_backend_destroy(struct wlr_backend *backend) { + zag_wlr_backend_destroy(backend); +} + diff --git a/include/render.h b/include/render.h new file mode 100644 index 0000000..cb0bd52 --- /dev/null +++ b/include/render.h @@ -0,0 +1,27 @@ +#ifndef ZAG_RENDER_H +#define ZAG_RENDER_H + +#include + +struct wlr_backend_impl; + +struct wlr_backend { + const struct wlr_backend_impl *impl; + + struct { + /** Raised when destroyed, passed the wlr_backend reference */ + struct wl_signal destroy; + /** Raised when new inputs are added, passed the wlr_input_device */ + struct wl_signal new_input; + /** Raised when new outputs are added, passed the wlr_output */ + struct wl_signal new_output; + } events; +}; + +struct wlr_backend *zag_wlr_backend_autocreate(struct wl_display *display); +struct wlr_renderer *zag_wlr_backend_get_renderer(struct wlr_backend *backend); +bool zag_wlr_backend_start(struct wlr_backend *backend); +void zag_wlr_backend_destroy(struct wlr_backend *backend); + + +#endif diff --git a/src/c.zig b/src/c.zig index 57302a9..5169a17 100644 --- a/src/c.zig +++ b/src/c.zig @@ -5,8 +5,8 @@ pub const c = @cImport({ @cInclude("time.h"); @cInclude("stdlib.h"); @cInclude("wayland-server-core.h"); - @cInclude("wlr/backend.h"); - @cInclude("wlr/render/wlr_renderer.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"); @@ -21,6 +21,8 @@ pub const c = @cImport({ @cInclude("wlr/types/wlr_xdg_shell.h"); @cInclude("wlr/util/log.h"); @cInclude("xkbcommon/xkbcommon.h"); + + @cInclude("include/render.h"); }); pub const manual = struct { diff --git a/src/main.zig b/src/main.zig index 0b7eac2..0aa5fce 100644 --- a/src/main.zig +++ b/src/main.zig @@ -11,8 +11,8 @@ const Server = struct { new_xdg_surface: c.wl_listener, views: c.wl_list, - cursor: ?*c.wlr_cursor, - cursor_mgr: ?*c.wlr_xcursor_manager, + 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, @@ -31,7 +31,7 @@ const Server = struct { grab_height: c_int, resize_edges: u32, - output_layout: ?*c.wlr_output_layout, + output_layout: *c.wlr_output_layout, outputs: c.wl_list, new_output: c.wl_listener, }; @@ -337,7 +337,7 @@ fn server_new_xdg_surface(listener: [*c]c.wl_listener, data: ?*c_void) callconv( view.*.destroy.notify = xdg_surface_destroy; c.wl_signal_add(&xdg_surface.*.events.destroy, &view.*.destroy); - var toplevel = xdg_surface.*.unnamed_161.toplevel; + var toplevel = xdg_surface.*.unnamed_160.toplevel; view.*.request_move.notify = xdg_toplevel_request_move; c.wl_signal_add(&toplevel.*.events.request_move, &view.*.request_move); @@ -360,7 +360,7 @@ fn keyboard_handle_modifiers(listener: [*c]c.wl_listener, data: ?*c_void) callco 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); + c.wlr_seat_keyboard_notify_modifiers(keyboard.*.server.*.seat, &keyboard.*.device.*.unnamed_37.keyboard.*.modifiers); } fn handle_keybinding(server: *Server, sym: c.xkb_keysym_t) bool { @@ -394,7 +394,7 @@ fn keyboard_handle_key(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) const server = keyboard.*.server; const seat = server.*.seat; - const keyboard_device = keyboard.*.device.*.unnamed_132.keyboard; + const keyboard_device = keyboard.*.device.*.unnamed_37.keyboard; // Translate libinput keycode -> xkbcommon const keycode = event.*.keycode + 8; @@ -444,7 +444,7 @@ fn server_new_keyboard(server: *Server, device: *c.wlr_input_device) void { 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; + var keyboard_device = device.*.unnamed_37.keyboard; c.wlr_keyboard_set_keymap(keyboard_device, keymap); c.wlr_keyboard_set_repeat_info(keyboard_device, 25, 600); @@ -460,6 +460,14 @@ fn server_new_keyboard(server: *Server, device: *c.wlr_input_device) void { c.wl_list_insert(&server.*.keyboards, &keyboard.*.link); } +fn server_new_pointer(server: *Server, device: *c.struct_wlr_input_device) void { + // We don't do anything special with pointers. All of our pointer handling + // is proxied through wlr_cursor. On another compositor, you might take this + // opportunity to do libinput configuration on the device to set + // acceleration, etc. + c.wlr_cursor_attach_input_device(server.*.cursor, device); +} + 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); @@ -467,10 +475,14 @@ fn server_new_input(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) vo switch (device.*.type) { .WLR_INPUT_DEVICE_KEYBOARD => server_new_keyboard(server, device), + .WLR_INPUT_DEVICE_POINTER => server_new_pointer(server, device), else => {}, } - var caps: u32 = 0; + // We need to let the wlr_seat know what our capabilities are, which is + // communiciated to the client. In TinyWL we always have a cursor, even if + // there are no pointer devices, so we always include that capability. + var caps: u32 = @intCast(u32, c.WL_SEAT_CAPABILITY_POINTER); // if list not empty if (c.wl_list_empty(&server.*.keyboards) == 0) { caps |= @intCast(u32, c.WL_SEAT_CAPABILITY_KEYBOARD); @@ -478,7 +490,230 @@ fn server_new_input(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) vo c.wlr_seat_set_capabilities(server.*.seat, caps); } +fn seat_request_cursor(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is rasied by the seat when a client provides a cursor image + var server = @fieldParentPtr(Server, "request_cursor", listener); + var event = @ptrCast(*c.wlr_seat_pointer_request_set_cursor_event, @alignCast(@alignOf(*c.wlr_seat_pointer_request_set_cursor_event), data)); + var focused_client = server.*.seat.*.pointer_state.focused_client; + + // This can be sent by any client, so we check to make sure this one is + // actually has pointer focus first. + if (focused_client == event.*.seat_client) { + // Once we've vetted the client, we can tell the cursor to use the + // provided surface as the cursor image. It will set the hardware cursor + // on the output that it's currently on and continue to do so as the + // cursor moves between outputs. + c.wlr_cursor_set_surface(server.*.cursor, event.*.surface, event.*.hotspot_x, event.*.hotspot_y); + } +} + +fn view_at(view: *View, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) bool { + // XDG toplevels may have nested surfaces, such as popup windows for context + // menus or tooltips. This function tests if any of those are underneath the + // coordinates lx and ly (in output Layout Coordinates). If so, it sets the + // surface pointer to that wlr_surface and the sx and sy coordinates to the + // coordinates relative to that surface's top-left corner. + var view_sx = lx - @intToFloat(f64, view.*.x); + var view_sy = ly - @intToFloat(f64, view.*.y); + + // This variable seems to have been unsued in TinyWL + // struct wlr_surface_state *state = &view->xdg_surface->surface->current; + + var _sx: f64 = undefined; + var _sy: f64 = undefined; + var _surface = c.wlr_xdg_surface_surface_at(view.*.xdg_surface, view_sx, view_sy, &_sx, &_sy); + + if (_surface) |surface_at| { + sx.* = _sx; + sy.* = _sy; + surface.* = surface_at; + return true; + } + + return false; +} + +fn desktop_view_at(server: *Server, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) ?*View { + // This iterates over all of our surfaces and attempts to find one under the + // cursor. This relies on server.*.views being ordered from top-to-bottom. + + //wl_list_for_each(view, &server->views, link) { + var view = @fieldParentPtr(View, "link", server.*.views.next); + while (&view.*.link != &server.*.views) { + if (view_at(view, lx, ly, surface, sx, sy)) { + return view; + } + + view = @fieldParentPtr(View, "link", view.*.link.next); + } + return null; +} + +fn process_cursor_move(server: *Server, time: u32) void { + // Move the grabbed view to the new position. + server.*.grabbed_view.?.*.x = @floatToInt(c_int, server.*.cursor.*.x - server.*.grab_x); + server.*.grabbed_view.?.*.y = @floatToInt(c_int, server.*.cursor.*.y - server.*.grab_y); +} + +fn process_cursor_resize(server: *Server, time: u32) void { + // Resizing the grabbed view can be a little bit complicated, because we + // could be resizing from any corner or edge. This not only resizes the view + // on one or two axes, but can also move the view if you resize from the top + // or left edges (or top-left corner). + // + // Note that I took some shortcuts here. In a more fleshed-out compositor, + // you'd wait for the client to prepare a buffer at the new size, then + // commit any movement that was prepared. + var view = server.*.grabbed_view; + + var dx: f64 = (server.*.cursor.*.x - server.*.grab_x); + var dy: f64 = (server.*.cursor.*.y - server.*.grab_y); + var x: f64 = @intToFloat(f64, view.?.*.x); + var y: f64 = @intToFloat(f64, view.?.*.y); + + var width = @intToFloat(f64, server.*.grab_width); + var height = @intToFloat(f64, server.*.grab_height); + if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_TOP) != 0) { + y = server.*.grab_y + dy; + height -= dy; + if (height < 1) { + y += height; + } + } else if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_BOTTOM) != 0) { + height += dy; + } + if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_LEFT) != 0) { + x = server.*.grab_x + dx; + width -= dx; + if (width < 1) { + x += width; + } + } else if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_RIGHT) != 0) { + width += dx; + } + view.?.*.x = @floatToInt(c_int, x); + view.?.*.y = @floatToInt(c_int, y); + _ = c.wlr_xdg_toplevel_set_size(view.?.*.xdg_surface, @floatToInt(u32, width), @floatToInt(u32, height)); +} + +fn process_cursor_motion(server: *Server, time: u32) void { + // If the mode is non-passthrough, delegate to those functions. + if (server.*.cursor_mode == CursorMode.Move) { + process_cursor_move(server, time); + return; + } else if (server.*.cursor_mode == CursorMode.Resize) { + process_cursor_resize(server, time); + return; + } + + // Otherwise, find the view under the pointer and send the event along. + var sx: f64 = undefined; + var sy: f64 = undefined; + var seat = server.*.seat; + var opt_surface: ?*c.wlr_surface = null; + var view = desktop_view_at(server, server.*.cursor.*.x, server.*.cursor.*.y, &opt_surface, &sx, &sy); + + if (view == null) { + // If there's no view under the cursor, set the cursor image to a + // default. This is what makes the cursor image appear when you move it + // around the screen, not over any views. + c.wlr_xcursor_manager_set_cursor_image(server.*.cursor_mgr, "left_ptr", server.*.cursor); + } + + if (opt_surface) |surface| { + const focus_changed = seat.*.pointer_state.focused_surface != surface; + // "Enter" the surface if necessary. This lets the client know that the + // cursor has entered one of its surfaces. + // + // Note that this gives the surface "pointer focus", which is distinct + // from keyboard focus. You get pointer focus by moving the pointer over + // a window. + c.wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + if (!focus_changed) { + // The enter event contains coordinates, so we only need to notify + // on motion if the focus did not change. + c.wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + } else { + // Clear pointer focus so future button events and such are not sent to + // the last client to have the cursor over it. + c.wlr_seat_pointer_clear_focus(seat); + } +} + +fn server_cursor_motion(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is forwarded by the cursor when a pointer emits a _relative_ + // pointer motion event (i.e. a delta) + var server = @fieldParentPtr(Server, "cursor_motion", listener); + var event = @ptrCast(*c.wlr_event_pointer_motion, @alignCast(@alignOf(*c.wlr_event_pointer_motion), data)); + // The cursor doesn't move unless we tell it to. The cursor automatically + // handles constraining the motion to the output layout, as well as any + // special configuration applied for the specific input device which + // generated the event. You can pass NULL for the device if you want to move + // the cursor around without any input. + c.wlr_cursor_move(server.*.cursor, event.*.device, event.*.delta_x, event.*.delta_y); + process_cursor_motion(server, event.*.time_msec); +} + +fn server_cursor_motion_absolute(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is forwarded by the cursor when a pointer emits an _absolute_ + // motion event, from 0..1 on each axis. This happens, for example, when + // wlroots is running under a Wayland window rather than KMS+DRM, and you + // move the mouse over the window. You could enter the window from any edge, + // so we have to warp the mouse there. There is also some hardware which + // emits these events. + var server = @fieldParentPtr(Server, "cursor_motion_absolute", listener); + var event = @ptrCast(*c.wlr_event_pointer_motion_absolute, @alignCast(@alignOf(*c.wlr_event_pointer_motion_absolute), data)); + c.wlr_cursor_warp_absolute(server.*.cursor, event.*.device, event.*.x, event.*.y); + process_cursor_motion(server, event.*.time_msec); +} + +fn server_cursor_button(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is forwarded by the cursor when a pointer emits a button + // event. + var server = @fieldParentPtr(Server, "cursor_button", listener); + var event = @ptrCast(*c.wlr_event_pointer_button, @alignCast(@alignOf(*c.wlr_event_pointer_button), data)); + // Notify the client with pointer focus that a button press has occurred + _ = c.wlr_seat_pointer_notify_button(server.*.seat, event.*.time_msec, event.*.button, event.*.state); + + var sx: f64 = undefined; + var sy: f64 = undefined; + + var surface: ?*c.wlr_surface = null; + var view = desktop_view_at(server, server.*.cursor.*.x, server.*.cursor.*.y, &surface, &sx, &sy); + + if (event.*.state == c.enum_wlr_button_state.WLR_BUTTON_RELEASED) { + // If you released any buttons, we exit interactive move/resize mode. + server.*.cursor_mode = CursorMode.Passthrough; + } else { + // Focus that client if the button was _pressed_ + if (view) |v| { + focus_view(v, surface.?); + } + } +} + +fn server_cursor_axis(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is forwarded by the cursor when a pointer emits an axis event, + // for example when you move the scroll wheel. + var server = @fieldParentPtr(Server, "cursor_axis", listener); + var event = @ptrCast(*c.wlr_event_pointer_axis, @alignCast(@alignOf(*c.wlr_event_pointer_axis), data)); + // Notify the client with pointer focus of the axis event. + c.wlr_seat_pointer_notify_axis(server.*.seat, event.*.time_msec, event.*.orientation, event.*.delta, event.*.delta_discrete, event.*.source); +} + +fn server_cursor_frame(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is forwarded by the cursor when a pointer emits an frame + // event. Frame events are sent after regular pointer events to group + // multiple events together. For instance, two axis events may happen at the + // same time, in which case a frame event won't be sent in between. + var server = @fieldParentPtr(Server, "cursor_frame", listener); + // Notify the client with pointer focus of the frame event. + c.wlr_seat_pointer_notify_frame(server.*.seat); +} + const ZagError = error{ + InitError, CantAddSocket, CantStartBackend, CantSetEnv, @@ -492,7 +727,7 @@ pub fn main() !void { 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().?; + server.wl_display = c.wl_display_create() orelse return ZagError.InitError; // The backend is a wlroots feature which abstracts the underlying input and // output hardware. The autocreate option will choose the most suitable @@ -502,12 +737,12 @@ pub fn main() !void { // 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); + server.backend = c.zag_wlr_backend_autocreate(server.wl_display) orelse return ZagError.InitError; // 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); + server.renderer = c.zag_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 @@ -535,6 +770,43 @@ pub fn main() !void { server.new_xdg_surface.notify = server_new_xdg_surface; c.wl_signal_add(&server.xdg_shell.*.events.new_surface, &server.new_xdg_surface); + // Creates a cursor, which is a wlroots utility for tracking the cursor + // image shown on screen. + server.cursor = c.wlr_cursor_create(); + c.wlr_cursor_attach_output_layout(server.cursor, server.output_layout); + + // Creates an xcursor manager, another wlroots utility which loads up + // Xcursor themes to source cursor images from and makes sure that cursor + // images are available at all scale factors on the screen (necessary for + // HiDPI support). We add a cursor theme at scale factor 1 to begin with. + server.cursor_mgr = c.wlr_xcursor_manager_create(null, 24); + _ = c.wlr_xcursor_manager_load(server.cursor_mgr, 1); + + // wlr_cursor *only* displays an image on screen. It does not move around + // when the pointer moves. However, we can attach input devices to it, and + // it will generate aggregate events for all of them. In these events, we + // can choose how we want to process them, forwarding them to clients and + // moving the cursor around. More detail on this process is described in my + // input handling blog post: + // + // https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html + // + // And more comments are sprinkled throughout the notify functions above. + server.cursor_motion.notify = server_cursor_motion; + c.wl_signal_add(&server.cursor.*.events.motion, &server.cursor_motion); + + server.cursor_motion_absolute.notify = server_cursor_motion_absolute; + c.wl_signal_add(&server.cursor.*.events.motion_absolute, &server.cursor_motion_absolute); + + server.cursor_button.notify = server_cursor_button; + c.wl_signal_add(&server.cursor.*.events.button, &server.cursor_button); + + server.cursor_axis.notify = server_cursor_axis; + c.wl_signal_add(&server.cursor.*.events.axis, &server.cursor_axis); + + server.cursor_frame.notify = server_cursor_frame; + c.wl_signal_add(&server.cursor.*.events.frame, &server.cursor_frame); + // 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 @@ -543,20 +815,20 @@ pub fn main() !void { 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); + 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); + c.zag_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); + if (!c.zag_wlr_backend_start(server.backend)) { + c.zag_wlr_backend_destroy(server.backend); c.wl_display_destroy(server.wl_display); return ZagError.CantStartBackend; } @@ -567,15 +839,9 @@ pub fn main() !void { return ZagError.CantSetEnv; } - const argv = [_][]const u8{ "/bin/sh", "-c", "alacritty" }; + const argv = [_][]const u8{ "/bin/sh", "-c", "WAYLAND_DEBUG=1 alacritty" }; var child = try std.ChildProcess.init(&argv, std.heap.c_allocator); try std.ChildProcess.spawn(child); - //if (startup_cmd) { - //if (std.os.linux.fork() == 0) { - // execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL); - // std.os.linux.execve("/bin/sh", - //} - //} // Run the Wayland event loop. This does not return until you exit the // compositor. Starting the backend rigged up all of the necessary event