From 50494add408bda394d71d6f0a830ca1a34771ba2 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 23 Mar 2020 14:04:54 +0100 Subject: [PATCH] Refactor xdg_shell handling --- src/main.zig | 8 -- src/server.zig | 44 ++++++++- src/view.zig | 226 +++++++++++++++++++++++++++++----------------- src/xdg_shell.zig | 68 -------------- 4 files changed, 184 insertions(+), 162 deletions(-) delete mode 100644 src/xdg_shell.zig diff --git a/src/main.zig b/src/main.zig index 76ddf8d..6da2ffe 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,14 +9,6 @@ pub fn main() !void { var server = try Server.init(std.heap.c_allocator); defer server.deinit(); - // 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 - server.views = std.ArrayList(View).init(std.heap.c_allocator); - 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); - try server.start(); // Spawn an instance of alacritty diff --git a/src/server.zig b/src/server.zig index 54d3efe..7206341 100644 --- a/src/server.zig +++ b/src/server.zig @@ -11,8 +11,10 @@ pub const Server = struct { listen_new_output: c.wl_listener, - xdg_shell: *c.wlr_xdg_shell, - new_xdg_surface: c.wl_listener, + wlr_xdg_shell: *c.wlr_xdg_shell, + listen_new_xdg_surface: c.wl_listener, + + // Must stay ordered bottom to top views: std.ArrayList(View), pub fn init(allocator: *std.mem.Allocator) !@This() { @@ -57,6 +59,17 @@ pub const Server = struct { // Setup a listener for new outputs server.listen_new_output = handle_new_output; c.wl_signal_add(&server.wlr_backend.*.events.new_output, &server.listen_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 + server.views = std.ArrayList(View).init(std.heap.c_allocator); + server.wlr_xdg_shell = c.wlr_xdg_shell_create(server.wl_display) orelse + return error.CantCreateWlrXdgShell; + server.listen_new_xdg_surface.notify = handle_new_xdg_surface; + c.wl_signal_add(&server.xdg_shell.*.events.new_surface, &server.listen_new_xdg_surface); + + return server; } /// Free allocated memory and clean up @@ -120,6 +133,31 @@ pub const Server = struct { var wlr_output = @ptrCast(*c.wlr_output, @alignCast(@alignOf(*c.wlr_output), data)); // TODO: Handle failure - server.outputs.append(Output.init(server, wlr_output) orelse return); + server.outputs.append(Output.init(server, wlr_output) catch unreachable); + } + + fn handle_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, "listen_new_xdg_surface", listener); + var wlr_xdg_surface = @ptrCast(*c.wlr_xdg_surface, @alignCast(@alignOf(*c.wlr_xdg_surface), data)); + + if (wlr_xdg_surface.role != c.enum_wlr_xdg_surface_role.WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + + // Init a View to handle this surface + server.*.views.append(View.init(server, wlr_xdg_surface)) catch unreachable; + } + + /// Finds the top most view under the output layout coordinates lx, ly + /// returns the view if found, and a pointer to the wlr_surface as well as the surface coordinates + pub fn desktop_view_at(self: *@This(), lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) ?*View { + for (server.*.views.span()) |*view| { + if (view.is_at(lx, ly, surface, sx, sy)) { + return view; + } + } + return null; } }; diff --git a/src/view.zig b/src/view.zig index 0156734..96ac6eb 100644 --- a/src/view.zig +++ b/src/view.zig @@ -3,90 +3,150 @@ const c = @import("c.zig").c; pub const View = struct { 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, + wlr_xdg_surface: *c.wlr_xdg_surface, + + listen_map: c.wl_listener, + listen_unmap: c.wl_listener, + listen_destroy: c.wl_listener, + // listen_request_move: c.wl_listener, + // listen_request_resize: c.wl_listener, + mapped: bool, x: c_int, y: c_int, + + pub fn init(server: *Server, wlr_xdg_surface: *c.wlr_xdg_surface) @This() { + var view = @This(){ + .server = server, + .wlr_xdg_surface = wlr_xdg_surface, + .listen_map = c.wl_listener{ + .link = undefined, + .notify = handle_map, + }, + .listen_unmap = c.wl_listener{ + .link = undefined, + .notify = handle_unmap, + }, + .listen_destroy = c.wl_listener{ + .link = undefined, + .notify = handle_destroy, + }, + // .listen_request_move = c.wl_listener{ + // .link = undefined, + // .notify = handle_request_move, + // }, + // .listen_request_resize = c.wl_listener{ + // .link = undefined, + // .notify = handle_request_resize, + // }, + }; + + // Listen to the various events it can emit + c.wl_signal_add(&xdg_surface.*.events.map, &view.*.listen_map); + c.wl_signal_add(&xdg_surface.*.events.unmap, &view.*.listen_unmap); + c.wl_signal_add(&xdg_surface.*.events.destroy, &view.*.listen_destroy); + + // var toplevel = xdg_surface.*.unnamed_160.toplevel; + // c.wl_signal_add(&toplevel.*.events.request_move, &view.*.request_move); + // c.wl_signal_add(&toplevel.*.events.request_resize, &view.*.request_resize); + + return view; + } + + fn handle_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 handle_unmap(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + var view = @fieldParentPtr(View, "unmap", listener); + view.*.mapped = false; + } + + fn handle_destroy(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { + var view = @fieldParentPtr(View, "destroy", listener); + var server = view.*.server; + const idx = for (server.*.views.span()) |*v, i| { + if (v == view) { + break i; + } + } else return; + _ = server.*.views.orderedRemove(idx); + } + + // 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 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; + + if (prev_surface == surface) { + // Don't re-focus an already focused surface. + return; + } + + 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); + } + + // Find the index + const idx = for (server.*.views.span()) |*v, i| { + if (v == view) { + break i; + } + } else unreachable; + + // Move the view to the front + server.*.views.append(server.*.views.orderedRemove(idx)) catch unreachable; + + var moved_view = &server.*.views.span()[server.*.views.span().len - 1]; + + // Activate the new surface + _ = c.wlr_xdg_toplevel_set_activated(moved_view.*.xdg_surface, true); + + // 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, moved_view.*.xdg_surface.*.surface, &keyboard.*.keycodes, keyboard.*.num_keycodes, &keyboard.*.modifiers); + } + + fn is_at(self: *@This(), 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 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; - - if (prev_surface == surface) { - // Don't re-focus an already focused surface. - return; - } - - 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); - } - - // Find the index - const idx = for (server.*.views.span()) |*v, i| { - if (v == view) { - break i; - } - } else unreachable; - - // Move the view to the front - server.*.views.append(server.*.views.orderedRemove(idx)) catch unreachable; - - var moved_view = &server.*.views.span()[server.*.views.span().len - 1]; - - // Activate the new surface - _ = c.wlr_xdg_toplevel_set_activated(moved_view.*.xdg_surface, true); - - // 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, moved_view.*.xdg_surface.*.surface, &keyboard.*.keycodes, keyboard.*.num_keycodes, &keyboard.*.modifiers); -} - -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. - for (server.*.views.span()) |*view| { - if (view_at(view, lx, ly, surface, sx, sy)) { - return view; - } - } - return null; -} diff --git a/src/xdg_shell.zig b/src/xdg_shell.zig deleted file mode 100644 index 3bcc736..0000000 --- a/src/xdg_shell.zig +++ /dev/null @@ -1,68 +0,0 @@ -const std = @import("std"); -const c = @import("c.zig").c; - -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, "unmap", listener); - view.*.mapped = false; -} - -fn xdg_surface_destroy(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void { - var view = @fieldParentPtr(View, "destroy", listener); - var server = view.*.server; - const idx = for (server.*.views.span()) |*v, i| { - if (v == view) { - break i; - } - } else return; - _ = server.*.views.orderedRemove(idx); -} - -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 - server.*.views.append(undefined) catch unreachable; - var view = &server.*.views.span()[server.*.views.span().len - 1]; - - 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_160.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); -}