Refactor xdg_shell handling
This commit is contained in:
		| @ -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 | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
| }; | ||||
|  | ||||
							
								
								
									
										226
									
								
								src/view.zig
									
									
									
									
									
								
							
							
						
						
									
										226
									
								
								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; | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user