From cd19a4615b822ab85ebe1430ec956f6b22f3d008 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 18 Apr 2020 12:21:43 +0200 Subject: [PATCH] Properly clean up resources on exit --- src/cursor.zig | 288 +++++++++++++++++++++--------------------- src/input_manager.zig | 7 + src/keyboard.zig | 42 +++--- src/main.zig | 4 +- src/output.zig | 14 ++ src/root.zig | 6 +- src/seat.zig | 13 +- src/server.zig | 6 +- src/view.zig | 6 + 9 files changed, 214 insertions(+), 172 deletions(-) diff --git a/src/cursor.zig b/src/cursor.zig index 839276a..4ca999d 100644 --- a/src/cursor.zig +++ b/src/cursor.zig @@ -17,14 +17,6 @@ pub const Cursor = struct { wlr_cursor: *c.wlr_cursor, wlr_xcursor_manager: *c.wlr_xcursor_manager, - listen_motion: c.wl_listener, - listen_motion_absolute: c.wl_listener, - listen_button: c.wl_listener, - listen_axis: c.wl_listener, - listen_frame: c.wl_listener, - - listen_request_set_cursor: c.wl_listener, - mode: CursorMode, grabbed_view: ?*View, grab_x: f64, @@ -33,6 +25,13 @@ pub const Cursor = struct { grab_height: c_int, resize_edges: u32, + listen_axis: c.wl_listener, + listen_button: c.wl_listener, + listen_frame: c.wl_listener, + listen_motion_absolute: c.wl_listener, + listen_motion: c.wl_listener, + listen_request_set_cursor: c.wl_listener, + pub fn init(self: *Self, seat: *Seat) !void { self.seat = seat; @@ -68,31 +67,99 @@ pub const Cursor = struct { // can choose how we want to process them, forwarding them to clients and // moving the cursor around. See following post for more detail: // https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html - self.listen_motion.notify = handleMotion; - c.wl_signal_add(&self.wlr_cursor.events.motion, &self.listen_motion); - - self.listen_motion_absolute.notify = handleMotionAbsolute; - c.wl_signal_add(&self.wlr_cursor.events.motion_absolute, &self.listen_motion_absolute); + self.listen_axis.notify = handleAxis; + c.wl_signal_add(&self.wlr_cursor.events.axis, &self.listen_axis); self.listen_button.notify = handleButton; c.wl_signal_add(&self.wlr_cursor.events.button, &self.listen_button); - self.listen_axis.notify = handleAxis; - c.wl_signal_add(&self.wlr_cursor.events.axis, &self.listen_axis); - self.listen_frame.notify = handleFrame; c.wl_signal_add(&self.wlr_cursor.events.frame, &self.listen_frame); - // This listens for clients requesting a specific cursor image + self.listen_motion_absolute.notify = handleMotionAbsolute; + c.wl_signal_add(&self.wlr_cursor.events.motion_absolute, &self.listen_motion_absolute); + + self.listen_motion.notify = handleMotion; + c.wl_signal_add(&self.wlr_cursor.events.motion, &self.listen_motion); + self.listen_request_set_cursor.notify = handleRequestSetCursor; c.wl_signal_add(&self.seat.wlr_seat.events.request_set_cursor, &self.listen_request_set_cursor); } - pub fn destroy(self: Self) void { + pub fn deinit(self: *Self) void { c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager); c.wlr_cursor_destroy(self.wlr_cursor); } + fn handleAxis(listener: ?*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. + const cursor = @fieldParentPtr(Cursor, "listen_axis", listener.?); + const 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( + cursor.seat.wlr_seat, + event.time_msec, + event.orientation, + event.delta, + event.delta_discrete, + event.source, + ); + } + + fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is forwarded by the cursor when a pointer emits a button + // event. + const cursor = @fieldParentPtr(Cursor, "listen_button", listener.?); + const 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( + cursor.seat.wlr_seat, + event.time_msec, + event.button, + event.state, + ); + + var sx: f64 = undefined; + var sy: f64 = undefined; + + var surface: ?*c.wlr_surface = null; + const view = cursor.seat.input_manager.server.root.viewAt( + cursor.wlr_cursor.x, + cursor.wlr_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. + cursor.mode = CursorMode.Passthrough; + } else { + // Focus that client if the button was _pressed_ + if (view) |v| { + cursor.seat.focus(v); + } + } + } + + fn handleFrame(listener: ?*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. + const cursor = @fieldParentPtr(Cursor, "listen_frame", listener.?); + // Notify the client with pointer focus of the frame event. + c.wlr_seat_pointer_notify_frame(cursor.seat.wlr_seat); + } + fn processMove(self: Self, time: u32) void { // Move the grabbed view to the new position. // TODO: log on null @@ -102,6 +169,64 @@ pub const Cursor = struct { } } + fn handleMotionAbsolute(listener: ?*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. + const cursor = @fieldParentPtr(Cursor, "listen_motion_absolute", listener.?); + const event = @ptrCast( + *c.wlr_event_pointer_motion_absolute, + @alignCast(@alignOf(*c.wlr_event_pointer_motion_absolute), data), + ); + c.wlr_cursor_warp_absolute(cursor.wlr_cursor, event.device, event.x, event.y); + cursor.processMotion(event.time_msec); + } + + fn handleMotion(listener: ?*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) + const cursor = @fieldParentPtr(Cursor, "listen_motion", listener.?); + const 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(cursor.wlr_cursor, event.device, event.delta_x, event.delta_y); + cursor.processMotion(event.time_msec); + } + + fn handleRequestSetCursor(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { + // This event is rasied by the seat when a client provides a cursor image + const cursor = @fieldParentPtr(Cursor, "listen_request_set_cursor", listener.?); + const event = @ptrCast( + *c.wlr_seat_pointer_request_set_cursor_event, + @alignCast(@alignOf(*c.wlr_seat_pointer_request_set_cursor_event), data), + ); + const focused_client = cursor.seat.wlr_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( + cursor.wlr_cursor, + event.surface, + event.hotspot_x, + event.hotspot_y, + ); + } + } + fn processsResize(self: Self, 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 @@ -205,131 +330,4 @@ pub const Cursor = struct { c.wlr_seat_pointer_clear_focus(wlr_seat); } } - - fn handleMotion(listener: ?*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) - const cursor = @fieldParentPtr(Cursor, "listen_motion", listener.?); - const 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(cursor.wlr_cursor, event.device, event.delta_x, event.delta_y); - cursor.processMotion(event.time_msec); - } - - fn handleMotionAbsolute(listener: ?*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. - const cursor = @fieldParentPtr(Cursor, "listen_motion_absolute", listener.?); - const event = @ptrCast( - *c.wlr_event_pointer_motion_absolute, - @alignCast(@alignOf(*c.wlr_event_pointer_motion_absolute), data), - ); - c.wlr_cursor_warp_absolute(cursor.wlr_cursor, event.device, event.x, event.y); - cursor.processMotion(event.time_msec); - } - - fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - // This event is forwarded by the cursor when a pointer emits a button - // event. - const cursor = @fieldParentPtr(Cursor, "listen_button", listener.?); - const 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( - cursor.seat.wlr_seat, - event.time_msec, - event.button, - event.state, - ); - - var sx: f64 = undefined; - var sy: f64 = undefined; - - var surface: ?*c.wlr_surface = null; - const view = cursor.seat.input_manager.server.root.viewAt( - cursor.wlr_cursor.x, - cursor.wlr_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. - cursor.mode = CursorMode.Passthrough; - } else { - // Focus that client if the button was _pressed_ - if (view) |v| { - cursor.seat.focus(v); - } - } - } - - fn handleAxis(listener: ?*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. - const cursor = @fieldParentPtr(Cursor, "listen_axis", listener.?); - const 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( - cursor.seat.wlr_seat, - event.time_msec, - event.orientation, - event.delta, - event.delta_discrete, - event.source, - ); - } - - fn handleFrame(listener: ?*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. - const cursor = @fieldParentPtr(Cursor, "listen_frame", listener.?); - // Notify the client with pointer focus of the frame event. - c.wlr_seat_pointer_notify_frame(cursor.seat.wlr_seat); - } - - fn handleRequestSetCursor(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - // This event is rasied by the seat when a client provides a cursor image - const cursor = @fieldParentPtr(Cursor, "listen_request_set_cursor", listener.?); - const event = @ptrCast( - *c.wlr_seat_pointer_request_set_cursor_event, - @alignCast(@alignOf(*c.wlr_seat_pointer_request_set_cursor_event), data), - ); - const focused_client = cursor.seat.wlr_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( - cursor.wlr_cursor, - event.surface, - event.hotspot_x, - event.hotspot_y, - ); - } - } }; diff --git a/src/input_manager.zig b/src/input_manager.zig index 6f4d7ae..d2c11db 100644 --- a/src/input_manager.zig +++ b/src/input_manager.zig @@ -32,6 +32,13 @@ pub const InputManager = struct { c.wl_signal_add(&self.server.wlr_backend.events.new_input, &self.listen_new_input); } + pub fn deinit(self: *Self) void { + while (self.seats.pop()) |seat_node| { + seat_node.data.deinit(); + self.server.allocator.destroy(seat_node); + } + } + /// Must be called whenever a view is unmapped. pub fn handleViewUnmap(self: Self, view: *View) void { var it = self.seats.first; diff --git a/src/keyboard.zig b/src/keyboard.zig index 7542a29..f270a22 100644 --- a/src/keyboard.zig +++ b/src/keyboard.zig @@ -11,8 +11,8 @@ pub const Keyboard = struct { device: *c.wlr_input_device, wlr_keyboard: *c.wlr_keyboard, - listen_modifiers: c.wl_listener, listen_key: c.wl_listener, + listen_modifiers: c.wl_listener, pub fn init(self: *Self, seat: *Seat, device: *c.wlr_input_device) !void { self.seat = seat; @@ -45,29 +45,11 @@ pub const Keyboard = struct { c.wlr_keyboard_set_repeat_info(self.wlr_keyboard, 25, 600); // Setup listeners for keyboard events - self.listen_modifiers.notify = handleModifiers; - c.wl_signal_add(&self.wlr_keyboard.events.modifiers, &self.listen_modifiers); - self.listen_key.notify = handleKey; c.wl_signal_add(&self.wlr_keyboard.events.key, &self.listen_key); - } - fn handleModifiers(listener: ?*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. */ - const keyboard = @fieldParentPtr(Keyboard, "listen_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.seat.wlr_seat, keyboard.device); - - // Send modifiers to the client. - c.wlr_seat_keyboard_notify_modifiers( - keyboard.seat.wlr_seat, - &keyboard.wlr_keyboard.modifiers, - ); + self.listen_modifiers.notify = handleModifiers; + c.wl_signal_add(&self.wlr_keyboard.events.modifiers, &self.listen_modifiers); } fn handleKey(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { @@ -144,6 +126,24 @@ pub const Keyboard = struct { } } + fn handleModifiers(listener: ?*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. */ + const keyboard = @fieldParentPtr(Keyboard, "listen_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.seat.wlr_seat, keyboard.device); + + // Send modifiers to the client. + c.wlr_seat_keyboard_notify_modifiers( + keyboard.seat.wlr_seat, + &keyboard.wlr_keyboard.modifiers, + ); + } + /// Handle any builtin, harcoded compsitor bindings such as VT switching. /// Returns true if the keysym was handled. fn handleBuiltinKeybind(self: Self, keysym: c.xkb_keysym_t) bool { diff --git a/src/main.zig b/src/main.zig index ec9d5ca..9b581b2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -12,11 +12,13 @@ pub fn main() !void { var server: Server = undefined; try server.init(std.heap.c_allocator); - defer server.destroy(); + defer server.deinit(); try server.start(); Log.Info.log("Running server...", .{}); server.run(); + + Log.Info.log("Shutting down server", .{}); } diff --git a/src/output.zig b/src/output.zig index e951fe3..bb88156 100644 --- a/src/output.zig +++ b/src/output.zig @@ -101,6 +101,20 @@ pub const Output = struct { } } + pub fn deinit(self: *Self) void { + for (self.layers) |*layer| { + while (layer.pop()) |layer_surface_node| { + self.root.server.allocator.destroy(layer_surface_node); + } + } + + while (self.views.first) |node| { + node.view.deinit(); + self.views.remove(node); + self.root.server.allocator.destroy(node); + } + } + /// Add a new view to the output. arrangeViews() will be called by the view /// when it is mapped. pub fn addView(self: *Self, wlr_xdg_surface: *c.wlr_xdg_surface) void { diff --git a/src/root.zig b/src/root.zig index eb7b0f3..7246577 100644 --- a/src/root.zig +++ b/src/root.zig @@ -50,7 +50,11 @@ pub const Root = struct { self.transaction_timer = null; } - pub fn destroy(self: Self) void { + pub fn deinit(self: *Self) void { + while (self.outputs.pop()) |output_node| { + output_node.data.deinit(); + self.server.allocator.destroy(output_node); + } c.wlr_output_layout_destroy(self.wlr_output_layout); } diff --git a/src/seat.zig b/src/seat.zig index dc25802..e011579 100644 --- a/src/seat.zig +++ b/src/seat.zig @@ -50,8 +50,17 @@ pub const Seat = struct { self.focus_stack.init(); } - pub fn destroy(self: Self) void { - self.cursor.destroy(); + pub fn deinit(self: *Self) void { + self.cursor.deinit(); + + while (self.keyboards.pop()) |node| { + self.input_manager.server.allocator.destroy(node); + } + + while (self.focus_stack.first) |node| { + self.focus_stack.remove(node); + self.input_manager.server.allocator.destroy(node); + } } /// Set the current focus. If a visible view is passed it will be focused. diff --git a/src/server.zig b/src/server.zig index df1beb3..8371860 100644 --- a/src/server.zig +++ b/src/server.zig @@ -99,10 +99,12 @@ pub const Server = struct { } /// Free allocated memory and clean up - pub fn destroy(self: Self) void { + pub fn deinit(self: *Self) void { + // Note: order is important here c.wl_display_destroy_clients(self.wl_display); c.wl_display_destroy(self.wl_display); - self.root.destroy(); + self.input_manager.deinit(); + self.root.deinit(); } /// Create the socket, set WAYLAND_DISPLAY, and start the backend diff --git a/src/view.zig b/src/view.zig index 6af7fe2..ea6fdc0 100644 --- a/src/view.zig +++ b/src/view.zig @@ -70,6 +70,12 @@ pub const View = struct { c.wl_signal_add(&self.wlr_xdg_surface.events.unmap, &self.listen_unmap); } + pub fn deinit(self: *Self) void { + if (self.stashed_buffer) |buffer| { + c.wlr_buffer_unref(buffer); + } + } + pub fn needsConfigure(self: Self) bool { if (self.pending_box) |pending_box| { return pending_box.width != self.current_box.width or