diff options
Diffstat (limited to 'src')
48 files changed, 0 insertions, 6099 deletions
diff --git a/src/Box.zig b/src/Box.zig deleted file mode 100644 index 0ba3e63..0000000 --- a/src/Box.zig +++ /dev/null @@ -1,34 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const c = @import("c.zig"); - -x: i32, -y: i32, -width: u32, -height: u32, - -pub fn toWlrBox(self: Self) c.wlr_box { - return c.wlr_box{ - .x = @intCast(c_int, self.x), - .y = @intCast(c_int, self.y), - .width = @intCast(c_int, self.width), - .height = @intCast(c_int, self.height), - }; -} diff --git a/src/Config.zig b/src/Config.zig deleted file mode 100644 index 20b7504..0000000 --- a/src/Config.zig +++ /dev/null @@ -1,284 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const Log = @import("log.zig").Log; -const Server = @import("Server.zig"); -const Mapping = @import("Mapping.zig"); - -/// Width of borders in pixels -border_width: u32, - -/// Amount of view padding in pixels -view_padding: u32, - -/// Amount of padding arount the outer edge of the layout in pixels -outer_padding: u32, - -/// Map of keymap mode name to mode id -mode_to_id: std.StringHashMap(usize), - -/// All user-defined keymap modes, indexed by mode id -modes: std.ArrayList(std.ArrayList(Mapping)), - -/// List of app_ids which will be started floating -float_filter: std.ArrayList([*:0]const u8), - -pub fn init(self: *Self, allocator: *std.mem.Allocator) !void { - self.border_width = 2; - self.view_padding = 8; - self.outer_padding = 8; - - self.mode_to_id = std.StringHashMap(usize).init(allocator); - try self.mode_to_id.putNoClobber("normal", 0); - try self.mode_to_id.putNoClobber("passthrough", 1); - - self.modes = std.ArrayList(std.ArrayList(Mapping)).init(allocator); - try self.modes.append(std.ArrayList(Mapping).init(allocator)); - try self.modes.append(std.ArrayList(Mapping).init(allocator)); - - self.float_filter = std.ArrayList([*:0]const u8).init(allocator); - - const normal_keybinds = &self.modes.items[0]; - const mod = c.WLR_MODIFIER_LOGO; - - // Mod+Shift+Return to start an instance of alacritty - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_Return, - mod | c.WLR_MODIFIER_SHIFT, - &[_][]const u8{ "spawn", "alacritty" }, - )); - - // Mod+Q to close the focused view - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_q, - mod, - &[_][]const u8{"close"}, - )); - - // Mod+E to exit river - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_e, - mod, - &[_][]const u8{"exit"}, - )); - - // Mod+J and Mod+K to focus the next/previous view in the layout stack - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_j, - mod, - &[_][]const u8{ "focus", "next" }, - )); - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_k, - mod, - &[_][]const u8{ "focus", "previous" }, - )); - - // Mod+Return to bump the focused view to the top of the layout stack, - // making it the new master - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_Return, - mod, - &[_][]const u8{"zoom"}, - )); - - // Mod+H and Mod+L to increase/decrease the width of the master column - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_h, - mod, - &[_][]const u8{ "mod_master_factor", "+0.05" }, - )); - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_l, - mod, - &[_][]const u8{ "mod_master_factor", "-0.05" }, - )); - - // Mod+Shift+H and Mod+Shift+L to increment/decrement the number of - // master views in the layout - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_h, - mod | c.WLR_MODIFIER_SHIFT, - &[_][]const u8{ "mod_master_count", "+1" }, - )); - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_l, - mod | c.WLR_MODIFIER_SHIFT, - &[_][]const u8{ "mod_master_count", "+1" }, - )); - - comptime var i = 0; - inline while (i < 9) : (i += 1) { - const str = &[_]u8{i + '0' + 1}; - // Mod+[1-9] to focus tag [1-9] - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_1 + i, - mod, - &[_][]const u8{ "focus_tag", str }, - )); - // Mod+Shift+[1-9] to tag focused view with tag [1-9] - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_1 + i, - mod | c.WLR_MODIFIER_SHIFT, - &[_][]const u8{ "tag_view", str }, - )); - // Mod+Ctrl+[1-9] to toggle focus of tag [1-9] - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_1 + i, - mod | c.WLR_MODIFIER_CTRL, - &[_][]const u8{ "toggle_tag_focus", str }, - )); - // Mod+Shift+Ctrl+[1-9] to toggle tag [1-9] of focused view - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_1 + i, - mod | c.WLR_MODIFIER_CTRL | c.WLR_MODIFIER_SHIFT, - &[_][]const u8{ "toggle_view_tag", str }, - )); - } - - // Mod+0 to focus all tags - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_0, - mod, - &[_][]const u8{"focus_all_tags"}, - )); - - // Mod+Shift+0 to tag focused view with all tags - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_0, - mod | c.WLR_MODIFIER_SHIFT, - &[_][]const u8{"tag_view_all_tags"}, - )); - - // Mod+Period and Mod+Comma to focus the next/previous output - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_period, - mod, - &[_][]const u8{ "focus_output", "next" }, - )); - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_comma, - mod, - &[_][]const u8{ "focus_output", "previous" }, - )); - - // Mod+Shift+Period/Comma to send the focused view to the the - // next/previous output - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_period, - mod | c.WLR_MODIFIER_SHIFT, - &[_][]const u8{ "send_to_output", "next" }, - )); - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_comma, - mod | c.WLR_MODIFIER_SHIFT, - &[_][]const u8{ "send_to_output", "previous" }, - )); - - // Mod+Space to toggle float - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_space, - mod, - &[_][]const u8{"toggle_float"}, - )); - - // Mod+F11 to enter passthrough mode - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_F11, - mod, - &[_][]const u8{ "enter_mode", "passthrough" }, - )); - - // Change master orientation with Mod+{Up,Right,Down,Left} - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_Up, - mod, - &[_][]const u8{ "layout", "TopMaster" }, - )); - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_Right, - mod, - &[_][]const u8{ "layout", "RightMaster" }, - )); - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_Down, - mod, - &[_][]const u8{ "layout", "BottomMaster" }, - )); - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_Left, - mod, - &[_][]const u8{ "layout", "LeftMaster" }, - )); - - // Mod+f to change to Full layout - try normal_keybinds.append(try Mapping.init( - allocator, - c.XKB_KEY_f, - mod, - &[_][]const u8{ "layout", "Full" }, - )); - - // Mod+F11 to return to normal mode - try self.modes.items[1].append(try Mapping.init( - allocator, - c.XKB_KEY_F11, - mod, - &[_][]const u8{ "enter_mode", "normal" }, - )); - - // Float views with app_id "float" - try self.float_filter.append("float"); -} - -pub fn deinit(self: Self, allocator: *std.mem.Allocator) void { - self.mode_to_id.deinit(); - for (self.modes.items) |*mode| mode.deinit(); - self.modes.deinit(); -} diff --git a/src/Control.zig b/src/Control.zig deleted file mode 100644 index e9ae576..0000000 --- a/src/Control.zig +++ /dev/null @@ -1,139 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); -const command = @import("command.zig"); - -const Log = @import("log.zig").Log; -const Server = @import("Server.zig"); - -const protocol_version = 1; - -const implementation = c.struct_zriver_control_v1_interface{ - .run_command = runCommand, -}; - -server: *Server, -wl_global: *c.wl_global, - -listen_display_destroy: c.wl_listener, - -pub fn init(self: *Self, server: *Server) !void { - self.server = server; - self.wl_global = c.wl_global_create( - server.wl_display, - &c.zriver_control_v1_interface, - protocol_version, - self, - bind, - ) orelse return error.CantCreateRiverWindowManagementGlobal; - - self.listen_display_destroy.notify = handleDisplayDestroy; - c.wl_display_add_destroy_listener(server.wl_display, &self.listen_display_destroy); -} - -fn handleDisplayDestroy(wl_listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_display_destroy", wl_listener.?); - c.wl_global_destroy(self.wl_global); -} - -/// Called when a client binds our global -fn bind(wl_client: ?*c.wl_client, data: ?*c_void, version: u32, id: u32) callconv(.C) void { - const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), data)); - const wl_resource = c.wl_resource_create( - wl_client, - &c.zriver_control_v1_interface, - @intCast(c_int, version), - id, - ) orelse { - c.wl_client_post_no_memory(wl_client); - return; - }; - c.wl_resource_set_implementation(wl_resource, &implementation, self, resourceDestroy); -} - -fn resourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void { - // TODO -} - -fn runCommand( - wl_client: ?*c.wl_client, - wl_resource: ?*c.wl_resource, - wl_array: ?*c.wl_array, - callback_id: u32, -) callconv(.C) void { - const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), c.wl_resource_get_user_data(wl_resource))); - const allocator = self.server.allocator; - const seat = self.server.input_manager.default_seat; - - var args = std.ArrayList([]const u8).init(allocator); - - var i: usize = 0; - const data = @ptrCast([*]const u8, wl_array.?.data); - while (i < wl_array.?.size) { - const slice = std.mem.spanZ(@ptrCast([*:0]const u8, &data[i])); - args.append(std.mem.dupe(allocator, u8, slice) catch unreachable) catch unreachable; - - i += slice.len + 1; - } - - const callback_resource = c.wl_resource_create( - wl_client, - &c.zriver_command_callback_v1_interface, - protocol_version, - callback_id, - ) orelse { - c.wl_client_post_no_memory(wl_client); - return; - }; - - c.wl_resource_set_implementation(callback_resource, null, null, null); - - var failure_message: []const u8 = undefined; - command.run(allocator, seat, args.items, &failure_message) catch |err| { - if (err == command.Error.CommandFailed) { - defer allocator.free(failure_message); - const out = std.cstr.addNullByte(allocator, failure_message) catch { - c.zriver_command_callback_v1_send_failure(callback_resource, "out of memory"); - return; - }; - defer allocator.free(out); - c.zriver_command_callback_v1_send_failure(callback_resource, out); - } else { - c.zriver_command_callback_v1_send_failure( - callback_resource, - switch (err) { - command.Error.NoCommand => "no command given", - command.Error.UnknownCommand => "unknown command", - command.Error.NotEnoughArguments => "not enough arguments", - command.Error.TooManyArguments => "too many arguments", - command.Error.Overflow => "value out of bounds", - command.Error.InvalidCharacter => "invalid character in argument", - command.Error.InvalidDirection => "invalid direction. Must be 'next' or 'previous'", - command.Error.OutOfMemory => "out of memory", - command.Error.CommandFailed => unreachable, - }, - ); - } - return; - }; - c.zriver_command_callback_v1_send_success(callback_resource); -} diff --git a/src/Cursor.zig b/src/Cursor.zig deleted file mode 100644 index 9eede57..0000000 --- a/src/Cursor.zig +++ /dev/null @@ -1,412 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const build_options = @import("build_options"); -const std = @import("std"); - -const c = @import("c.zig"); - -const LayerSurface = @import("LayerSurface.zig"); -const Log = @import("log.zig").Log; -const Output = @import("Output.zig"); -const Seat = @import("Seat.zig"); -const View = @import("View.zig"); -const ViewStack = @import("view_stack.zig").ViewStack; - -const CursorMode = enum { - Passthrough, - Move, - Resize, -}; - -seat: *Seat, -wlr_cursor: *c.wlr_cursor, -wlr_xcursor_manager: *c.wlr_xcursor_manager, - -mode: CursorMode, -grabbed_view: ?*View, -grab_x: f64, -grab_y: f64, -grab_width: c_int, -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; - - // Creates a wlroots utility for tracking the cursor image shown on screen. - self.wlr_cursor = c.wlr_cursor_create() orelse - return error.CantCreateWlrCursor; - - // 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. - self.wlr_xcursor_manager = c.wlr_xcursor_manager_create(null, 24) orelse - return error.CantCreateWlrXCursorManager; - c.wlr_cursor_attach_output_layout(self.wlr_cursor, seat.input_manager.server.root.wlr_output_layout); - if (c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, 1) == 0) { - if (build_options.xwayland) { - if (c.wlr_xcursor_manager_get_xcursor( - self.wlr_xcursor_manager, - "left_ptr", - 1, - )) |wlr_xcursor| { - const image: *c.wlr_xcursor_image = wlr_xcursor.*.images[0]; - c.wlr_xwayland_set_cursor( - seat.input_manager.server.wlr_xwayland, - image.buffer, - image.width * 4, - image.width, - image.height, - @intCast(i32, image.hotspot_x), - @intCast(i32, image.hotspot_y), - ); - } - } - } else { - Log.Error.log("Failed to load an xcursor theme", .{}); - } - - self.mode = CursorMode.Passthrough; - self.grabbed_view = null; - self.grab_x = 0.0; - self.grab_y = 0.0; - self.grab_width = 0; - self.grab_height = 0; - self.resize_edges = 0; - - // 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. See following post for more detail: - // https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html - 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_frame.notify = handleFrame; - c.wl_signal_add(&self.wlr_cursor.events.frame, &self.listen_frame); - - 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 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(Self, "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 self = @fieldParentPtr(Self, "listen_button", listener.?); - const event = @ptrCast( - *c.wlr_event_pointer_button, - @alignCast(@alignOf(*c.wlr_event_pointer_button), data), - ); - var sx: f64 = undefined; - var sy: f64 = undefined; - - if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |wlr_surface| { - // If the found surface is a keyboard inteactive layer surface, - // give it keyboard focus. - if (c.wlr_surface_is_layer_surface(wlr_surface)) { - const wlr_layer_surface = c.wlr_layer_surface_v1_from_wlr_surface(wlr_surface); - if (wlr_layer_surface.*.current.keyboard_interactive) { - const layer_surface = @ptrCast( - *LayerSurface, - @alignCast(@alignOf(*LayerSurface), wlr_layer_surface.*.data), - ); - self.seat.setFocusRaw(.{ .layer = layer_surface }); - } - } - - // If the found surface is an xdg toplevel surface, send keyboard - // focus to the view. - if (c.wlr_surface_is_xdg_surface(wlr_surface)) { - const wlr_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(wlr_surface); - if (wlr_xdg_surface.*.role == .WLR_XDG_SURFACE_ROLE_TOPLEVEL) { - const view = @ptrCast(*View, @alignCast(@alignOf(*View), wlr_xdg_surface.*.data)); - self.seat.focus(view); - } - } - - _ = c.wlr_seat_pointer_notify_button( - self.seat.wlr_seat, - event.time_msec, - event.button, - event.state, - ); - } -} - -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 self = @fieldParentPtr(Self, "listen_frame", listener.?); - // Notify the client with pointer focus of the frame event. - c.wlr_seat_pointer_notify_frame(self.seat.wlr_seat); -} - -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 self = @fieldParentPtr(Self, "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(self.wlr_cursor, event.device, event.x, event.y); - self.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 self = @fieldParentPtr(Self, "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(self.wlr_cursor, event.device, event.delta_x, event.delta_y); - self.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 self = @fieldParentPtr(Self, "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 = self.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. - Log.Debug.log("Focused client set cursor", .{}); - c.wlr_cursor_set_surface( - self.wlr_cursor, - event.surface, - event.hotspot_x, - event.hotspot_y, - ); - } -} - -fn processMotion(self: Self, time: u32) void { - var sx: f64 = undefined; - var sy: f64 = undefined; - if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |wlr_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. - if (self.seat.input_manager.inputAllowed(wlr_surface)) { - const wlr_seat = self.seat.wlr_seat; - const focus_change = wlr_seat.pointer_state.focused_surface != wlr_surface; - if (focus_change) { - Log.Debug.log("Pointer notify enter at ({},{})", .{ sx, sy }); - c.wlr_seat_pointer_notify_enter(wlr_seat, wlr_surface, sx, sy); - } else { - // 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(wlr_seat, time, sx, sy); - } - return; - } - } - - // There is either no surface under the cursor or input is disallowed - // Reset the cursor image to the default - c.wlr_xcursor_manager_set_cursor_image( - self.wlr_xcursor_manager, - "left_ptr", - self.wlr_cursor, - ); - // 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(self.seat.wlr_seat); -} - -/// Find the topmost surface under the output layout coordinates lx/ly -/// returns the surface if found and sets the sx/sy parametes to the -/// surface coordinates. -fn surfaceAt(self: Self, lx: f64, ly: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { - // Find the output to check - const root = self.seat.input_manager.server.root; - const wlr_output = c.wlr_output_layout_output_at(root.wlr_output_layout, lx, ly) orelse - return null; - const output = @ptrCast( - *Output, - @alignCast(@alignOf(*Output), wlr_output.*.data orelse return null), - ); - - // Get output-local coords from the layout coords - var ox = lx; - var oy = ly; - c.wlr_output_layout_output_coords(root.wlr_output_layout, wlr_output, &ox, &oy); - - // Check layers and views from top to bottom - const layer_idxs = [_]usize{ - c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, - c.ZWLR_LAYER_SHELL_V1_LAYER_TOP, - c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, - c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, - }; - - // Check overlay layer incl. popups - if (layerSurfaceAt(output.*, output.layers[layer_idxs[0]], ox, oy, sx, sy, false)) |surface| { - return surface; - } - - // Check top-background popups only - for (layer_idxs[1..4]) |layer_idx| { - if (layerSurfaceAt(output.*, output.layers[layer_idx], ox, oy, sx, sy, true)) |surface| { - return surface; - } - } - - // Check top layer - if (layerSurfaceAt(output.*, output.layers[layer_idxs[1]], ox, oy, sx, sy, false)) |surface| { - return surface; - } - - // Check floating views then normal views - if (viewSurfaceAt(output.*, ox, oy, sx, sy, true)) |surface| { - return surface; - } - if (viewSurfaceAt(output.*, ox, oy, sx, sy, false)) |surface| { - return surface; - } - - // Check the bottom-background layers - for (layer_idxs[2..4]) |layer_idx| { - if (layerSurfaceAt(output.*, output.layers[layer_idx], ox, oy, sx, sy, false)) |surface| { - return surface; - } - } - - return null; -} - -/// Find the topmost surface on the given layer at ox,oy. Will only check -/// popups if popups_only is true. -fn layerSurfaceAt( - output: Output, - layer: std.TailQueue(LayerSurface), - ox: f64, - oy: f64, - sx: *f64, - sy: *f64, - popups_only: bool, -) ?*c.wlr_surface { - var it = layer.first; - while (it) |node| : (it = node.next) { - const layer_surface = &node.data; - const surface = c.wlr_layer_surface_v1_surface_at( - layer_surface.wlr_layer_surface, - ox - @intToFloat(f64, layer_surface.box.x), - oy - @intToFloat(f64, layer_surface.box.y), - sx, - sy, - ); - if (surface) |found| { - if (!popups_only) { - return found; - } else if (c.wlr_surface_is_xdg_surface(found)) { - const wlr_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(found); - if (wlr_xdg_surface.*.role == .WLR_XDG_SURFACE_ROLE_POPUP) { - return found; - } - } - } - } - return null; -} - -/// Find the topmost visible view surface (incl. popups) at ox,oy. Will -/// check only floating views if floating is true. -fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64, floating: bool) ?*c.wlr_surface { - var it = ViewStack(View).iterator(output.views.first, output.current_focused_tags); - while (it.next()) |node| { - const view = &node.view; - if (view.floating != floating) { - continue; - } - if (view.surfaceAt(ox, oy, sx, sy)) |found| { - return found; - } - } - return null; -} diff --git a/src/Decoration.zig b/src/Decoration.zig deleted file mode 100644 index 91c5bed..0000000 --- a/src/Decoration.zig +++ /dev/null @@ -1,53 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const DecorationManager = @import("DecorationManager.zig"); - -// TODO: this needs to listen for destroy and free nodes from the deco list -decoration_manager: *DecorationManager, -wlr_xdg_toplevel_decoration: *c.wlr_xdg_toplevel_decoration_v1, - -listen_request_mode: c.wl_listener, - -pub fn init( - self: *Self, - decoration_manager: *DecorationManager, - wlr_xdg_toplevel_decoration: *c.wlr_xdg_toplevel_decoration_v1, -) void { - self.decoration_manager = decoration_manager; - self.wlr_xdg_toplevel_decoration = wlr_xdg_toplevel_decoration; - - self.listen_request_mode.notify = handleRequestMode; - c.wl_signal_add(&self.wlr_xdg_toplevel_decoration.events.request_mode, &self.listen_request_mode); - - handleRequestMode(&self.listen_request_mode, self.wlr_xdg_toplevel_decoration); -} - -fn handleRequestMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_request_mode", listener.?); - // TODO: we might need to take this configure serial and do a transaction - _ = c.wlr_xdg_toplevel_decoration_v1_set_mode( - self.wlr_xdg_toplevel_decoration, - c.wlr_xdg_toplevel_decoration_v1_mode.WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE, - ); -} diff --git a/src/DecorationManager.zig b/src/DecorationManager.zig deleted file mode 100644 index 5a9abb4..0000000 --- a/src/DecorationManager.zig +++ /dev/null @@ -1,57 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const Decoration = @import("Decoration.zig"); -const Server = @import("Server.zig"); - -server: *Server, - -wlr_xdg_decoration_manager: *c.wlr_xdg_decoration_manager_v1, - -decorations: std.SinglyLinkedList(Decoration), - -listen_new_toplevel_decoration: c.wl_listener, - -pub fn init(self: *Self, server: *Server) !void { - self.server = server; - self.wlr_xdg_decoration_manager = c.wlr_xdg_decoration_manager_v1_create(server.wl_display) orelse - return error.CantCreateWlrXdgDecorationManager; - - self.listen_new_toplevel_decoration.notify = handleNewToplevelDecoration; - c.wl_signal_add( - &self.wlr_xdg_decoration_manager.events.new_toplevel_decoration, - &self.listen_new_toplevel_decoration, - ); -} - -fn handleNewToplevelDecoration(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_toplevel_decoration", listener.?); - const wlr_xdg_toplevel_decoration = @ptrCast( - *c.wlr_xdg_toplevel_decoration_v1, - @alignCast(@alignOf(*c.wlr_xdg_toplevel_decoration_v1), data), - ); - - const node = self.decorations.allocateNode(self.server.allocator) catch unreachable; - node.data.init(self, wlr_xdg_toplevel_decoration); - self.decorations.prepend(node); -} diff --git a/src/InputManager.zig b/src/InputManager.zig deleted file mode 100644 index 3a95346..0000000 --- a/src/InputManager.zig +++ /dev/null @@ -1,146 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const Log = @import("log.zig").Log; -const Seat = @import("Seat.zig"); -const Server = @import("Server.zig"); - -const default_seat_name = "default"; - -server: *Server, - -wlr_input_inhibit_manager: *c.wlr_input_inhibit_manager, - -seats: std.TailQueue(Seat), -default_seat: *Seat, - -exclusive_client: ?*c.wl_client, - -listen_inhibit_activate: c.wl_listener, -listen_inhibit_deactivate: c.wl_listener, -listen_new_input: c.wl_listener, - -pub fn init(self: *Self, server: *Server) !void { - self.server = server; - - // This is automatically freed when the display is destroyed - self.wlr_input_inhibit_manager = - c.wlr_input_inhibit_manager_create(server.wl_display) orelse - return error.CantCreateInputInhibitManager; - - self.seats = std.TailQueue(Seat).init(); - - const seat_node = try server.allocator.create(std.TailQueue(Seat).Node); - try seat_node.data.init(self, default_seat_name); - self.default_seat = &seat_node.data; - self.seats.prepend(seat_node); - - self.exclusive_client = null; - - // Set up all listeners - self.listen_inhibit_activate.notify = handleInhibitActivate; - c.wl_signal_add( - &self.wlr_input_inhibit_manager.events.activate, - &self.listen_inhibit_activate, - ); - - self.listen_inhibit_deactivate.notify = handleInhibitDeactivate; - c.wl_signal_add( - &self.wlr_input_inhibit_manager.events.deactivate, - &self.listen_inhibit_deactivate, - ); - - self.listen_new_input.notify = handleNewInput; - 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; - while (it) |node| : (it = node.next) { - const seat = &node.data; - seat.handleViewUnmap(view); - } -} - -/// Returns true if input is currently allowed on the passed surface. -pub fn inputAllowed(self: Self, wlr_surface: *c.wlr_surface) bool { - return if (self.exclusive_client) |exclusive_client| - exclusive_client == c.wl_resource_get_client(wlr_surface.resource) - else - true; -} - -fn handleInhibitActivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_inhibit_activate", listener.?); - - Log.Debug.log("Input inhibitor activated", .{}); - - // Clear focus of all seats - var seat_it = self.seats.first; - while (seat_it) |seat_node| : (seat_it = seat_node.next) { - seat_node.data.setFocusRaw(.{ .none = {} }); - } - - self.exclusive_client = self.wlr_input_inhibit_manager.active_client; -} - -fn handleInhibitDeactivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_inhibit_deactivate", listener.?); - - Log.Debug.log("Input inhibitor deactivated", .{}); - - self.exclusive_client = null; - - // Calling arrangeLayers() like this ensures that any top or overlay, - // keyboard-interactive surfaces will re-grab focus. - var output_it = self.server.root.outputs.first; - while (output_it) |output_node| : (output_it = output_node.next) { - output_node.data.arrangeLayers(); - } - - // After ensuring that any possible layer surface focus grab has occured, - // have each Seat handle focus. - var seat_it = self.seats.first; - while (seat_it) |seat_node| : (seat_it = seat_node.next) { - seat_node.data.focus(null); - } -} - -/// This event is raised by the backend when a new input device becomes available. -fn handleNewInput(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_input", listener.?); - const device = @ptrCast(*c.wlr_input_device, @alignCast(@alignOf(*c.wlr_input_device), data)); - - // TODO: suport multiple seats - if (self.seats.first) |seat_node| { - seat_node.data.addDevice(device) catch unreachable; - } -} diff --git a/src/Keyboard.zig b/src/Keyboard.zig deleted file mode 100644 index cbfd6cd..0000000 --- a/src/Keyboard.zig +++ /dev/null @@ -1,180 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const Log = @import("log.zig").Log; -const Seat = @import("Seat.zig"); - -seat: *Seat, -wlr_input_device: *c.wlr_input_device, -wlr_keyboard: *c.wlr_keyboard, - -listen_key: c.wl_listener, -listen_modifiers: c.wl_listener, - -pub fn init(self: *Self, seat: *Seat, wlr_input_device: *c.wlr_input_device) !void { - self.seat = seat; - self.wlr_input_device = wlr_input_device; - self.wlr_keyboard = @field(wlr_input_device, c.wlr_input_device_union).keyboard; - - // 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(.XKB_CONTEXT_NO_FLAGS) orelse - return error.CantCreateXkbContext; - defer c.xkb_context_unref(context); - - const keymap = c.xkb_keymap_new_from_names( - context, - &rules, - .XKB_KEYMAP_COMPILE_NO_FLAGS, - ) orelse - return error.CantCreateXkbKeymap; - defer c.xkb_keymap_unref(keymap); - - // TODO: handle failure after https://github.com/swaywm/wlroots/pull/2081 - c.wlr_keyboard_set_keymap(self.wlr_keyboard, keymap); - c.wlr_keyboard_set_repeat_info(self.wlr_keyboard, 25, 600); - - // Setup listeners for keyboard events - self.listen_key.notify = handleKey; - c.wl_signal_add(&self.wlr_keyboard.events.key, &self.listen_key); - - 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 { - // This event is raised when a key is pressed or released. - const self = @fieldParentPtr(Self, "listen_key", listener.?); - const event = @ptrCast( - *c.wlr_event_keyboard_key, - @alignCast(@alignOf(*c.wlr_event_keyboard_key), data), - ); - - const wlr_keyboard = self.wlr_keyboard; - - // Translate libinput keycode -> xkbcommon - const keycode = event.keycode + 8; - - // Get a list of keysyms as xkb reports them - var translated_keysyms: ?[*]c.xkb_keysym_t = undefined; - const translated_keysyms_len = c.xkb_state_key_get_syms( - wlr_keyboard.xkb_state, - keycode, - &translated_keysyms, - ); - - // Get a list of keysyms ignoring modifiers (e.g. 1 instead of !) - // Important for mappings like Mod+Shift+1 - var raw_keysyms: ?[*]c.xkb_keysym_t = undefined; - const layout_index = c.xkb_state_key_get_layout(wlr_keyboard.xkb_state, keycode); - const raw_keysyms_len = c.xkb_keymap_key_get_syms_by_level( - wlr_keyboard.keymap, - keycode, - layout_index, - 0, - &raw_keysyms, - ); - - var handled = false; - // TODO: These modifiers aren't properly handled, see sway's code - const modifiers = c.wlr_keyboard_get_modifiers(wlr_keyboard); - if (event.state == .WLR_KEY_PRESSED) { - var i: usize = 0; - while (i < translated_keysyms_len) : (i += 1) { - if (self.handleBuiltinMapping(translated_keysyms.?[i])) { - handled = true; - break; - } else if (self.seat.handleMapping(translated_keysyms.?[i], modifiers)) { - handled = true; - break; - } - } - if (!handled) { - i = 0; - while (i < raw_keysyms_len) : (i += 1) { - if (self.handleBuiltinMapping(raw_keysyms.?[i])) { - handled = true; - break; - } else if (self.seat.handleMapping(raw_keysyms.?[i], modifiers)) { - handled = true; - break; - } - } - } - } - - if (!handled) { - // Otherwise, we pass it along to the client. - const wlr_seat = self.seat.wlr_seat; - c.wlr_seat_set_keyboard(wlr_seat, self.wlr_input_device); - c.wlr_seat_keyboard_notify_key( - wlr_seat, - event.time_msec, - event.keycode, - @intCast(u32, @enumToInt(event.state)), - ); - } -} - -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 self = @fieldParentPtr(Self, "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(self.seat.wlr_seat, self.wlr_input_device); - - // Send modifiers to the client. - c.wlr_seat_keyboard_notify_modifiers( - self.seat.wlr_seat, - &self.wlr_keyboard.modifiers, - ); -} - -/// Handle any builtin, harcoded compsitor mappings such as VT switching. -/// Returns true if the keysym was handled. -fn handleBuiltinMapping(self: Self, keysym: c.xkb_keysym_t) bool { - if (keysym >= c.XKB_KEY_XF86Switch_VT_1 and keysym <= c.XKB_KEY_XF86Switch_VT_12) { - Log.Debug.log("Switch VT keysym received", .{}); - const wlr_backend = self.seat.input_manager.server.wlr_backend; - if (c.river_wlr_backend_is_multi(wlr_backend)) { - if (c.river_wlr_backend_get_session(wlr_backend)) |session| { - const vt = keysym - c.XKB_KEY_XF86Switch_VT_1 + 1; - Log.Debug.log("Switching to VT {}", .{vt}); - _ = c.wlr_session_change_vt(session, vt); - } - } - return true; - } - return false; -} diff --git a/src/LayerSurface.zig b/src/LayerSurface.zig deleted file mode 100644 index 088dd97..0000000 --- a/src/LayerSurface.zig +++ /dev/null @@ -1,196 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const Box = @import("Box.zig"); -const Log = @import("log.zig").Log; -const Output = @import("Output.zig"); -const XdgPopup = @import("XdgPopup.zig"); - -output: *Output, -wlr_layer_surface: *c.wlr_layer_surface_v1, - -box: Box, -layer: c.zwlr_layer_shell_v1_layer, - -// Listeners active the entire lifetime of the layser surface -listen_destroy: c.wl_listener, -listen_map: c.wl_listener, -listen_unmap: c.wl_listener, - -// Listeners only active while the layer surface is mapped -listen_commit: c.wl_listener, -listen_new_popup: c.wl_listener, - -pub fn init( - self: *Self, - output: *Output, - wlr_layer_surface: *c.wlr_layer_surface_v1, -) void { - self.output = output; - self.wlr_layer_surface = wlr_layer_surface; - wlr_layer_surface.data = self; - - self.layer = wlr_layer_surface.client_pending.layer; - - // Temporarily add to the output's list and apply the pending state to allow - // for inital arrangement which sends the first configure. - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - const list = &output.layers[@intCast(usize, @enumToInt(self.layer))]; - const stashed_state = wlr_layer_surface.current; - wlr_layer_surface.current = wlr_layer_surface.client_pending; - list.append(node); - output.arrangeLayers(); - list.remove(node); - wlr_layer_surface.current = stashed_state; - - // Set up listeners that are active for the entire lifetime of the layer surface - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&self.wlr_layer_surface.events.destroy, &self.listen_destroy); - - self.listen_map.notify = handleMap; - c.wl_signal_add(&self.wlr_layer_surface.events.map, &self.listen_map); - - self.listen_unmap.notify = handleUnmap; - c.wl_signal_add(&self.wlr_layer_surface.events.unmap, &self.listen_unmap); -} - -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); - const output = self.output; - - Log.Debug.log("Layer surface '{}' destroyed", .{self.wlr_layer_surface.namespace}); - - // Remove listeners active the entire lifetime of the layer surface - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_map.link); - c.wl_list_remove(&self.listen_unmap.link); - - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - output.root.server.allocator.destroy(node); -} - -fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_map", listener.?); - const wlr_layer_surface = self.wlr_layer_surface; - - Log.Debug.log("Layer surface '{}' mapped.", .{wlr_layer_surface.namespace}); - - // Add listeners that are only active while mapped - self.listen_commit.notify = handleCommit; - c.wl_signal_add(&wlr_layer_surface.surface.*.events.commit, &self.listen_commit); - - self.listen_new_popup.notify = handleNewPopup; - c.wl_signal_add(&wlr_layer_surface.events.new_popup, &self.listen_new_popup); - - c.wlr_surface_send_enter( - wlr_layer_surface.surface, - wlr_layer_surface.output, - ); - - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - self.output.layers[@intCast(usize, @enumToInt(self.layer))].append(node); -} - -fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_unmap", listener.?); - - Log.Debug.log("Layer surface '{}' unmapped.", .{self.wlr_layer_surface.namespace}); - - // This is a bit ugly: we need to use the wlr bool here since surfaces - // may be closed during the inital configure which we preform - // while unmapped. wlroots currently calls unmap unconditionally on close - // even if the surface is not mapped. I sent a patch which was merged, but - // we need to wait for a release to use it. - // - // TODO(wlroots): Remove this check on updating - // https://github.com/swaywm/wlroots/commit/11e94c406bb75c9a8990ce99489798411deb110c - if (self.wlr_layer_surface.mapped) { - // remove listeners only active while the layer surface is mapped - c.wl_list_remove(&self.listen_commit.link); - c.wl_list_remove(&self.listen_new_popup.link); - } - - // Remove from the output's list of layer surfaces - const self_node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - self.output.layers[@intCast(usize, @enumToInt(self.layer))].remove(self_node); - - // If the unmapped surface is focused, clear focus - var it = self.output.root.server.input_manager.seats.first; - while (it) |node| : (it = node.next) { - const seat = &node.data; - if (seat.focused_layer) |current_focus| { - if (current_focus == self) { - seat.setFocusRaw(.{ .none = {} }); - } - } - } - - // This gives exclusive focus to a keyboard interactive top or overlay layer - // surface if there is one. - self.output.arrangeLayers(); - - // Ensure that focus is given to the appropriate view if there is no - // other top/overlay layer surface to grab focus. - it = self.output.root.server.input_manager.seats.first; - while (it) |node| : (it = node.next) { - const seat = &node.data; - seat.focus(null); - } -} - -fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_commit", listener.?); - const wlr_layer_surface = self.wlr_layer_surface; - - if (self.wlr_layer_surface.output == null) { - Log.Error.log("Layer surface committed with null output", .{}); - return; - } - - // If the layer changed, move the LayerSurface to the proper list - if (self.layer != self.wlr_layer_surface.current.layer) { - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - - const old_layer_idx = @intCast(usize, @enumToInt(self.layer)); - self.output.layers[old_layer_idx].remove(node); - - self.layer = self.wlr_layer_surface.current.layer; - - const new_layer_idx = @intCast(usize, @enumToInt(self.layer)); - self.output.layers[new_layer_idx].append(node); - } - - // TODO: only reconfigure if things haven't changed - // https://github.com/swaywm/wlroots/issues/1079 - self.output.arrangeLayers(); -} - -fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_popup", listener.?); - const wlr_xdg_popup = @ptrCast(*c.wlr_xdg_popup, @alignCast(@alignOf(*c.wlr_xdg_popup), data)); - const allocator = self.output.root.server.allocator; - - // This will free itself on destroy - var xdg_popup = allocator.create(XdgPopup) catch unreachable; - xdg_popup.init(self.output, &self.box, wlr_xdg_popup); -} diff --git a/src/Mapping.zig b/src/Mapping.zig deleted file mode 100644 index 61df624..0000000 --- a/src/Mapping.zig +++ /dev/null @@ -1,46 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -keysym: c.xkb_keysym_t, -modifiers: u32, -command_args: []const []const u8, - -pub fn init( - allocator: *std.mem.Allocator, - keysym: c.xkb_keysym_t, - modifiers: u32, - command_args: []const []const u8, -) !Self { - var owned_args = try allocator.alloc([]u8, command_args.len); - for (command_args) |arg, i| owned_args[i] = try std.mem.dupe(allocator, u8, arg); - return Self{ - .keysym = keysym, - .modifiers = modifiers, - .command_args = owned_args, - }; -} - -pub fn deinit(self: Self, allocator: *std.mem.Allocator) void { - for (self.command_args) |arg| allocator.free(arg); - allocator.free(self.command_args); -} diff --git a/src/Output.zig b/src/Output.zig deleted file mode 100644 index a32c000..0000000 --- a/src/Output.zig +++ /dev/null @@ -1,744 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); -const render = @import("render.zig"); - -const Box = @import("Box.zig"); -const LayerSurface = @import("LayerSurface.zig"); -const Log = @import("log.zig").Log; -const Root = @import("Root.zig"); -const View = @import("View.zig"); -const ViewStack = @import("view_stack.zig").ViewStack; - -root: *Root, -wlr_output: *c.wlr_output, - -/// All layer surfaces on the output, indexed by the layer enum. -layers: [4]std.TailQueue(LayerSurface), - -/// The area left for views and other layer surfaces after applying the -/// exclusive zones of exclusive layer surfaces. -usable_box: Box, - -/// The top of the stack is the "most important" view. -views: ViewStack(View), - -/// A bit field of focused tags -current_focused_tags: u32, -pending_focused_tags: ?u32, - -/// Number of views in "master" section of the screen. -master_count: u32, - -/// Percentage of the total screen that the master section takes up. -master_factor: f64, - -/// Current layout of the output. -layout: Layout, - -// All listeners for this output, in alphabetical order -listen_destroy: c.wl_listener, -listen_frame: c.wl_listener, -listen_mode: c.wl_listener, - -// All possible layouts. -pub const Layout = enum { - TopMaster, - RightMaster, - BottomMaster, - LeftMaster, - Full, -}; - -const LayoutName = struct { - name: []const u8, - layout: Layout, -}; - -// zig fmt: off -const layout_names = [_]LayoutName { - .{ .name = "TopMaster", .layout = Layout.TopMaster, }, - .{ .name = "RightMaster", .layout = Layout.RightMaster, }, - .{ .name = "BottomMaster", .layout = Layout.BottomMaster, }, - .{ .name = "LeftMaster", .layout = Layout.LeftMaster, }, - .{ .name = "Full", .layout = Layout.Full, }, -}; -// zig fmt: on - -pub fn getLayoutByName(self: Self, name: []const u8) Layout { - for (layout_names) |current| { - if (std.mem.eql(u8, name, current.name)) { - return current.layout; - } - } - Log.Error.log("Layout '{}' does not exist", .{name}); - // In case of error default to LeftMaster - return Layout.LeftMaster; -} - -pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void { - // 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, - // refresh rate), and each monitor supports only a specific set of modes. We - // just pick the monitor's preferred mode, a more sophisticated compositor - // would let the user configure it. - - // if not empty - if (c.wl_list_empty(&wlr_output.modes) == 0) { - // TODO: handle failure - const mode = c.wlr_output_preferred_mode(wlr_output); - c.wlr_output_set_mode(wlr_output, mode); - c.wlr_output_enable(wlr_output, true); - if (!c.wlr_output_commit(wlr_output)) { - return error.CantCommitWlrOutputMode; - } - } - - self.root = root; - self.wlr_output = wlr_output; - wlr_output.data = self; - - for (self.layers) |*layer| { - layer.* = std.TailQueue(LayerSurface).init(); - } - - self.views.init(); - - self.current_focused_tags = 1 << 0; - self.pending_focused_tags = null; - - self.master_count = 1; - - self.master_factor = 0.6; - - // LeftMaster is the default layout for all outputs - self.layout = Layout.LeftMaster; - - // Set up listeners - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&wlr_output.events.destroy, &self.listen_destroy); - - self.listen_frame.notify = handleFrame; - c.wl_signal_add(&wlr_output.events.frame, &self.listen_frame); - - self.listen_mode.notify = handleMode; - c.wl_signal_add(&wlr_output.events.mode, &self.listen_mode); - - if (c.river_wlr_output_is_noop(wlr_output)) { - // A noop output is always 0 x 0 - self.usable_box = .{ - .x = 0, - .y = 0, - .width = 0, - .height = 0, - }; - } else { - // Add the new output to the layout. The add_auto function arranges outputs - // from left-to-right in the order they appear. A more sophisticated - // compositor would let the user configure the arrangement of outputs in the - // layout. This automatically creates an output global on the wl_display. - c.wlr_output_layout_add_auto(root.wlr_output_layout, wlr_output); - - var width: c_int = undefined; - var height: c_int = undefined; - c.wlr_output_effective_resolution(wlr_output, &width, &height); - self.usable_box = .{ - .x = 0, - .y = 0, - .width = @intCast(u32, width), - .height = @intCast(u32, height), - }; - } -} - -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); - } -} - -pub fn getRenderer(self: Self) *c.wlr_renderer { - return c.river_wlr_backend_get_renderer(self.wlr_output.backend); -} - -const MasterPosition = enum { - Top, - Right, - Bottom, - Left, -}; - -/// Default layout of master-stack and slave-stack. -pub fn layoutMasterStack(self: *Self, visible_count: u32, output_tags: u32, position: MasterPosition) void { - const master_count = std.math.min(self.master_count, visible_count); - const slave_count = if (master_count >= visible_count) 0 else visible_count - master_count; - - const border_width = self.root.server.config.border_width; - const view_padding = self.root.server.config.view_padding; - const outer_padding = self.root.server.config.outer_padding; - - const layout_width = @intCast(u32, self.usable_box.width) - outer_padding * 2; - const layout_height = @intCast(u32, self.usable_box.height) - outer_padding * 2; - - // Depending on position of the master area, - // the *_stack_size is either width or height - var master_stack_size: u32 = undefined; - var slave_stack_size: u32 = undefined; - if (master_count > 0 and slave_count > 0) { - // If both master and slave views are present - if (position == MasterPosition.Right or position == MasterPosition.Left) { - master_stack_size = @floatToInt(u32, @round(@intToFloat(f64, layout_width) * self.master_factor)); - slave_stack_size = layout_width - master_stack_size; - } else { - master_stack_size = @floatToInt(u32, @round(@intToFloat(f64, layout_height) * self.master_factor)); - slave_stack_size = layout_height - master_stack_size; - } - } else if (master_count > 0) { - if (position == MasterPosition.Right or position == MasterPosition.Left) { - master_stack_size = layout_width; - } else { - master_stack_size = layout_height; - } - slave_stack_size = 0; - } else { - if (position == MasterPosition.Right or position == MasterPosition.Left) { - slave_stack_size = layout_width; - } else { - slave_stack_size = layout_height; - } - master_stack_size = 0; - } - - var i: u32 = 0; - var it = ViewStack(View).pendingIterator(self.views.first, output_tags); - while (it.next()) |node| { - const view = &node.view; - - if (view.floating) { - continue; - } - - var new_box: Box = undefined; - - // Add the remainder to the first master/slave to ensure every - // pixel of height is used - if (i < master_count) { - if (position == MasterPosition.Top) { // Top master - const master_width = @divTrunc(layout_width, master_count); - const master_width_rem = layout_width % master_count; - new_box = .{ - .x = @intCast(i32, i * master_width + if (i > 0) master_width_rem else 0), - .y = 0, - .width = master_width + if (i == 0) master_width_rem else 0, - .height = master_stack_size, - }; - } else if (position == MasterPosition.Right) { // Right master - const master_height = @divTrunc(layout_height, master_count); - const master_height_rem = layout_height % master_count; - new_box = .{ - .x = @intCast(i32, slave_stack_size), - .y = @intCast(i32, i * master_height + if (i > 0) master_height_rem else 0), - .width = master_stack_size, - .height = master_height + if (i == 0) master_height_rem else 0, - }; - } else if (position == MasterPosition.Bottom) { // Bottom master - const master_width = @divTrunc(layout_width, master_count); - const master_width_rem = layout_width % master_count; - new_box = .{ - .x = @intCast(i32, i * master_width + if (i > 0) master_width_rem else 0), - .y = @intCast(i32, slave_stack_size), - .width = master_width + if (i == 0) master_width_rem else 0, - .height = master_stack_size, - }; - } else { // Left master - const master_height = @divTrunc(layout_height, master_count); - const master_height_rem = layout_height % master_count; - new_box = .{ - .x = 0, - .y = @intCast(i32, i * master_height + if (i > 0) master_height_rem else 0), - .width = master_stack_size, - .height = master_height + if (i == 0) master_height_rem else 0, - }; - } - } else { - if (position == MasterPosition.Top) { // Top master - const slave_width = @divTrunc(layout_width, slave_count); - const slave_width_rem = layout_width % slave_count; - new_box = .{ - .x = @intCast(i32, (i - master_count) * slave_width + if (i > master_count) slave_width_rem else 0), - .y = @intCast(i32, master_stack_size), - .width = slave_width + if (i == master_count) slave_width_rem else 0, - .height = slave_stack_size, - }; - } else if (position == MasterPosition.Right) { // Right master - const slave_height = @divTrunc(layout_height, slave_count); - const slave_height_rem = layout_height % slave_count; - new_box = .{ - .x = 0, - .y = @intCast(i32, (i - master_count) * slave_height + if (i > master_count) slave_height_rem else 0), - .width = slave_stack_size, - .height = slave_height + if (i == master_count) slave_height_rem else 0, - }; - } else if (position == MasterPosition.Bottom) { // Bottom master - const slave_width = @divTrunc(layout_width, slave_count); - const slave_width_rem = layout_width % slave_count; - new_box = .{ - .x = @intCast(i32, (i - master_count) * slave_width + if (i > master_count) slave_width_rem else 0), - .y = 0, - .width = slave_width + if (i == master_count) slave_width_rem else 0, - .height = slave_stack_size, - }; - } else { // Left master - const slave_height = @divTrunc(layout_height, slave_count); - const slave_height_rem = layout_height % slave_count; - new_box = .{ - .x = @intCast(i32, master_stack_size), - .y = @intCast(i32, (i - master_count) * slave_height + if (i > master_count) slave_height_rem else 0), - .width = slave_stack_size, - .height = slave_height + if (i == master_count) slave_height_rem else 0, - }; - } - } - - // Apply offsets from borders and padding - const xy_offset = @intCast(i32, border_width + outer_padding + view_padding); - new_box.x += self.usable_box.x + xy_offset; - new_box.y += self.usable_box.y + xy_offset; - - // Reduce size to allow space for borders/padding - const delta_size = (border_width + view_padding) * 2; - new_box.width -= delta_size; - new_box.height -= delta_size; - - // Set the view's pending box to the new dimensions - view.pending_box = new_box; - - i += 1; - } -} - -/// Wrapper for default layout with master area on the top -pub fn layoutTopMaster(self: *Self, visible_count: u32, output_tags: u32) void { - layoutMasterStack(self, visible_count, output_tags, MasterPosition.Top); -} - -/// Wrapper for default layout with master area on the right -pub fn layoutRightMaster(self: *Self, visible_count: u32, output_tags: u32) void { - layoutMasterStack(self, visible_count, output_tags, MasterPosition.Right); -} - -/// Wrapper for default layout with master area on the bottom -pub fn layoutBottomMaster(self: *Self, visible_count: u32, output_tags: u32) void { - layoutMasterStack(self, visible_count, output_tags, MasterPosition.Bottom); -} - -/// Wrapper for default layout with master area on the left -pub fn layoutLeftMaster(self: *Self, visible_count: u32, output_tags: u32) void { - layoutMasterStack(self, visible_count, output_tags, MasterPosition.Left); -} - -/// A layout in which every window uses the maximum available space. -pub fn layoutFull(self: *Self, visible_count: u32, output_tags: u32) void { - const border_width = self.root.server.config.border_width; - const view_padding = self.root.server.config.view_padding; - const outer_padding = self.root.server.config.outer_padding; - - const layout_width = @intCast(u32, self.usable_box.width) - - (outer_padding * 2) - (border_width * 2) - (view_padding * 2); - const layout_height = @intCast(u32, self.usable_box.height) - - (outer_padding * 2) - (border_width * 2) - (view_padding * 2); - const x_offset = self.usable_box.x + @intCast(i32, outer_padding + border_width + view_padding); - const y_offset = self.usable_box.y + @intCast(i32, outer_padding + border_width + view_padding); - - var i: u32 = 0; - var it = ViewStack(View).pendingIterator(self.views.first, output_tags); - while (it.next()) |node| { - const view = &node.view; - - if (view.floating) { - continue; - } - - var new_box: Box = undefined; - new_box = .{ - .x = x_offset, - .y = y_offset, - .width = layout_width, - .height = layout_height, - }; - - view.pending_box = new_box; - - i += 1; - } -} - -/// Arrange all views on the output for the current layout. Modifies only -/// pending state, the changes are not appplied until a transaction is started -/// and completed. -pub fn arrangeViews(self: *Self) void { - // If the output has a zero dimension, trying to arrange would cause - // underflow and is pointless anyway - if (self.usable_box.width == 0 or self.usable_box.height == 0) { - return; - } - - const output_tags = if (self.pending_focused_tags) |tags| - tags - else - self.current_focused_tags; - - const visible_count = blk: { - var count: u32 = 0; - var it = ViewStack(View).pendingIterator(self.views.first, output_tags); - while (it.next()) |node| { - if (node.view.floating) { - continue; - } - count += 1; - } - break :blk count; - }; - - // A single view should always use the maximum available space. This is - // implemented via the "full" layout to remove the need of every single - // layout to explicitly handle this edge case or the other edge case of - // no visible views. - if (visible_count <= 1) { - layoutFull(self, visible_count, output_tags); - return; - } - - switch (self.layout) { - .Full => layoutFull(self, visible_count, output_tags), - .TopMaster => layoutTopMaster(self, visible_count, output_tags), - .RightMaster => layoutRightMaster(self, visible_count, output_tags), - .BottomMaster => layoutBottomMaster(self, visible_count, output_tags), - .LeftMaster => layoutLeftMaster(self, visible_count, output_tags), - } -} - -/// Arrange all layer surfaces of this output and addjust the usable aread -pub fn arrangeLayers(self: *Self) void { - const full_box = blk: { - var width: c_int = undefined; - var height: c_int = undefined; - c.wlr_output_effective_resolution(self.wlr_output, &width, &height); - break :blk Box{ - .x = 0, - .y = 0, - .width = @intCast(u32, width), - .height = @intCast(u32, height), - }; - }; - - // This box is modified as exclusive zones are applied - var usable_box = full_box; - - const layer_idxs = [_]usize{ - c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, - c.ZWLR_LAYER_SHELL_V1_LAYER_TOP, - c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, - c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, - }; - - // Arrange all layer surfaces with exclusive zones, applying them to the - // usable box along the way. - for (layer_idxs) |layer| { - self.arrangeLayer(self.layers[layer], full_box, &usable_box, true); - } - - // If the the usable_box has changed, we need to rearrange the output - if (!std.meta.eql(self.usable_box, usable_box)) { - self.usable_box = usable_box; - self.root.arrange(); - } - - // Arrange the layers without exclusive zones - for (layer_idxs) |layer| { - self.arrangeLayer(self.layers[layer], full_box, &usable_box, false); - } - - // Find the topmost layer surface in the top or overlay layers which - // requests keyboard interactivity if any. - const topmost_surface = outer: for (layer_idxs[0..2]) |layer| { - // Iterate in reverse order since the last layer is rendered on top - var it = self.layers[layer].last; - while (it) |node| : (it = node.prev) { - const layer_surface = &node.data; - if (layer_surface.wlr_layer_surface.current.keyboard_interactive) { - break :outer layer_surface; - } - } - } else null; - - var it = self.root.server.input_manager.seats.first; - while (it) |node| : (it = node.next) { - const seat = &node.data; - - // Only grab focus of seats which have the output focused - if (seat.focused_output != self) { - continue; - } - - if (topmost_surface) |to_focus| { - // If we found a surface that requires focus, grab the focus of all - // seats. - seat.setFocusRaw(.{ .layer = to_focus }); - } else if (seat.focused_layer) |current_focus| { - // If the seat is currently focusing a layer without keyboard - // interactivity, clear the focused layer. - if (!current_focus.wlr_layer_surface.current.keyboard_interactive) { - seat.setFocusRaw(.{ .none = {} }); - seat.focus(null); - } - } - } -} - -/// Arrange the layer surfaces of a given layer -fn arrangeLayer( - self: *Self, - layer: std.TailQueue(LayerSurface), - full_box: Box, - usable_box: *Box, - exclusive: bool, -) void { - var it = layer.first; - while (it) |node| : (it = node.next) { - const layer_surface = &node.data; - const current_state = layer_surface.wlr_layer_surface.current; - - // If the value of exclusive_zone is greater than zero, then it exclusivly - // occupies some area of the screen. - if (exclusive != (current_state.exclusive_zone > 0)) { - continue; - } - - // If the exclusive zone is set to -1, this means the the client would like - // to ignore any exclusive zones and use the full area of the output. - const bounds = if (current_state.exclusive_zone == -1) &full_box else usable_box; - - var new_box: Box = undefined; - - // Horizontal alignment - const anchor_left = @intCast(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); - const anchor_right = @intCast(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); - if (current_state.desired_width == 0) { - const anchor_left_right = anchor_left | anchor_right; - if (current_state.anchor & anchor_left_right == anchor_left_right) { - new_box.x = bounds.x + @intCast(i32, current_state.margin.left); - new_box.width = bounds.width - - (current_state.margin.left + current_state.margin.right); - } else { - Log.Error.log( - "Protocol Error: layer surface '{}' requested width 0 without anchoring to opposite edges.", - .{layer_surface.wlr_layer_surface.namespace}, - ); - c.wlr_layer_surface_v1_close(layer_surface.wlr_layer_surface); - continue; - } - } else if (current_state.anchor & anchor_left != 0) { - new_box.x = bounds.x + @intCast(i32, current_state.margin.left); - new_box.width = current_state.desired_width; - } else if (current_state.anchor & anchor_right != 0) { - new_box.x = bounds.x + @intCast(i32, bounds.width - current_state.desired_width - - current_state.margin.right); - new_box.width = current_state.desired_width; - } else { - new_box.x = bounds.x + @intCast(i32, bounds.width / 2 - current_state.desired_width / 2); - new_box.width = current_state.desired_width; - } - - // Vertical alignment - const anchor_top = @intCast(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP); - const anchor_bottom = @intCast(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM); - if (current_state.desired_height == 0) { - const anchor_top_bottom = anchor_top | anchor_bottom; - if (current_state.anchor & anchor_top_bottom == anchor_top_bottom) { - new_box.y = bounds.y + @intCast(i32, current_state.margin.top); - new_box.height = bounds.height - - (current_state.margin.top + current_state.margin.bottom); - } else { - Log.Error.log( - "Protocol Error: layer surface '{}' requested height 0 without anchoring to opposite edges.", - .{layer_surface.wlr_layer_surface.namespace}, - ); - c.wlr_layer_surface_v1_close(layer_surface.wlr_layer_surface); - continue; - } - } else if (current_state.anchor & anchor_top != 0) { - new_box.y = bounds.y + @intCast(i32, current_state.margin.top); - new_box.height = current_state.desired_height; - } else if (current_state.anchor & anchor_bottom != 0) { - new_box.y = bounds.y + @intCast(i32, bounds.height - current_state.desired_height - - current_state.margin.bottom); - new_box.height = current_state.desired_height; - } else { - new_box.y = bounds.y + @intCast(i32, bounds.height / 2 - current_state.desired_height / 2); - new_box.height = current_state.desired_height; - } - - layer_surface.box = new_box; - - // Apply the exclusive zone to the current bounds - const edges = [4]struct { - anchors: u32, - to_increase: ?*i32, - to_decrease: ?*u32, - margin: u32, - }{ - .{ - .anchors = anchor_left | anchor_right | anchor_top, - .to_increase = &usable_box.y, - .to_decrease = &usable_box.height, - .margin = current_state.margin.top, - }, - .{ - .anchors = anchor_left | anchor_right | anchor_bottom, - .to_increase = null, - .to_decrease = &usable_box.height, - .margin = current_state.margin.bottom, - }, - .{ - .anchors = anchor_left | anchor_top | anchor_bottom, - .to_increase = &usable_box.x, - .to_decrease = &usable_box.width, - .margin = current_state.margin.left, - }, - .{ - .anchors = anchor_right | anchor_top | anchor_bottom, - .to_increase = null, - .to_decrease = &usable_box.width, - .margin = current_state.margin.right, - }, - }; - - for (edges) |edge| { - if (current_state.anchor & edge.anchors == edge.anchors and - current_state.exclusive_zone + @intCast(i32, edge.margin) > 0) - { - const delta = current_state.exclusive_zone + @intCast(i32, edge.margin); - if (edge.to_increase) |value| { - value.* += delta; - } - if (edge.to_decrease) |value| { - value.* -= @intCast(u32, delta); - } - } - } - - // Tell the client to assume the new size - Log.Debug.log("send configure, {} x {}", .{ layer_surface.box.width, layer_surface.box.height }); - c.wlr_layer_surface_v1_configure( - layer_surface.wlr_layer_surface, - layer_surface.box.width, - layer_surface.box.height, - ); - } -} - -/// Called when the output is destroyed. Evacuate all views from the output -/// and then remove it from the list of outputs. -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); - const root = self.root; - - Log.Debug.log("Output {} destroyed", .{self.wlr_output.name}); - - // Use the first output in the list that is not the one being destroyed. - // If there is no other real output, use the noop output. - var output_it = root.outputs.first; - const fallback_output = while (output_it) |output_node| : (output_it = output_node.next) { - if (&output_node.data != self) { - break &output_node.data; - } - } else &root.noop_output; - - // Move all views from the destroyed output to the fallback one - while (self.views.last) |node| { - const view = &node.view; - view.sendToOutput(fallback_output); - } - - // Close all layer surfaces on the destroyed output - for (self.layers) |*layer, layer_idx| { - while (layer.pop()) |node| { - const layer_surface = &node.data; - // We need to move the closing layer surface to the noop output - // since it may not be immediately destoryed. This just a request - // to close which will trigger unmap and destroy events in - // response, and the LayerSurface needs a valid output to - // handle them. - root.noop_output.layers[layer_idx].prepend(node); - layer_surface.output = &root.noop_output; - c.wlr_layer_surface_v1_close(layer_surface.wlr_layer_surface); - } - } - - // If any seat has the destroyed output focused, focus the fallback one - var seat_it = root.server.input_manager.seats.first; - while (seat_it) |seat_node| : (seat_it = seat_node.next) { - const seat = &seat_node.data; - if (seat.focused_output == self) { - seat.focused_output = fallback_output; - seat.focus(null); - } - } - - // Remove all listeners - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_frame.link); - c.wl_list_remove(&self.listen_mode.link); - - // Clean up the wlr_output - self.wlr_output.data = null; - - // Remove the destroyed output from the list - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - root.outputs.remove(node); - root.server.allocator.destroy(node); - - // Arrange the root in case evacuated views affect the layout - root.arrange(); -} - -fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - // This function is called every time an output is ready to display a frame, - // generally at the output's refresh rate (e.g. 60Hz). - const self = @fieldParentPtr(Self, "listen_frame", listener.?); - render.renderOutput(self); -} - -fn handleMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_mode", listener.?); - self.arrangeLayers(); - self.root.arrange(); -} diff --git a/src/Root.zig b/src/Root.zig deleted file mode 100644 index f87d7ae..0000000 --- a/src/Root.zig +++ /dev/null @@ -1,247 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); -const build_options = @import("build_options"); - -const c = @import("c.zig"); - -const Log = @import("log.zig").Log; -const Output = @import("Output.zig"); -const Server = @import("Server.zig"); -const View = @import("View.zig"); -const ViewStack = @import("view_stack.zig").ViewStack; -const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig"); - -/// Responsible for all windowing operations -server: *Server, - -wlr_output_layout: *c.wlr_output_layout, -outputs: std.TailQueue(Output), - -/// This output is used internally when no real outputs are available. -/// It is not advertised to clients. -noop_output: Output, - -/// This list stores all unmanaged Xwayland windows. This needs to be in root -/// since X is like the wild west and who knows where these things will go. -xwayland_unmanaged_views: if (build_options.xwayland) std.TailQueue(XwaylandUnmanaged) else void, - -/// Number of pending configures sent in the current transaction. -/// A value of 0 means there is no current transaction. -pending_configures: u32, - -/// Handles timeout of transactions -transaction_timer: ?*c.wl_event_source, - -pub fn init(self: *Self, server: *Server) !void { - self.server = server; - - // Create an output layout, which a wlroots utility for working with an - // arrangement of screens in a physical layout. - self.wlr_output_layout = c.wlr_output_layout_create() orelse - return error.CantCreateWlrOutputLayout; - errdefer c.wlr_output_layout_destroy(self.wlr_output_layout); - - self.outputs = std.TailQueue(Output).init(); - - const noop_wlr_output = c.river_wlr_noop_add_output(server.noop_backend) orelse - return error.CantAddNoopOutput; - try self.noop_output.init(self, noop_wlr_output); - - if (build_options.xwayland) { - self.xwayland_unmanaged_views = std.TailQueue(XwaylandUnmanaged).init(); - } - - self.pending_configures = 0; - - self.transaction_timer = null; -} - -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); -} - -pub fn addOutput(self: *Self, wlr_output: *c.wlr_output) void { - // TODO: Handle failure - const node = self.outputs.allocateNode(self.server.allocator) catch unreachable; - node.data.init(self, wlr_output) catch unreachable; - self.outputs.append(node); - - // if we previously had no real outputs, move focus from the noop output - // to the new one. - if (self.outputs.len == 1) { - // TODO: move views from the noop output to the new one and focus(null) - var it = self.server.input_manager.seats.first; - while (it) |seat_node| : (it = seat_node.next) { - seat_node.data.focused_output = &self.outputs.first.?.data; - } - } -} - -/// Clear the current focus. -pub fn clearFocus(self: *Self) void { - if (self.focused_view) |view| { - _ = c.wlr_xdg_toplevel_set_activated(view.wlr_xdg_surface, false); - } - self.focused_view = null; -} - -/// Arrange all views on all outputs and then start a transaction. -pub fn arrange(self: *Self) void { - var it = self.outputs.first; - while (it) |output_node| : (it = output_node.next) { - output_node.data.arrangeViews(); - } - self.startTransaction(); -} - -/// Initiate an atomic change to the layout. This change will not be -/// applied until all affected clients ack a configure and commit a buffer. -fn startTransaction(self: *Self) void { - // If a new transaction is started while another is in progress, we need - // to reset the pending count to 0 and clear serials from the views - self.pending_configures = 0; - - // Iterate over all views of all outputs - var output_it = self.outputs.first; - while (output_it) |node| : (output_it = node.next) { - const output = &node.data; - var view_it = ViewStack(View).iterator(output.views.first, 0xFFFFFFFF); - while (view_it.next()) |view_node| { - const view = &view_node.view; - // Clear the serial in case this transaction is interrupting a prior one. - view.pending_serial = null; - - if (view.needsConfigure()) { - view.configure(); - self.pending_configures += 1; - - // We save the current buffer, so we can send an early - // frame done event to give the client a head start on - // redrawing. - view.sendFrameDone(); - } - - // If there is a saved buffer present, then this transaction is interrupting - // a previous transaction and we should keep the old buffer. - if (view.stashed_buffer == null) { - view.stashBuffer(); - } - } - } - - if (self.pending_configures > 0) { - Log.Debug.log( - "Started transaction with {} pending configures.", - .{self.pending_configures}, - ); - - // TODO: log failure to create timer and commit immediately - self.transaction_timer = c.wl_event_loop_add_timer( - self.server.wl_event_loop, - handleTimeout, - self, - ); - - // Set timeout to 200ms - if (c.wl_event_source_timer_update(self.transaction_timer, 200) == -1) { - // TODO: handle failure - } - } else { - self.commitTransaction(); - } -} - -fn handleTimeout(data: ?*c_void) callconv(.C) c_int { - const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), data)); - - Log.Error.log("Transaction timed out. Some imperfect frames may be shown.", .{}); - - self.commitTransaction(); - - return 0; -} - -pub fn notifyConfigured(self: *Self) void { - self.pending_configures -= 1; - if (self.pending_configures == 0) { - // Stop the timer, as we didn't timeout - if (c.wl_event_source_timer_update(self.transaction_timer, 0) == -1) { - // TODO: handle failure - } - self.commitTransaction(); - } -} - -/// Apply the pending state and drop stashed buffers. This means that -/// the next frame drawn will be the post-transaction state of the -/// layout. Should only be called after all clients have configured for -/// the new layout. If called early imperfect frames may be drawn. -fn commitTransaction(self: *Self) void { - // TODO: apply damage properly - - // Ensure this is set to 0 to avoid entering invalid state (e.g. if called due to timeout) - self.pending_configures = 0; - - // Iterate over all views of all outputs - var output_it = self.outputs.first; - while (output_it) |output_node| : (output_it = output_node.next) { - const output = &output_node.data; - - // If there were pending focused tags, make them the current focus - if (output.pending_focused_tags) |tags| { - Log.Debug.log( - "changing current focus: {b:0>10} to {b:0>10}", - .{ output.current_focused_tags, tags }, - ); - output.current_focused_tags = tags; - output.pending_focused_tags = null; - } - - var view_it = ViewStack(View).iterator(output.views.first, 0xFFFFFFFF); - while (view_it.next()) |view_node| { - const view = &view_node.view; - // Ensure that all pending state is cleared - view.pending_serial = null; - if (view.pending_box) |state| { - view.current_box = state; - view.pending_box = null; - } - - // Apply possible pending tags - if (view.pending_tags) |tags| { - view.current_tags = tags; - view.pending_tags = null; - } - - view.dropStashedBuffer(); - } - } - - // Iterate over all seats and update focus - var it = self.server.input_manager.seats.first; - while (it) |seat_node| : (it = seat_node.next) { - seat_node.data.focus(null); - } -} diff --git a/src/Seat.zig b/src/Seat.zig deleted file mode 100644 index 82979fc..0000000 --- a/src/Seat.zig +++ /dev/null @@ -1,312 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); -const command = @import("command.zig"); - -const Cursor = @import("Cursor.zig"); -const InputManager = @import("InputManager.zig"); -const Keyboard = @import("Keyboard.zig"); -const LayerSurface = @import("LayerSurface.zig"); -const Output = @import("Output.zig"); -const View = @import("View.zig"); -const ViewStack = @import("view_stack.zig").ViewStack; - -const FocusTarget = union(enum) { - view: *View, - layer: *LayerSurface, - none: void, -}; - -input_manager: *InputManager, -wlr_seat: *c.wlr_seat, - -/// Multiple mice are handled by the same Cursor -cursor: Cursor, - -/// Mulitple keyboards are handled separately -keyboards: std.TailQueue(Keyboard), - -/// ID of the current keymap mode -mode_id: usize, - -/// Currently focused output, may be the noop output if no -focused_output: *Output, - -/// Currently focused view if any -focused_view: ?*View, - -/// Stack of views in most recently focused order -/// If there is a currently focused view, it is on top. -focus_stack: ViewStack(*View), - -/// Currently focused layer, if any. While this is non-null, no views may -/// recieve focus. -focused_layer: ?*LayerSurface, - -listen_request_set_selection: c.wl_listener, - -pub fn init(self: *Self, input_manager: *InputManager, name: []const u8) !void { - self.input_manager = input_manager; - - // This will be automatically destroyed when the display is destroyed - self.wlr_seat = c.wlr_seat_create(input_manager.server.wl_display, name.ptr) orelse - return error.CantCreateWlrSeat; - - try self.cursor.init(self); - errdefer self.cursor.destroy(); - - self.keyboards = std.TailQueue(Keyboard).init(); - - self.mode_id = 0; - - self.focused_output = &self.input_manager.server.root.noop_output; - - self.focused_view = null; - - self.focus_stack.init(); - - self.focused_layer = null; - - self.listen_request_set_selection.notify = handleRequestSetSelection; - c.wl_signal_add(&self.wlr_seat.events.request_set_selection, &self.listen_request_set_selection); -} - -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. -/// If null is passed, the first visible view in the focus stack will be focused. -pub fn focus(self: *Self, _view: ?*View) void { - var view = _view; - - // While a layer surface is focused, views may not recieve focus - if (self.focused_layer != null) { - std.debug.assert(self.focused_view == null); - return; - } - - // If view is null or not currently visible - if (if (view) |v| - v.output != self.focused_output or - v.current_tags & self.focused_output.current_focused_tags == 0 - else - true) { - // Set view to the first currently visible view on in the focus stack if any - var it = ViewStack(*View).iterator( - self.focus_stack.first, - self.focused_output.current_focused_tags, - ); - view = while (it.next()) |node| { - if (node.view.output == self.focused_output) { - break node.view; - } - } else null; - } - - if (view) |view_to_focus| { - // Find or allocate a new node in the focus stack for the target view - var it = self.focus_stack.first; - while (it) |node| : (it = node.next) { - // If the view is found, move it to the top of the stack - if (node.view == view_to_focus) { - const new_focus_node = self.focus_stack.remove(node); - self.focus_stack.push(node); - break; - } - } else { - // The view is not in the stack, so allocate a new node and prepend it - const new_focus_node = self.input_manager.server.allocator.create( - ViewStack(*View).Node, - ) catch unreachable; - new_focus_node.view = view_to_focus; - self.focus_stack.push(new_focus_node); - } - - // Focus the target view - self.setFocusRaw(.{ .view = view_to_focus }); - } else { - // Otherwise clear the focus - self.setFocusRaw(.{ .none = {} }); - } -} - -/// Switch focus to the target, handling unfocus and input inhibition -/// properly. This should only be called directly if dealing with layers. -pub fn setFocusRaw(self: *Self, focus_target: FocusTarget) void { - // If the target is already focused, do nothing - if (switch (focus_target) { - .view => |target_view| target_view == self.focused_view, - .layer => |target_layer| target_layer == self.focused_layer, - .none => false, - }) { - return; - } - - // Obtain the target wlr_surface - const target_wlr_surface = switch (focus_target) { - .view => |target_view| target_view.wlr_surface.?, - .layer => |target_layer| target_layer.wlr_layer_surface.surface.?, - .none => null, - }; - - // If input is not allowed on the target surface (e.g. due to an active - // input inhibitor) do not set focus. If there is no target surface we - // still clear the focus. - if (if (target_wlr_surface) |wlr_surface| - self.input_manager.inputAllowed(wlr_surface) - else - true) { - // First clear the current focus - if (self.focused_view) |current_focus| { - std.debug.assert(self.focused_layer == null); - current_focus.setFocused(false); - self.focused_view = null; - } - if (self.focused_layer) |current_focus| { - std.debug.assert(self.focused_view == null); - self.focused_layer = null; - } - c.wlr_seat_keyboard_clear_focus(self.wlr_seat); - - // Set the new focus - switch (focus_target) { - .view => |target_view| { - std.debug.assert(self.focused_output == target_view.output); - target_view.setFocused(true); - self.focused_view = target_view; - }, - .layer => |target_layer| blk: { - std.debug.assert(self.focused_output == target_layer.output); - self.focused_layer = target_layer; - }, - .none => {}, - } - - // Tell wlroots to send the new keyboard focus if we have a target - if (target_wlr_surface) |wlr_surface| { - const keyboard: *c.wlr_keyboard = c.wlr_seat_get_keyboard(self.wlr_seat); - c.wlr_seat_keyboard_notify_enter( - self.wlr_seat, - wlr_surface, - &keyboard.keycodes, - keyboard.num_keycodes, - &keyboard.modifiers, - ); - } - } -} - -/// Handle the unmapping of a view, removing it from the focus stack and -/// setting the focus if needed. -pub fn handleViewUnmap(self: *Self, view: *View) void { - // Remove the node from the focus stack and destroy it. - var it = self.focus_stack.first; - while (it) |node| : (it = node.next) { - if (node.view == view) { - self.focus_stack.remove(node); - self.input_manager.server.allocator.destroy(node); - break; - } - } - - // If the unmapped view is focused, choose a new focus - if (self.focused_view) |current_focus| { - if (current_focus == view) { - self.focus(null); - } - } -} - -/// Handle any user-defined mapping for the passed keysym and modifiers -/// Returns true if the key was handled -pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32) bool { - const modes = &self.input_manager.server.config.modes; - for (modes.items[self.mode_id].items) |mapping| { - if (modifiers == mapping.modifiers and keysym == mapping.keysym) { - // Execute the bound command - const allocator = self.input_manager.server.allocator; - var failure_message: []const u8 = undefined; - command.run(allocator, self, mapping.command_args, &failure_message) catch |err| { - // TODO: log the error - if (err == command.Error.CommandFailed) - allocator.free(failure_message); - }; - return true; - } - } - return false; -} - -/// Add a newly created input device to the seat and update the reported -/// capabilities. -pub fn addDevice(self: *Self, device: *c.wlr_input_device) !void { - switch (device.type) { - .WLR_INPUT_DEVICE_KEYBOARD => self.addKeyboard(device) catch unreachable, - .WLR_INPUT_DEVICE_POINTER => self.addPointer(device), - else => {}, - } - - // We need to let the wlr_seat know what our capabilities are, which is - // communiciated to the client. We always have a cursor, even if - // there are no pointer devices, so we always include that capability. - var caps = @intCast(u32, c.WL_SEAT_CAPABILITY_POINTER); - // if list not empty - if (self.keyboards.len > 0) { - caps |= @intCast(u32, c.WL_SEAT_CAPABILITY_KEYBOARD); - } - c.wlr_seat_set_capabilities(self.wlr_seat, caps); -} - -fn addKeyboard(self: *Self, device: *c.wlr_input_device) !void { - c.wlr_seat_set_keyboard(self.wlr_seat, device); - - const node = try self.keyboards.allocateNode(self.input_manager.server.allocator); - try node.data.init(self, device); - self.keyboards.append(node); -} - -fn addPointer(self: Self, 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(self.cursor.wlr_cursor, device); -} - -fn handleRequestSetSelection(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_request_set_selection", listener.?); - const event = @ptrCast( - *c.wlr_seat_request_set_selection_event, - @alignCast(@alignOf(*c.wlr_seat_request_set_selection_event), data), - ); - c.wlr_seat_set_selection(self.wlr_seat, event.source, event.serial); -} diff --git a/src/Server.zig b/src/Server.zig deleted file mode 100644 index e46a619..0000000 --- a/src/Server.zig +++ /dev/null @@ -1,279 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const build_options = @import("build_options"); -const std = @import("std"); - -const c = @import("c.zig"); - -const Config = @import("Config.zig"); -const DecorationManager = @import("DecorationManager.zig"); -const InputManager = @import("InputManager.zig"); -const LayerSurface = @import("LayerSurface.zig"); -const Log = @import("log.zig").Log; -const Output = @import("Output.zig"); -const Root = @import("Root.zig"); -const View = @import("View.zig"); -const ViewStack = @import("view_stack.zig").ViewStack; -const Control = @import("Control.zig"); -const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig"); - -allocator: *std.mem.Allocator, - -wl_display: *c.wl_display, -wl_event_loop: *c.wl_event_loop, - -wlr_backend: *c.wlr_backend, -noop_backend: *c.wlr_backend, -listen_new_output: c.wl_listener, - -wlr_xdg_shell: *c.wlr_xdg_shell, -listen_new_xdg_surface: c.wl_listener, - -wlr_layer_shell: *c.wlr_layer_shell_v1, -listen_new_layer_surface: c.wl_listener, - -wlr_xwayland: if (build_options.xwayland) *c.wlr_xwayland else void, -listen_new_xwayland_surface: if (build_options.xwayland) c.wl_listener else void, - -decoration_manager: DecorationManager, -input_manager: InputManager, -root: Root, -config: Config, -control: Control, - -pub fn init(self: *Self, allocator: *std.mem.Allocator) !void { - self.allocator = allocator; - - // The Wayland display is managed by libwayland. It handles accepting - // clients from the Unix socket, managing Wayland globals, and so on. - self.wl_display = c.wl_display_create() orelse - return error.CantCreateWlDisplay; - errdefer c.wl_display_destroy(self.wl_display); - - // Should never return null if the display was created successfully - self.wl_event_loop = c.wl_display_get_event_loop(self.wl_display) orelse - return error.CantGetEventLoop; - - // The wlr_backend abstracts the input/output hardware. Autocreate chooses - // the best option based on the environment, for example DRM when run from - // a tty or wayland if WAYLAND_DISPLAY is set. This frees itself when the - // wl_display is destroyed. - self.wlr_backend = c.river_wlr_backend_autocreate(self.wl_display) orelse - return error.CantCreateWlrBackend; - - // This backend is used to create a noop output for use when no actual - // outputs are available. This frees itself when the wl_display is destroyed. - self.noop_backend = c.river_wlr_noop_backend_create(self.wl_display) orelse - return error.CantCreateNoopBackend; - - // 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. - const wlr_renderer = c.river_wlr_backend_get_renderer(self.wlr_backend) orelse - return error.CantGetWlrRenderer; - // TODO: Handle failure after https://github.com/swaywm/wlroots/pull/2080 - c.wlr_renderer_init_wl_display(wlr_renderer, self.wl_display); // orelse - // return error.CantInitWlDisplay; - self.listen_new_output.notify = handleNewOutput; - c.wl_signal_add(&self.wlr_backend.events.new_output, &self.listen_new_output); - - const wlr_compositor = c.wlr_compositor_create(self.wl_display, wlr_renderer) orelse - return error.CantCreateWlrCompositor; - - // Set up xdg shell - self.wlr_xdg_shell = c.wlr_xdg_shell_create(self.wl_display) orelse - return error.CantCreateWlrXdgShell; - self.listen_new_xdg_surface.notify = handleNewXdgSurface; - c.wl_signal_add(&self.wlr_xdg_shell.events.new_surface, &self.listen_new_xdg_surface); - - // Set up layer shell - self.wlr_layer_shell = c.wlr_layer_shell_v1_create(self.wl_display) orelse - return error.CantCreateWlrLayerShell; - self.listen_new_layer_surface.notify = handleNewLayerSurface; - c.wl_signal_add(&self.wlr_layer_shell.events.new_surface, &self.listen_new_layer_surface); - - // Set up xwayland if built with support - if (build_options.xwayland) { - self.wlr_xwayland = c.wlr_xwayland_create(self.wl_display, wlr_compositor, false) orelse - return error.CantCreateWlrXwayland; - self.listen_new_xwayland_surface.notify = handleNewXwaylandSurface; - c.wl_signal_add(&self.wlr_xwayland.events.new_surface, &self.listen_new_xwayland_surface); - } - - try self.config.init(self.allocator); - try self.decoration_manager.init(self); - try self.root.init(self); - // Must be called after root is initialized - try self.input_manager.init(self); - try self.control.init(self); - - // These all free themselves when the wl_display is destroyed - _ = c.wlr_data_device_manager_create(self.wl_display) orelse - return error.CantCreateWlrDataDeviceManager; - _ = c.wlr_screencopy_manager_v1_create(self.wl_display) orelse - return error.CantCreateWlrScreencopyManager; - _ = c.wlr_xdg_output_manager_v1_create(self.wl_display, self.root.wlr_output_layout) orelse - return error.CantCreateWlrOutputManager; -} - -/// Free allocated memory and clean up -pub fn deinit(self: *Self) void { - // Note: order is important here - if (build_options.xwayland) { - c.wlr_xwayland_destroy(self.wlr_xwayland); - } - c.wl_display_destroy_clients(self.wl_display); - c.wl_display_destroy(self.wl_display); - self.input_manager.deinit(); - self.root.deinit(); - self.config.deinit(self.allocator); -} - -/// Create the socket, set WAYLAND_DISPLAY, and start the backend -pub fn start(self: Self) !void { - // Add a Unix socket to the Wayland display. - const socket = c.wl_display_add_socket_auto(self.wl_display) orelse - return error.CantAddSocket; - - // Start the backend. This will enumerate outputs and inputs, become the DRM - // master, etc - if (!c.river_wlr_backend_start(self.wlr_backend)) { - return error.CantStartBackend; - } - - // Set the WAYLAND_DISPLAY environment variable to our socket - if (c.setenv("WAYLAND_DISPLAY", socket, 1) == -1) { - return error.CantSetEnv; - } - - if (build_options.xwayland) { - if (c.setenv("DISPLAY", &self.wlr_xwayland.display_name, 1) == -1) { - return error.CantSetEnv; - } - } -} - -/// Enter the wayland event loop and block until the compositor is exited -pub fn run(self: Self) void { - c.wl_display_run(self.wl_display); -} - -fn handleNewOutput(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_output", listener.?); - const wlr_output = @ptrCast(*c.wlr_output, @alignCast(@alignOf(*c.wlr_output), data)); - Log.Debug.log("New output {}", .{wlr_output.name}); - self.root.addOutput(wlr_output); -} - -fn handleNewXdgSurface(listener: ?*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. - const self = @fieldParentPtr(Self, "listen_new_xdg_surface", listener.?); - const wlr_xdg_surface = @ptrCast(*c.wlr_xdg_surface, @alignCast(@alignOf(*c.wlr_xdg_surface), data)); - - if (wlr_xdg_surface.role == .WLR_XDG_SURFACE_ROLE_POPUP) { - Log.Debug.log("New xdg_popup", .{}); - return; - } - - Log.Debug.log("New xdg_toplevel", .{}); - - // The View will add itself to the output's view stack on map - const output = self.input_manager.default_seat.focused_output; - const node = self.allocator.create(ViewStack(View).Node) catch unreachable; - node.view.init(output, output.current_focused_tags, wlr_xdg_surface); -} - -/// This event is raised when the layer_shell recieves a new surface from a client. -fn handleNewLayerSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_layer_surface", listener.?); - const wlr_layer_surface = @ptrCast( - *c.wlr_layer_surface_v1, - @alignCast(@alignOf(*c.wlr_layer_surface_v1), data), - ); - - Log.Debug.log( - "New layer surface: namespace {}, layer {}, anchor {}, size {}x{}, margin ({},{},{},{}), exclusive_zone {}", - .{ - wlr_layer_surface.namespace, - wlr_layer_surface.client_pending.layer, - wlr_layer_surface.client_pending.anchor, - wlr_layer_surface.client_pending.desired_width, - wlr_layer_surface.client_pending.desired_height, - wlr_layer_surface.client_pending.margin.top, - wlr_layer_surface.client_pending.margin.right, - wlr_layer_surface.client_pending.margin.bottom, - wlr_layer_surface.client_pending.margin.left, - wlr_layer_surface.client_pending.exclusive_zone, - }, - ); - - // If the new layer surface does not have an output assigned to it, use the - // first output or close the surface if none are available. - if (wlr_layer_surface.output == null) { - if (self.root.outputs.first) |node| { - const output = &node.data; - Log.Debug.log( - "New layer surface had null output, assigning it to output {}", - .{output.wlr_output.name}, - ); - wlr_layer_surface.output = output.wlr_output; - } else { - Log.Error.log( - "No output available for layer surface '{}'", - .{wlr_layer_surface.namespace}, - ); - c.wlr_layer_surface_v1_close(wlr_layer_surface); - return; - } - } - - // The layer surface will add itself to the proper list of the output on map - const output = @ptrCast(*Output, @alignCast(@alignOf(*Output), wlr_layer_surface.output.*.data)); - const node = self.allocator.create(std.TailQueue(LayerSurface).Node) catch unreachable; - node.data.init(output, wlr_layer_surface); -} - -fn handleNewXwaylandSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_xwayland_surface", listener.?); - const wlr_xwayland_surface = @ptrCast( - *c.wlr_xwayland_surface, - @alignCast(@alignOf(*c.wlr_xwayland_surface), data), - ); - - if (wlr_xwayland_surface.override_redirect) { - Log.Debug.log("New unmanaged xwayland surface", .{}); - // The unmanged surface will add itself to the list of unmanaged views - // in Root when it is mapped. - const node = self.allocator.create(std.TailQueue(XwaylandUnmanaged).Node) catch unreachable; - node.data.init(&self.root, wlr_xwayland_surface); - return; - } - - Log.Debug.log( - "New xwayland surface: title '{}', class '{}'", - .{ wlr_xwayland_surface.title, wlr_xwayland_surface.class }, - ); - - // The View will add itself to the output's view stack on map - const output = self.input_manager.default_seat.focused_output; - const node = self.allocator.create(ViewStack(View).Node) catch unreachable; - node.view.init(output, output.current_focused_tags, wlr_xwayland_surface); -} diff --git a/src/View.zig b/src/View.zig deleted file mode 100644 index 8ab32cc..0000000 --- a/src/View.zig +++ /dev/null @@ -1,271 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const build_options = @import("build_options"); -const std = @import("std"); - -const c = @import("c.zig"); - -const Box = @import("Box.zig"); -const Log = @import("log.zig").Log; -const Output = @import("Output.zig"); -const Root = @import("Root.zig"); -const ViewStack = @import("view_stack.zig").ViewStack; -const XdgToplevel = @import("XdgToplevel.zig"); -const XwaylandView = if (build_options.xwayland) - @import("XwaylandView.zig") -else - @import("VoidView.zig"); - -const Impl = union(enum) { - xdg_toplevel: XdgToplevel, - xwayland_view: XwaylandView, -}; - -/// The implementation of this view -impl: Impl, - -/// The output this view is currently associated with -output: *Output, - -/// This is non-null exactly when the view is mapped -wlr_surface: ?*c.wlr_surface, - -/// If the view is floating or not -floating: bool, - -/// True if the view is currentlt focused by at lease one seat -focused: bool, - -/// The current output-relative coordinates and dimensions of the view -current_box: Box, -pending_box: ?Box, - -/// The dimensions the view would have taken if we didn't force it to tile -natural_width: u32, -natural_height: u32, - -current_tags: u32, -pending_tags: ?u32, - -pending_serial: ?u32, - -// This is what we render while a transaction is in progress -stashed_buffer: ?*c.wlr_buffer, - -pub fn init( - self: *Self, - output: *Output, - tags: u32, - surface: var, -) void { - self.output = output; - - self.wlr_surface = null; - - self.focused = false; - - self.current_box = Box{ - .x = 0, - .y = 0, - .height = 0, - .width = 0, - }; - self.pending_box = null; - - self.current_tags = tags; - self.pending_tags = null; - - self.pending_serial = null; - - self.stashed_buffer = null; - - if (@TypeOf(surface) == *c.wlr_xdg_surface) { - self.impl = .{ .xdg_toplevel = undefined }; - self.impl.xdg_toplevel.init(self, surface); - } else if (build_options.xwayland and @TypeOf(surface) == *c.wlr_xwayland_surface) { - self.impl = .{ .xwayland_view = undefined }; - self.impl.xwayland_view.init(self, surface); - } else unreachable; -} - -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 - pending_box.height != self.current_box.height; - } else { - return false; - } -} - -pub fn configure(self: Self) void { - if (self.pending_box) |pending_box| { - switch (self.impl) { - .xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(pending_box), - .xwayland_view => |xwayland_view| xwayland_view.configure(pending_box), - } - } else { - Log.Error.log("Configure called on a View with no pending box", .{}); - } -} - -pub fn sendFrameDone(self: Self) void { - var now: c.timespec = undefined; - _ = c.clock_gettime(c.CLOCK_MONOTONIC, &now); - c.wlr_surface_send_frame_done(self.wlr_surface.?, &now); -} - -pub fn dropStashedBuffer(self: *Self) void { - // TODO: log debug error - if (self.stashed_buffer) |buffer| { - c.wlr_buffer_unref(buffer); - self.stashed_buffer = null; - } -} - -pub fn stashBuffer(self: *Self) void { - // TODO: log debug error if there is already a saved buffer - if (self.wlr_surface) |wlr_surface| { - if (c.wlr_surface_has_buffer(wlr_surface)) { - _ = c.wlr_buffer_ref(wlr_surface.buffer); - self.stashed_buffer = wlr_surface.buffer; - } - } -} - -/// Set the focued bool and the active state of the view if it is a toplevel -pub fn setFocused(self: *Self, focused: bool) void { - self.focused = focused; - switch (self.impl) { - .xdg_toplevel => |xdg_toplevel| xdg_toplevel.setActivated(focused), - .xwayland_view => |xwayland_view| xwayland_view.setActivated(focused), - } -} - -/// If true is passsed, make the view float. If false, return it to the tiled -/// layout. -pub fn setFloating(self: *Self, float: bool) void { - if (float and !self.floating) { - self.floating = true; - self.pending_box = Box{ - .x = std.math.max(0, @divTrunc(@intCast(i32, self.output.usable_box.width) - - @intCast(i32, self.natural_width), 2)), - .y = std.math.max(0, @divTrunc(@intCast(i32, self.output.usable_box.height) - - @intCast(i32, self.natural_height), 2)), - .width = self.natural_width, - .height = self.natural_height, - }; - } else if (!float and self.floating) { - self.floating = false; - } -} - -/// Move a view from one output to another, sending the required enter/leave -/// events. -pub fn sendToOutput(self: *Self, destination_output: *Output) void { - const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); - - self.output.views.remove(node); - destination_output.views.push(node); - - c.wlr_surface_send_leave(self.wlr_surface, self.output.wlr_output); - c.wlr_surface_send_enter(self.wlr_surface, destination_output.wlr_output); - - self.output = destination_output; -} - -pub fn close(self: Self) void { - switch (self.impl) { - .xdg_toplevel => |xdg_toplevel| xdg_toplevel.close(), - .xwayland_view => |xwayland_view| xwayland_view.close(), - } -} - -pub fn forEachSurface( - self: Self, - iterator: c.wlr_surface_iterator_func_t, - user_data: ?*c_void, -) void { - switch (self.impl) { - .xdg_toplevel => |xdg_toplevel| xdg_toplevel.forEachSurface(iterator, user_data), - .xwayland_view => |xwayland_view| xwayland_view.forEachSurface(iterator, user_data), - } -} - -/// Return the surface at output coordinates ox, oy and set sx, sy to the -/// corresponding surface-relative coordinates, if there is a surface. -pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { - return switch (self.impl) { - .xdg_toplevel => |xdg_toplevel| xdg_toplevel.surfaceAt(ox, oy, sx, sy), - .xwayland_view => |xwayland_view| xwayland_view.surfaceAt(ox, oy, sx, sy), - }; -} - -/// Called by the impl when the surface is ready to be displayed -pub fn map(self: *Self) void { - const root = self.output.root; - - // Add the view to the stack of its output - const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); - self.output.views.push(node); - - // Focus the newly mapped view. Note: if a seat is focusing a different output - // it will continue to do so. - var it = root.server.input_manager.seats.first; - while (it) |seat_node| : (it = seat_node.next) { - seat_node.data.focus(self); - } - - c.wlr_surface_send_enter(self.wlr_surface.?, self.output.wlr_output); - - root.arrange(); -} - -/// Called by the impl when the surface will no longer be displayed -pub fn unmap(self: *Self) void { - const root = self.output.root; - - self.wlr_surface = null; - - // Inform all seats that the view has been unmapped so they can handle focus - var it = root.server.input_manager.seats.first; - while (it) |node| : (it = node.next) { - const seat = &node.data; - seat.handleViewUnmap(self); - } - - // Remove the view from its output's stack - const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); - self.output.views.remove(node); - - root.arrange(); -} - -/// Destory the view and free the ViewStack node holding it. -pub fn destroy(self: *const Self) void { - self.deinit(); - const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); - self.output.root.server.allocator.destroy(node); -} diff --git a/src/VoidView.zig b/src/VoidView.zig deleted file mode 100644 index 72c57a7..0000000 --- a/src/VoidView.zig +++ /dev/null @@ -1,48 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const Box = @import("Box.zig"); - -pub fn configure(self: Self, pending_box: Box) void { - unreachable; -} - -pub fn setActivated(self: Self, activated: bool) void { - unreachable; -} - -pub fn close(self: Self) void { - unreachable; -} - -pub fn forEachSurface( - self: Self, - iterator: c.wlr_surface_iterator_func_t, - user_data: ?*c_void, -) void { - unreachable; -} - -pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { - unreachable; -} diff --git a/src/XdgPopup.zig b/src/XdgPopup.zig deleted file mode 100644 index b4ee56c..0000000 --- a/src/XdgPopup.zig +++ /dev/null @@ -1,82 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const Box = @import("Box.zig"); -const Output = @import("Output.zig"); - -/// The output this popup is displayed on. -output: *Output, - -/// Box of the parent of this popup tree. Needed to unconstrain child popups. -parent_box: *const Box, - -/// The corresponding wlroots object -wlr_xdg_popup: *c.wlr_xdg_popup, - -listen_destroy: c.wl_listener, -listen_new_popup: c.wl_listener, - -pub fn init( - self: *Self, - output: *Output, - parent_box: *const Box, - wlr_xdg_popup: *c.wlr_xdg_popup, -) void { - self.output = output; - self.parent_box = parent_box; - self.wlr_xdg_popup = wlr_xdg_popup; - - // The output box relative to the parent of the popup - var box = c.wlr_output_layout_get_box(output.root.wlr_output_layout, output.wlr_output).*; - box.x -= parent_box.x; - box.y -= parent_box.y; - c.wlr_xdg_popup_unconstrain_from_box(wlr_xdg_popup, &box); - - // Setup listeners - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&wlr_xdg_popup.base.*.events.destroy, &self.listen_destroy); - - self.listen_new_popup.notify = handleNewPopup; - c.wl_signal_add(&wlr_xdg_popup.base.*.events.new_popup, &self.listen_new_popup); -} - -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); - const allocator = self.output.root.server.allocator; - - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_new_popup.link); - - allocator.destroy(self); -} - -/// Called when a new xdg popup is requested by the client -fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_popup", listener.?); - const wlr_xdg_popup = @ptrCast(*c.wlr_xdg_popup, @alignCast(@alignOf(*c.wlr_xdg_popup), data)); - const allocator = self.output.root.server.allocator; - - // This will free itself on destroy - var xdg_popup = allocator.create(Self) catch unreachable; - xdg_popup.init(self.output, self.parent_box, wlr_xdg_popup); -} diff --git a/src/XdgToplevel.zig b/src/XdgToplevel.zig deleted file mode 100644 index b3806db..0000000 --- a/src/XdgToplevel.zig +++ /dev/null @@ -1,201 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const Box = @import("Box.zig"); -const Log = @import("log.zig").Log; -const View = @import("View.zig"); -const ViewStack = @import("view_stack.zig").ViewStack; -const XdgPopup = @import("XdgPopup.zig"); - -/// The view this xdg toplevel implements -view: *View, - -/// The corresponding wlroots object -wlr_xdg_surface: *c.wlr_xdg_surface, - -// Listeners that are always active over the view's lifetime -listen_destroy: c.wl_listener, -listen_map: c.wl_listener, -listen_unmap: c.wl_listener, - -// Listeners that are only active while the view is mapped -listen_commit: c.wl_listener, -listen_new_popup: c.wl_listener, - -pub fn init(self: *Self, view: *View, wlr_xdg_surface: *c.wlr_xdg_surface) void { - self.view = view; - self.wlr_xdg_surface = wlr_xdg_surface; - wlr_xdg_surface.data = self; - - // Inform the xdg toplevel that it is tiled. - // For example this prevents firefox from drawing shadows around itself - _ = c.wlr_xdg_toplevel_set_tiled(self.wlr_xdg_surface, c.WLR_EDGE_LEFT | - c.WLR_EDGE_RIGHT | c.WLR_EDGE_TOP | c.WLR_EDGE_BOTTOM); - - // Add listeners that are active over the view's entire lifetime - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&self.wlr_xdg_surface.events.destroy, &self.listen_destroy); - - self.listen_map.notify = handleMap; - c.wl_signal_add(&self.wlr_xdg_surface.events.map, &self.listen_map); - - self.listen_unmap.notify = handleUnmap; - c.wl_signal_add(&self.wlr_xdg_surface.events.unmap, &self.listen_unmap); -} - -pub fn configure(self: Self, pending_box: Box) void { - self.view.pending_serial = c.wlr_xdg_toplevel_set_size( - self.wlr_xdg_surface, - pending_box.width, - pending_box.height, - ); -} - -pub fn setActivated(self: Self, activated: bool) void { - _ = c.wlr_xdg_toplevel_set_activated(self.wlr_xdg_surface, activated); -} - -/// Close the view. This will lead to the unmap and destroy events being sent -pub fn close(self: Self) void { - c.wlr_xdg_toplevel_send_close(self.wlr_xdg_surface); -} - -pub fn forEachSurface( - self: Self, - iterator: c.wlr_surface_iterator_func_t, - user_data: ?*c_void, -) void { - c.wlr_xdg_surface_for_each_surface(self.wlr_xdg_surface, iterator, user_data); -} - -/// Return the surface at output coordinates ox, oy and set sx, sy to the -/// corresponding surface-relative coordinates, if there is a surface. -pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { - return c.wlr_xdg_surface_surface_at( - self.wlr_xdg_surface, - ox - @intToFloat(f64, self.view.current_box.x), - oy - @intToFloat(f64, self.view.current_box.y), - sx, - sy, - ); -} - -/// Called when the xdg surface is destroyed -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); - const output = self.view.output; - - // Remove listeners that are active for the entire lifetime of the view - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_map.link); - c.wl_list_remove(&self.listen_unmap.link); - - self.view.destroy(); -} - -/// Called when the xdg surface is mapped, or ready to display on-screen. -fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_map", listener.?); - const view = self.view; - const root = view.output.root; - - // Add listeners that are only active while mapped - self.listen_commit.notify = handleCommit; - c.wl_signal_add(&self.wlr_xdg_surface.surface.*.events.commit, &self.listen_commit); - - self.listen_new_popup.notify = handleNewPopup; - c.wl_signal_add(&self.wlr_xdg_surface.events.new_popup, &self.listen_new_popup); - - view.wlr_surface = self.wlr_xdg_surface.surface; - view.floating = false; - - view.natural_width = @intCast(u32, self.wlr_xdg_surface.geometry.width); - view.natural_height = @intCast(u32, self.wlr_xdg_surface.geometry.height); - - if (view.natural_width == 0 and view.natural_height == 0) { - view.natural_width = @intCast(u32, self.wlr_xdg_surface.surface.*.current.width); - view.natural_height = @intCast(u32, self.wlr_xdg_surface.surface.*.current.height); - } - - const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field( - self.wlr_xdg_surface, - c.wlr_xdg_surface_union, - ).toplevel; - const state = &wlr_xdg_toplevel.current; - const app_id: [*:0]const u8 = if (wlr_xdg_toplevel.app_id) |id| id else "NULL"; - - Log.Debug.log("View with app_id '{}' mapped", .{app_id}); - - for (root.server.config.float_filter.items) |filter_app_id| { - // Make views with app_ids listed in the float filter float - if (std.mem.eql(u8, std.mem.span(app_id), std.mem.span(filter_app_id))) { - view.setFloating(true); - break; - } - } else if ((wlr_xdg_toplevel.parent != null) or - (state.min_width != 0 and state.min_height != 0 and - (state.min_width == state.max_width or state.min_height == state.max_height))) - { - // If the toplevel has a parent or is of fixed size make it float - view.setFloating(true); - } - - view.map(); -} - -/// Called when the surface is unmapped and will no longer be displayed. -fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_unmap", listener.?); - const root = self.view.output.root; - - self.view.unmap(); - - // Remove listeners that are only active while mapped - c.wl_list_remove(&self.listen_commit.link); - c.wl_list_remove(&self.listen_new_popup.link); -} - -/// Called when the surface is comitted -/// TODO: check for unexpected change in size and react as needed -fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_commit", listener.?); - const view = self.view; - - if (view.pending_serial) |s| { - if (s == self.wlr_xdg_surface.configure_serial) { - view.output.root.notifyConfigured(); - view.pending_serial = null; - } - } -} - -/// Called when a new xdg popup is requested by the client -fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_popup", listener.?); - const wlr_xdg_popup = @ptrCast(*c.wlr_xdg_popup, @alignCast(@alignOf(*c.wlr_xdg_popup), data)); - const allocator = self.view.output.root.server.allocator; - - // This will free itself on destroy - var xdg_popup = allocator.create(XdgPopup) catch unreachable; - xdg_popup.init(self.view.output, &self.view.current_box, wlr_xdg_popup); -} diff --git a/src/XwaylandUnmanaged.zig b/src/XwaylandUnmanaged.zig deleted file mode 100644 index fe33e73..0000000 --- a/src/XwaylandUnmanaged.zig +++ /dev/null @@ -1,136 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const Box = @import("Box.zig"); -const Log = @import("log.zig").Log; -const Root = @import("Root.zig"); - -root: *Root, - -/// The corresponding wlroots object -wlr_xwayland_surface: *c.wlr_xwayland_surface, - -// Listeners that are always active over the view's lifetime -liseten_request_configure: c.wl_listener, -listen_destroy: c.wl_listener, -listen_map: c.wl_listener, -listen_unmap: c.wl_listener, - -// Listeners that are only active while the view is mapped -listen_commit: c.wl_listener, - -pub fn init(self: *Self, root: *Root, wlr_xwayland_surface: *c.wlr_xwayland_surface) void { - self.root = root; - self.wlr_xwayland_surface = wlr_xwayland_surface; - - // Add listeners that are active over the view's entire lifetime - self.liseten_request_configure.notify = handleRequestConfigure; - c.wl_signal_add(&wlr_xwayland_surface.events.request_configure, &self.liseten_request_configure); - - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&wlr_xwayland_surface.events.destroy, &self.listen_destroy); - - self.listen_map.notify = handleMap; - c.wl_signal_add(&wlr_xwayland_surface.events.map, &self.listen_map); - - self.listen_unmap.notify = handleUnmap; - c.wl_signal_add(&wlr_xwayland_surface.events.unmap, &self.listen_unmap); -} - -/// Return the surface at output coordinates ox, oy and set sx, sy to the -/// corresponding surface-relative coordinates, if there is a surface. -pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { - return c.wlr_surface_surface_at( - self.wlr_xwayland_surface.surface, - ox - @intToFloat(f64, self.view.current_box.x), - oy - @intToFloat(f64, self.view.current_box.y), - sx, - sy, - ); -} - -fn handleRequestConfigure(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "liseten_request_configure", listener.?); - const wlr_xwayland_surface_configure_event = @ptrCast( - *c.wlr_xwayland_surface_configure_event, - @alignCast(@alignOf(*c.wlr_xwayland_surface_configure_event), data), - ); - c.wlr_xwayland_surface_configure( - self.wlr_xwayland_surface, - wlr_xwayland_surface_configure_event.x, - wlr_xwayland_surface_configure_event.y, - wlr_xwayland_surface_configure_event.width, - wlr_xwayland_surface_configure_event.height, - ); -} - -/// Called when the xwayland surface is destroyed -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); - - // Remove listeners that are active for the entire lifetime of the view - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_map.link); - c.wl_list_remove(&self.listen_unmap.link); - - // Deallocate the node - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - self.root.server.allocator.destroy(node); -} - -/// Called when the xwayland surface is mapped, or ready to display on-screen. -fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_map", listener.?); - const root = self.root; - - // Add self to the list of unmanaged views in the root - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - root.xwayland_unmanaged_views.prepend(node); - - // Add listeners that are only active while mapped - self.listen_commit.notify = handleCommit; - c.wl_signal_add(&self.wlr_xwayland_surface.surface.*.events.commit, &self.listen_commit); - - // TODO: handle keyboard focus - // if (wlr_xwayland_or_surface_wants_focus(self.wlr_xwayland_surface)) { ... -} - -/// Called when the surface is unmapped and will no longer be displayed. -fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_unmap", listener.?); - - // Remove self from the list of unmanged views in the root - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - self.root.xwayland_unmanaged_views.remove(node); - - // Remove listeners that are only active while mapped - c.wl_list_remove(&self.listen_commit.link); - - // TODO: return focus -} - -/// Called when the surface is comitted -fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_commit", listener.?); - // TODO: check if the surface has moved for damage tracking -} diff --git a/src/XwaylandView.zig b/src/XwaylandView.zig deleted file mode 100644 index dbd35e4..0000000 --- a/src/XwaylandView.zig +++ /dev/null @@ -1,159 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const Self = @This(); - -const std = @import("std"); - -const c = @import("c.zig"); - -const Box = @import("Box.zig"); -const Log = @import("log.zig").Log; -const View = @import("View.zig"); -const ViewStack = @import("view_stack.zig").ViewStack; -const XdgPopup = @import("XdgPopup.zig"); - -/// The view this xwayland view implements -view: *View, - -/// The corresponding wlroots object -wlr_xwayland_surface: *c.wlr_xwayland_surface, - -// Listeners that are always active over the view's lifetime -listen_destroy: c.wl_listener, -listen_map: c.wl_listener, -listen_unmap: c.wl_listener, - -// Listeners that are only active while the view is mapped -listen_commit: c.wl_listener, - -pub fn init(self: *Self, view: *View, wlr_xwayland_surface: *c.wlr_xwayland_surface) void { - self.view = view; - self.wlr_xwayland_surface = wlr_xwayland_surface; - wlr_xwayland_surface.data = self; - - // Add listeners that are active over the view's entire lifetime - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&self.wlr_xwayland_surface.events.destroy, &self.listen_destroy); - - self.listen_map.notify = handleMap; - c.wl_signal_add(&self.wlr_xwayland_surface.events.map, &self.listen_map); - - self.listen_unmap.notify = handleUnmap; - c.wl_signal_add(&self.wlr_xwayland_surface.events.unmap, &self.listen_unmap); -} - -/// Tell the client to take a new size -pub fn configure(self: Self, pending_box: Box) void { - c.wlr_xwayland_surface_configure( - self.wlr_xwayland_surface, - @intCast(i16, pending_box.x), - @intCast(i16, pending_box.y), - @intCast(u16, pending_box.width), - @intCast(u16, pending_box.height), - ); - // Xwayland surfaces don't use serials, so we will just assume they have - // configured the next time they commit. Set pending serial to a dummy - // value to indicate that a transaction has started. Note: we can't just - // call notifyConfigured() here as the transaction has not yet been fully - // initiated. - self.view.pending_serial = 0x66666666; -} - -/// Inform the xwayland surface that it has gained focus -pub fn setActivated(self: Self, activated: bool) void { - c.wlr_xwayland_surface_activate(self.wlr_xwayland_surface, activated); -} - -/// Close the view. This will lead to the unmap and destroy events being sent -pub fn close(self: Self) void { - c.wlr_xwayland_surface_close(self.wlr_xwayland_surface); -} - -/// Iterate over all surfaces of the xwayland view. -pub fn forEachSurface( - self: Self, - iterator: c.wlr_surface_iterator_func_t, - user_data: ?*c_void, -) void { - c.wlr_surface_for_each_surface(self.wlr_xwayland_surface.surface, iterator, user_data); -} - -/// Return the surface at output coordinates ox, oy and set sx, sy to the -/// corresponding surface-relative coordinates, if there is a surface. -pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { - return c.wlr_surface_surface_at( - self.wlr_xwayland_surface.surface, - ox - @intToFloat(f64, self.view.current_box.x), - oy - @intToFloat(f64, self.view.current_box.y), - sx, - sy, - ); -} - -/// Called when the xwayland surface is destroyed -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); - - // Remove listeners that are active for the entire lifetime of the view - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_map.link); - c.wl_list_remove(&self.listen_unmap.link); - - self.view.destroy(); -} - -/// Called when the xwayland surface is mapped, or ready to display on-screen. -fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_map", listener.?); - const view = self.view; - const root = view.output.root; - - // Add listeners that are only active while mapped - self.listen_commit.notify = handleCommit; - c.wl_signal_add(&self.wlr_xwayland_surface.surface.*.events.commit, &self.listen_commit); - - view.wlr_surface = self.wlr_xwayland_surface.surface; - view.floating = false; - - view.natural_width = self.wlr_xwayland_surface.width; - view.natural_height = self.wlr_xwayland_surface.height; - - view.map(); -} - -/// Called when the surface is unmapped and will no longer be displayed. -fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_unmap", listener.?); - - self.view.unmap(); - - // Remove listeners that are only active while mapped - c.wl_list_remove(&self.listen_commit.link); -} - -/// Called when the surface is comitted -/// TODO: check for unexpected change in size and react as needed -fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_commit", listener.?); - const view = self.view; - // See comment in XwaylandView.configure() - if (view.pending_serial != null) { - view.output.root.notifyConfigured(); - view.pending_serial = null; - } -} diff --git a/src/c.zig b/src/c.zig deleted file mode 100644 index df4bcee..0000000 --- a/src/c.zig +++ /dev/null @@ -1,60 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -pub usingnamespace @cImport({ - @cDefine("_POSIX_C_SOURCE", "200809L"); - @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_buffer.h"); - @cInclude("wlr/types/wlr_compositor.h"); - @cInclude("wlr/types/wlr_cursor.h"); - @cInclude("wlr/types/wlr_data_device.h"); - @cInclude("wlr/types/wlr_input_device.h"); - @cInclude("wlr/types/wlr_input_inhibitor.h"); - @cInclude("wlr/types/wlr_keyboard.h"); - @cInclude("wlr/types/wlr_layer_shell_v1.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_screencopy_v1.h"); - @cInclude("wlr/types/wlr_seat.h"); - @cInclude("wlr/types/wlr_xcursor_manager.h"); - @cInclude("wlr/types/wlr_xdg_decoration_v1.h"); - @cInclude("wlr/types/wlr_xdg_output_v1.h"); - @cInclude("wlr/types/wlr_xdg_shell.h"); - if (@import("build_options").xwayland) @cInclude("wlr/xwayland.h"); - @cInclude("wlr/util/log.h"); - @cInclude("xkbcommon/xkbcommon.h"); - - // Contains a subset of functions from wlr/backend.h and wlr/render/wlr_renderer.h - // that can be automatically imported - @cInclude("include/bindings.h"); - - @cInclude("river-control-unstable-v1-protocol.h"); -}); - -// These are needed because zig currently names translated anonymous unions -// with a global counter, which makes code unportable. -// See https://github.com/ifreund/river/issues/17 -pub const wlr_xdg_surface_union = @typeInfo(wlr_xdg_surface).Struct.fields[5].name; -pub const wlr_input_device_union = @typeInfo(wlr_input_device).Struct.fields[8].name; diff --git a/src/command.zig b/src/command.zig deleted file mode 100644 index c1db559..0000000 --- a/src/command.zig +++ /dev/null @@ -1,122 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const Seat = @import("Seat.zig"); - -const impl = struct { - const close = @import("command/close.zig").close; - const declareMode = @import("command/declare_mode.zig").declareMode; - const enterMode = @import("command/enter_mode.zig").enterMode; - const exit = @import("command/exit.zig").exit; - const focus = @import("command/focus.zig").focus; - const focusAllTags = @import("command/focus_all_tags.zig").focusAllTags; - const focusOutput = @import("command/focus_output.zig").focusOutput; - const map = @import("command/map.zig").map; - const focusTag = @import("command/focus_tag.zig").focusTag; - const layout = @import("command/layout.zig").layout; - const modMasterCount = @import("command/mod_master_count.zig").modMasterCount; - const modMasterFactor = @import("command/mod_master_factor.zig").modMasterFactor; - const sendToOutput = @import("command/send_to_output.zig").sendToOutput; - const spawn = @import("command/spawn.zig").spawn; - const tagView = @import("command/tag_view.zig").tagView; - const tagViewAllTags = @import("command/tag_view_all_tags.zig").tagViewAllTags; - const toggleFloat = @import("command/toggle_float.zig").toggleFloat; - const toggleTagFocus = @import("command/toggle_tag_focus.zig").toggleTagFocus; - const toggleViewTag = @import("command/toggle_view_tag.zig").toggleViewTag; - const zoom = @import("command/zoom.zig").zoom; -}; - -pub const Direction = enum { - Next, - Prev, - - pub fn parse(str: []const u8) error{InvalidDirection}!Direction { - return if (std.mem.eql(u8, str, "next")) - Direction.Next - else if (std.mem.eql(u8, str, "previous")) - Direction.Prev - else - error.InvalidDirection; - } -}; - -const Definition = struct { - name: []const u8, - impl: fn (*std.mem.Allocator, *Seat, []const []const u8, *[]const u8) Error!void, -}; - -// TODO: this could be replaced with a comptime hashmap -// zig fmt: off -const str_to_impl_fn = [_]Definition{ - .{ .name = "close", .impl = impl.close }, - .{ .name = "declare_mode", .impl = impl.declareMode}, - .{ .name = "enter_mode", .impl = impl.enterMode }, - .{ .name = "exit", .impl = impl.exit }, - .{ .name = "focus", .impl = impl.focus }, - .{ .name = "focus_all_tags", .impl = impl.focusAllTags }, - .{ .name = "focus_output", .impl = impl.focusOutput }, - .{ .name = "focus_tag", .impl = impl.focusTag }, - .{ .name = "layout", .impl = impl.layout }, - .{ .name = "mod_master_count", .impl = impl.modMasterCount }, - .{ .name = "mod_master_factor", .impl = impl.modMasterFactor }, - .{ .name = "send_to_output", .impl = impl.sendToOutput }, - .{ .name = "spawn", .impl = impl.spawn }, - .{ .name = "map", .impl = impl.map }, - .{ .name = "tag_view", .impl = impl.tagView }, - .{ .name = "tag_view_all_tags", .impl = impl.tagViewAllTags }, - .{ .name = "toggle_float", .impl = impl.toggleFloat }, - .{ .name = "toggle_tag_focus", .impl = impl.toggleTagFocus }, - .{ .name = "toggle_view_tag", .impl = impl.toggleViewTag }, - .{ .name = "zoom", .impl = impl.zoom }, -}; -// zig fmt: on - -pub const Error = error{ - NoCommand, - UnknownCommand, - NotEnoughArguments, - TooManyArguments, - Overflow, - InvalidCharacter, - InvalidDirection, - OutOfMemory, - CommandFailed, -}; - -/// Run a command for the given Seat. The `args` parameter is similar to the -/// classic argv in that the command to be run is passed as the first argument. -/// If the command fails with Error.CommandFailed, a failure message will be -/// allocated and the slice pointed to by the `failure_message` parameter will -/// be set to point to it. The caller is responsible for freeing this message -/// in the case of failure. -pub fn run( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len == 0) return Error.NoCommand; - - const name = args[0]; - const impl_fn = for (str_to_impl_fn) |definition| { - if (std.mem.eql(u8, name, definition.name)) break definition.impl; - } else return Error.UnknownCommand; - - try impl_fn(allocator, seat, args, failure_message); -} diff --git a/src/command/close.zig b/src/command/close.zig deleted file mode 100644 index 04b2dcd..0000000 --- a/src/command/close.zig +++ /dev/null @@ -1,37 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Close the focused view, if any. -pub fn close( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (seat.focused_view) |view| { - // Note: we don't call arrange() here as it will be called - // automatically when the view is unmapped. - view.close(); - } -} diff --git a/src/command/declare_mode.zig b/src/command/declare_mode.zig deleted file mode 100644 index 943ede8..0000000 --- a/src/command/declare_mode.zig +++ /dev/null @@ -1,51 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Mapping = @import("../Mapping.zig"); -const Seat = @import("../Seat.zig"); - -/// Declare a new keymap mode -pub fn declareMode( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const config = &seat.input_manager.server.config; - const new_mode_name = args[1]; - - if (config.mode_to_id.get(new_mode_name) != null) { - failure_message.* = try std.fmt.allocPrint( - allocator, - "mode '{}' already exists and cannot be re-declared", - .{new_mode_name}, - ); - return Error.CommandFailed; - } - - try config.mode_to_id.putNoClobber(new_mode_name, config.modes.items.len); - errdefer _ = config.mode_to_id.remove(new_mode_name); - try config.modes.append(std.ArrayList(Mapping).init(allocator)); -} diff --git a/src/command/enter_mode.zig b/src/command/enter_mode.zig deleted file mode 100644 index 8c7e3e4..0000000 --- a/src/command/enter_mode.zig +++ /dev/null @@ -1,43 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Switch to the given mode -pub fn enterMode( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const config = seat.input_manager.server.config; - const target_mode = args[1]; - seat.mode_id = config.mode_to_id.getValue(target_mode) orelse { - failure_message.* = try std.fmt.allocPrint( - allocator, - "cannot enter non-existant mode '{}'", - .{target_mode}, - ); - return Error.CommandFailed; - }; -} diff --git a/src/command/exit.zig b/src/command/exit.zig deleted file mode 100644 index a21e4f3..0000000 --- a/src/command/exit.zig +++ /dev/null @@ -1,34 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Exit the compositor, terminating the wayland session. -pub fn exit( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len > 1) return Error.TooManyArguments; - c.wl_display_terminate(seat.input_manager.server.wl_display); -} diff --git a/src/command/focus.zig b/src/command/focus.zig deleted file mode 100644 index 0e2c9e0..0000000 --- a/src/command/focus.zig +++ /dev/null @@ -1,67 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Direction = @import("../command.zig").Direction; -const Seat = @import("../Seat.zig"); -const View = @import("../View.zig"); -const ViewStack = @import("../view_stack.zig").ViewStack; - -/// Focus either the next or the previous visible view, depending on the enum -/// passed. Does nothing if there are 1 or 0 views in the stack. -pub fn focus( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const direction = try Direction.parse(args[1]); - const output = seat.focused_output; - - if (seat.focused_view) |current_focus| { - // If there is a currently focused view, focus the next visible view in the stack. - const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus); - var it = switch (direction) { - .Next => ViewStack(View).iterator(focused_node, output.current_focused_tags), - .Prev => ViewStack(View).reverseIterator(focused_node, output.current_focused_tags), - }; - - // Skip past the focused node - _ = it.next(); - // Focus the next visible node if there is one - if (it.next()) |node| { - seat.focus(&node.view); - return; - } - } - - // There is either no currently focused view or the last visible view in the - // stack is focused and we need to wrap. - var it = switch (direction) { - .Next => ViewStack(View).iterator(output.views.first, output.current_focused_tags), - .Prev => ViewStack(View).reverseIterator(output.views.last, output.current_focused_tags), - }; - - seat.focus(if (it.next()) |node| &node.view else null); -} diff --git a/src/command/focus_all_tags.zig b/src/command/focus_all_tags.zig deleted file mode 100644 index 28e001d..0000000 --- a/src/command/focus_all_tags.zig +++ /dev/null @@ -1,32 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Set focus to all tags -pub fn focusAllTags( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - seat.focused_output.pending_focused_tags = 0xFFFFFFFF; - seat.input_manager.server.root.arrange(); -} diff --git a/src/command/focus_output.zig b/src/command/focus_output.zig deleted file mode 100644 index b11664f..0000000 --- a/src/command/focus_output.zig +++ /dev/null @@ -1,54 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Direction = @import("../command.zig").Direction; -const Output = @import("../Output.zig"); -const Seat = @import("../Seat.zig"); - -/// Focus either the next or the previous output, depending on the bool passed. -/// Does nothing if there is only one output. -pub fn focusOutput( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const direction = try Direction.parse(args[1]); - const root = &seat.input_manager.server.root; - // If the noop output is focused, there are no other outputs to switch to - if (seat.focused_output == &root.noop_output) { - std.debug.assert(root.outputs.len == 0); - return; - } - - // Focus the next/prev output in the list if there is one, else wrap - const focused_node = @fieldParentPtr(std.TailQueue(Output).Node, "data", seat.focused_output); - seat.focused_output = switch (direction) { - .Next => if (focused_node.next) |node| &node.data else &root.outputs.first.?.data, - .Prev => if (focused_node.prev) |node| &node.data else &root.outputs.last.?.data, - }; - - seat.focus(null); -} diff --git a/src/command/focus_tag.zig b/src/command/focus_tag.zig deleted file mode 100644 index 8f69e6e..0000000 --- a/src/command/focus_tag.zig +++ /dev/null @@ -1,37 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Switch focus to the passed tag. -pub fn focusTag( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const tag = try std.fmt.parseInt(u32, args[1], 10); - const tags = @as(u32, 1) << @intCast(u5, tag - 1); - seat.focused_output.pending_focused_tags = tags; - seat.input_manager.server.root.arrange(); -} diff --git a/src/command/layout.zig b/src/command/layout.zig deleted file mode 100644 index 4a21050..0000000 --- a/src/command/layout.zig +++ /dev/null @@ -1,37 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Leon Henrik Plickat -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -pub fn layout( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - seat.focused_output.layout = seat.focused_output.getLayoutByName(args[1]); - seat.focused_output.arrangeViews(); - seat.input_manager.server.root.startTransaction(); -} diff --git a/src/command/map.zig b/src/command/map.zig deleted file mode 100644 index 90c0b33..0000000 --- a/src/command/map.zig +++ /dev/null @@ -1,110 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Mapping = @import("../Mapping.zig"); -const Seat = @import("../Seat.zig"); - -const modifier_names = [_]struct { - name: []const u8, - modifier: u32, -}{ - .{ .name = "Shift", .modifier = c.WLR_MODIFIER_SHIFT }, - .{ .name = "Lock", .modifier = c.WLR_MODIFIER_CAPS }, - .{ .name = "Control", .modifier = c.WLR_MODIFIER_CTRL }, - .{ .name = "Mod1", .modifier = c.WLR_MODIFIER_ALT }, - .{ .name = "Mod2", .modifier = c.WLR_MODIFIER_MOD2 }, - .{ .name = "Mod3", .modifier = c.WLR_MODIFIER_MOD3 }, - .{ .name = "Mod4", .modifier = c.WLR_MODIFIER_LOGO }, - .{ .name = "Mod5", .modifier = c.WLR_MODIFIER_MOD5 }, -}; - -/// Create a new mapping for a given mode -/// -/// Example: -/// map normal Mod4|Shift Return spawn alacritty -pub fn map( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 4) return Error.NotEnoughArguments; - - // Parse the mode - const config = seat.input_manager.server.config; - const target_mode = args[1]; - const mode_id = config.mode_to_id.getValue(target_mode) orelse { - failure_message.* = try std.fmt.allocPrint( - allocator, - "cannot add mapping to non-existant mode '{}p'", - .{target_mode}, - ); - return Error.CommandFailed; - }; - - // Parse the modifiers - var it = std.mem.split(args[2], "|"); - var modifiers: u32 = 0; - while (it.next()) |mod_name| { - for (modifier_names) |def| { - if (std.mem.eql(u8, def.name, mod_name)) { - modifiers |= def.modifier; - break; - } - } else { - failure_message.* = try std.fmt.allocPrint( - allocator, - "invalid modifier '{}'", - .{mod_name}, - ); - return Error.CommandFailed; - } - } - - // Parse the keysym - const keysym_name = try std.cstr.addNullByte(allocator, args[3]); - defer allocator.free(keysym_name); - const keysym = c.xkb_keysym_from_name(keysym_name, .XKB_KEYSYM_CASE_INSENSITIVE); - if (keysym == c.XKB_KEY_NoSymbol) { - failure_message.* = try std.fmt.allocPrint( - allocator, - "invalid keysym '{}'", - .{args[3]}, - ); - return Error.CommandFailed; - } - - // Check if the mapping already exists - const mode_mappings = &config.modes.items[mode_id]; - for (mode_mappings.items) |existant_mapping| { - if (existant_mapping.modifiers == modifiers and existant_mapping.keysym == keysym) { - failure_message.* = try std.fmt.allocPrint( - allocator, - "a mapping for modifiers '{}' and keysym '{}' already exists", - .{ args[2], args[3] }, - ); - return Error.CommandFailed; - } - } - - try mode_mappings.append(try Mapping.init(allocator, keysym, modifiers, args[4..])); -} diff --git a/src/command/mod_master_count.zig b/src/command/mod_master_count.zig deleted file mode 100644 index 38a379e..0000000 --- a/src/command/mod_master_count.zig +++ /dev/null @@ -1,42 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Modify the number of master views -pub fn modMasterCount( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const delta = try std.fmt.parseInt(i32, args[1], 10); - const output = seat.focused_output; - output.master_count = @intCast( - u32, - std.math.max(0, @intCast(i32, output.master_count) + delta), - ); - seat.input_manager.server.root.arrange(); -} diff --git a/src/command/mod_master_factor.zig b/src/command/mod_master_factor.zig deleted file mode 100644 index ec8065a..0000000 --- a/src/command/mod_master_factor.zig +++ /dev/null @@ -1,45 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Modify the percent of the width of the screen that the master views occupy. -pub fn modMasterFactor( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const delta = try std.fmt.parseFloat(f64, args[1]); - const output = seat.focused_output; - const new_master_factor = std.math.min( - std.math.max(output.master_factor + delta, 0.05), - 0.95, - ); - if (new_master_factor != output.master_factor) { - output.master_factor = new_master_factor; - seat.input_manager.server.root.arrange(); - } -} diff --git a/src/command/send_to_output.zig b/src/command/send_to_output.zig deleted file mode 100644 index de92f6f..0000000 --- a/src/command/send_to_output.zig +++ /dev/null @@ -1,62 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Direction = @import("../command.zig").Direction; -const Output = @import("../Output.zig"); -const Seat = @import("../Seat.zig"); - -/// Send the focused view to the the next or the previous output, depending on -/// the bool passed. Does nothing if there is only one output. -pub fn sendToOutput( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const direction = try Direction.parse(args[1]); - const root = &seat.input_manager.server.root; - - if (seat.focused_view) |view| { - // If the noop output is focused, there is nowhere to send the view - if (view.output == &root.noop_output) { - std.debug.assert(root.outputs.len == 0); - return; - } - - // Send to the next/preg output in the list if there is one, else wrap - const current_node = @fieldParentPtr(std.TailQueue(Output).Node, "data", view.output); - const destination_output = switch (direction) { - .Next => if (current_node.next) |node| &node.data else &root.outputs.first.?.data, - .Prev => if (current_node.prev) |node| &node.data else &root.outputs.last.?.data, - }; - - // Move the view to the target output - view.sendToOutput(destination_output); - - // Handle the change and focus whatever's next in the focus stack - root.arrange(); - seat.focus(null); - } -} diff --git a/src/command/spawn.zig b/src/command/spawn.zig deleted file mode 100644 index 880483a..0000000 --- a/src/command/spawn.zig +++ /dev/null @@ -1,47 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Spawn a program. -pub fn spawn( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - - const cmd = try std.mem.join(allocator, " ", args[1..]); - defer allocator.free(cmd); - - const child_args = [_][]const u8{ "/bin/sh", "-c", cmd }; - const child = try std.ChildProcess.init(&child_args, allocator); - defer child.deinit(); - - std.ChildProcess.spawn(child) catch |err| { - failure_message.* = try std.fmt.allocPrint( - allocator, - "failed to spawn {}: {}.", - .{ cmd, err }, - ); - return Error.CommandFailed; - }; -} diff --git a/src/command/tag_view.zig b/src/command/tag_view.zig deleted file mode 100644 index 735481d..0000000 --- a/src/command/tag_view.zig +++ /dev/null @@ -1,43 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Set the tag of the focused view. -pub fn tagView( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const tag = try std.fmt.parseInt(u32, args[1], 10); - const tags = @as(u32, 1) << @intCast(u5, tag - 1); - if (seat.focused_view) |view| { - if (view.current_tags != tags) { - view.pending_tags = tags; - seat.input_manager.server.root.arrange(); - } - } -} diff --git a/src/command/tag_view_all_tags.zig b/src/command/tag_view_all_tags.zig deleted file mode 100644 index 2d187f3..0000000 --- a/src/command/tag_view_all_tags.zig +++ /dev/null @@ -1,38 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Tag the focused view with all tags. -pub fn tagViewAllTags( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (seat.focused_view) |view| { - if (view.current_tags != 0xFFFFFFFF) { - view.pending_tags = 0xFFFFFFFF; - seat.input_manager.server.root.arrange(); - } - } -} diff --git a/src/command/toggle_float.zig b/src/command/toggle_float.zig deleted file mode 100644 index 25655e8..0000000 --- a/src/command/toggle_float.zig +++ /dev/null @@ -1,38 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Make the focused view float or stop floating, depending on its current -/// state. -pub fn toggleFloat( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len > 1) return Error.TooManyArguments; - if (seat.focused_view) |view| { - view.setFloating(!view.floating); - view.output.root.arrange(); - } -} diff --git a/src/command/toggle_tag_focus.zig b/src/command/toggle_tag_focus.zig deleted file mode 100644 index 09761c7..0000000 --- a/src/command/toggle_tag_focus.zig +++ /dev/null @@ -1,43 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Toggle focus of the passsed tags. -pub fn toggleTagFocus( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const tag = try std.fmt.parseInt(u32, args[1], 10); - const tags = @as(u32, 1) << @intCast(u5, tag - 1); - const output = seat.focused_output; - const new_focused_tags = output.current_focused_tags ^ tags; - if (new_focused_tags != 0) { - output.pending_focused_tags = new_focused_tags; - seat.input_manager.server.root.arrange(); - } -} diff --git a/src/command/toggle_view_tag.zig b/src/command/toggle_view_tag.zig deleted file mode 100644 index 469a750..0000000 --- a/src/command/toggle_view_tag.zig +++ /dev/null @@ -1,44 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); - -/// Toggle the passed tag of the focused view -pub fn toggleViewTag( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const tag = try std.fmt.parseInt(u32, args[1], 10); - const tags = @as(u32, 1) << @intCast(u5, tag - 1); - if (seat.focused_view) |view| { - const new_tags = view.current_tags ^ tags; - if (new_tags != 0) { - view.pending_tags = new_tags; - seat.input_manager.server.root.arrange(); - } - } -} diff --git a/src/command/zoom.zig b/src/command/zoom.zig deleted file mode 100644 index 59156ab..0000000 --- a/src/command/zoom.zig +++ /dev/null @@ -1,54 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("../c.zig"); - -const Error = @import("../command.zig").Error; -const Seat = @import("../Seat.zig"); -const View = @import("../View.zig"); -const ViewStack = @import("../view_stack.zig").ViewStack; - -/// Bump the focused view to the top of the stack. If the view on the top of -/// the stack is focused, bump the second view to the top. -pub fn zoom( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - failure_message: *[]const u8, -) Error!void { - if (args.len > 1) return Error.TooManyArguments; - - if (seat.focused_view) |current_focus| { - const output = seat.focused_output; - const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus); - - var it = ViewStack(View).iterator(output.views.first, output.current_focused_tags); - const zoom_node = if (focused_node == it.next()) - if (it.next()) |second| second else null - else - focused_node; - - if (zoom_node) |to_bump| { - output.views.remove(to_bump); - output.views.push(to_bump); - seat.input_manager.server.root.arrange(); - seat.focus(&to_bump.view); - } - } -} diff --git a/src/log.zig b/src/log.zig deleted file mode 100644 index 30d2564..0000000 --- a/src/log.zig +++ /dev/null @@ -1,41 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -pub const Log = enum { - const Self = @This(); - - Silent = 0, - Error = 1, - Info = 2, - Debug = 3, - - var verbosity = Self.Error; - - pub fn init(_verbosity: Self) void { - verbosity = _verbosity; - } - - fn log(level: Self, comptime format: []const u8, args: var) void { - if (@enumToInt(level) <= @enumToInt(verbosity)) { - // TODO: log the time since start in the same format as wlroots - // TODO: use color if logging to a tty - std.debug.warn("[{}] " ++ format ++ "\n", .{@tagName(level)} ++ args); - } - } -}; diff --git a/src/render.zig b/src/render.zig deleted file mode 100644 index a7055ef..0000000 --- a/src/render.zig +++ /dev/null @@ -1,315 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const build_options = @import("build_options"); -const std = @import("std"); - -const c = @import("c.zig"); - -const Box = @import("Box.zig"); -const LayerSurface = @import("LayerSurface.zig"); -const Output = @import("Output.zig"); -const Server = @import("Server.zig"); -const View = @import("View.zig"); -const ViewStack = @import("view_stack.zig").ViewStack; - -const SurfaceRenderData = struct { - output: *const Output, - - /// In output layout coordinates relative to the output - output_x: i32, - output_y: i32, - - when: *c.timespec, -}; - -pub fn renderOutput(output: *Output) void { - const wlr_renderer = output.getRenderer(); - - var now: c.timespec = undefined; - _ = c.clock_gettime(c.CLOCK_MONOTONIC, &now); - - // wlr_output_attach_render makes the OpenGL context current. - if (!c.wlr_output_attach_render(output.wlr_output, null)) { - return; - } - // The "effective" resolution can change if you rotate your outputs. - var width: c_int = undefined; - var height: c_int = undefined; - c.wlr_output_effective_resolution(output.wlr_output, &width, &height); - // Begin the renderer (calls glViewport and some other GL sanity checks) - c.wlr_renderer_begin(wlr_renderer, width, height); - - const color = [_]f32{ 0.0, 0.16862745, 0.21176471, 1.0 }; - c.wlr_renderer_clear(wlr_renderer, &color); - - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &now); - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now); - - // The first view in the list is "on top" so iterate in reverse. - var it = ViewStack(View).reverseIterator(output.views.last, output.current_focused_tags); - while (it.next()) |node| { - const view = &node.view; - // This check prevents a race condition when a frame is requested - // between mapping of a view and the first configure being handled. - if (view.current_box.width == 0 or view.current_box.height == 0) { - continue; - } - // Floating views are rendered on top of normal views - if (view.floating) { - continue; - } - renderView(output.*, view, &now); - renderBorders(output.*, view, &now); - } - - // Render floating views - it = ViewStack(View).reverseIterator(output.views.last, output.current_focused_tags); - while (it.next()) |node| { - const view = &node.view; - // This check prevents a race condition when a frame is requested - // between mapping of a view and the first configure being handled. - if (view.current_box.width == 0 or view.current_box.height == 0) { - continue; - } - if (!view.floating) { - continue; - } - renderView(output.*, view, &now); - renderBorders(output.*, view, &now); - } - - // Render xwayland unmanged views - if (build_options.xwayland) { - renderXwaylandUnmanaged(output.*, &now); - } - - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now); - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now); - - // Hardware cursors are rendered by the GPU on a separate plane, and can be - // moved around without re-rendering what's beneath them - which is more - // efficient. However, not all hardware supports hardware cursors. For this - // reason, wlroots provides a software fallback, which we ask it to render - // here. wlr_cursor handles configuring hardware vs software cursors for you, - // and this function is a no-op when hardware cursors are in use. - c.wlr_output_render_software_cursors(output.wlr_output, null); - - // Conclude rendering and swap the buffers, showing the final frame - // on-screen. - c.wlr_renderer_end(wlr_renderer); - // TODO: handle failure - _ = c.wlr_output_commit(output.wlr_output); -} - -/// Render all surfaces on the passed layer -fn renderLayer(output: Output, layer: std.TailQueue(LayerSurface), now: *c.timespec) void { - var it = layer.first; - while (it) |node| : (it = node.next) { - const layer_surface = &node.data; - var rdata = SurfaceRenderData{ - .output = &output, - .output_x = layer_surface.box.x, - .output_y = layer_surface.box.y, - .when = now, - }; - c.wlr_layer_surface_v1_for_each_surface( - layer_surface.wlr_layer_surface, - renderSurface, - &rdata, - ); - } -} - -fn renderView(output: Output, view: *View, now: *c.timespec) void { - // If we have a stashed buffer, we are in the middle of a transaction - // and need to render that buffer until the transaction is complete. - if (view.stashed_buffer) |buffer| { - var box = c.wlr_box{ - .x = view.current_box.x, - .y = view.current_box.y, - .width = @intCast(c_int, view.current_box.width), - .height = @intCast(c_int, view.current_box.height), - }; - - // Scale the box to the output's current scaling factor - scaleBox(&box, output.wlr_output.scale); - - var matrix: [9]f32 = undefined; - c.wlr_matrix_project_box( - &matrix, - &box, - .WL_OUTPUT_TRANSFORM_NORMAL, - 0.0, - &output.wlr_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( - output.getRenderer(), - buffer.texture, - &matrix, - 1.0, - ); - } else { - // Since there is no stashed buffer, we are not in the middle of - // a transaction and may simply render each toplevel surface. - var rdata = SurfaceRenderData{ - .output = &output, - .output_x = view.current_box.x, - .output_y = view.current_box.y, - .when = now, - }; - - view.forEachSurface(renderSurface, &rdata); - } -} - -/// Render all xwayland unmanaged windows that appear on the output -fn renderXwaylandUnmanaged(output: Output, now: *c.timespec) void { - const root = output.root; - const output_box: *c.wlr_box = c.wlr_output_layout_get_box( - root.wlr_output_layout, - output.wlr_output, - ); - - var it = output.root.xwayland_unmanaged_views.first; - while (it) |node| : (it = node.next) { - const wlr_xwayland_surface = node.data.wlr_xwayland_surface; - - var rdata = SurfaceRenderData{ - .output = &output, - .output_x = wlr_xwayland_surface.x - output_box.x, - .output_y = wlr_xwayland_surface.y - output_box.y, - .when = now, - }; - c.wlr_surface_for_each_surface(wlr_xwayland_surface.surface, renderSurface, &rdata); - } -} - -/// This function is passed to wlroots to render each surface during iteration -fn renderSurface( - _surface: ?*c.wlr_surface, - surface_x: c_int, - surface_y: c_int, - data: ?*c_void, -) callconv(.C) void { - // wlroots says this will never be null - const surface = _surface.?; - const rdata = @ptrCast(*SurfaceRenderData, @alignCast(@alignOf(SurfaceRenderData), data)); - const output = rdata.output; - const wlr_output = output.wlr_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. - const texture = c.wlr_surface_get_texture(surface); - if (texture == null) { - return; - } - - var box = c.wlr_box{ - .x = rdata.output_x + surface_x, - .y = rdata.output_y + surface_y, - .width = surface.current.width, - .height = surface.current.height, - }; - - // Scale the box to the output's current scaling factor - scaleBox(&box, wlr_output.scale); - - // 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. - var matrix: [9]f32 = undefined; - const transform = c.wlr_output_transform_invert(surface.current.transform); - c.wlr_matrix_project_box(&matrix, &box, transform, 0.0, &wlr_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(output.getRenderer(), 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 renderBorders(output: Output, view: *View, now: *c.timespec) void { - var border: Box = undefined; - const color = if (view.focused) - [_]f32{ 0.57647059, 0.63137255, 0.63137255, 1.0 } // Solarized base1 - else - [_]f32{ 0.34509804, 0.43137255, 0.45882353, 1.0 }; // Solarized base01 - const border_width = output.root.server.config.border_width; - - // left and right, covering the corners as well - border.y = view.current_box.y - @intCast(i32, border_width); - border.width = border_width; - border.height = view.current_box.height + border_width * 2; - - // left - border.x = view.current_box.x - @intCast(i32, border_width); - renderRect(output, border, color); - - // right - border.x = view.current_box.x + @intCast(i32, view.current_box.width); - renderRect(output, border, color); - - // top and bottom - border.x = view.current_box.x; - border.width = view.current_box.width; - border.height = border_width; - - // top - border.y = view.current_box.y - @intCast(i32, border_width); - renderRect(output, border, color); - - // bottom border - border.y = view.current_box.y + @intCast(i32, view.current_box.height); - renderRect(output, border, color); -} - -fn renderRect(output: Output, box: Box, color: [4]f32) void { - var wlr_box = box.toWlrBox(); - scaleBox(&wlr_box, output.wlr_output.scale); - c.wlr_render_rect( - output.getRenderer(), - &wlr_box, - &color, - &output.wlr_output.transform_matrix, - ); -} - -/// Scale a wlr_box, taking the possibility of fractional scaling into account. -fn scaleBox(box: *c.wlr_box, scale: f64) void { - box.x = @floatToInt(c_int, @round(@intToFloat(f64, box.x) * scale)); - box.y = @floatToInt(c_int, @round(@intToFloat(f64, box.y) * scale)); - box.width = scaleLength(box.width, box.x, scale); - box.height = scaleLength(box.height, box.x, scale); -} - -/// Scales a width/height. -/// -/// This might seem overly complex, but it needs to work for fractional scaling. -fn scaleLength(length: c_int, offset: c_int, scale: f64) c_int { - return @floatToInt(c_int, @round(@intToFloat(f64, offset + length) * scale) - - @round(@intToFloat(f64, offset) * scale)); -} diff --git a/src/river.zig b/src/river.zig deleted file mode 100644 index a5f5334..0000000 --- a/src/river.zig +++ /dev/null @@ -1,42 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @import("c.zig"); - -const Log = @import("log.zig").Log; -const Server = @import("Server.zig"); - -pub fn main() !void { - Log.init(Log.Debug); - c.wlr_log_init(.WLR_ERROR, null); - - Log.Info.log("Initializing server", .{}); - - var server: Server = undefined; - try server.init(std.heap.c_allocator); - defer server.deinit(); - - try server.start(); - - Log.Info.log("Running server...", .{}); - - server.run(); - - Log.Info.log("Shutting down server", .{}); -} diff --git a/src/riverctl.zig b/src/riverctl.zig deleted file mode 100644 index 287c1aa..0000000 --- a/src/riverctl.zig +++ /dev/null @@ -1,109 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const std = @import("std"); - -const c = @cImport({ - @cInclude("wayland-client.h"); - @cInclude("river-control-unstable-v1-client-protocol.h"); -}); - -const wl_registry_listener = c.wl_registry_listener{ - .global = handleGlobal, - .global_remove = handleGlobalRemove, -}; - -const command_callback_listener = c.zriver_command_callback_v1_listener{ - .success = handleSuccess, - .failure = handleFailure, -}; - -var river_control_optional: ?*c.zriver_control_v1 = null; - -pub fn main() !void { - const wl_display = c.wl_display_connect(null) orelse return error.CantConnectToDisplay; - const wl_registry = c.wl_display_get_registry(wl_display); - - if (c.wl_registry_add_listener(wl_registry, &wl_registry_listener, null) < 0) - return error.FailedToAddListener; - if (c.wl_display_roundtrip(wl_display) < 0) return error.RoundtripFailed; - - const river_control = river_control_optional orelse return error.RiverControlNotAdvertised; - - var command: c.wl_array = undefined; - c.wl_array_init(&command); - var it = std.process.args(); - // Skip our name - _ = it.nextPosix(); - while (it.nextPosix()) |arg| { - // Add one as we need to copy the null terminators as well - var ptr = @ptrCast([*]u8, c.wl_array_add(&command, arg.len + 1) orelse - return error.OutOfMemory); - for (arg) |ch, i| ptr[i] = ch; - ptr[arg.len] = 0; - } - - const command_callback = c.zriver_control_v1_run_command(river_control, &command); - if (c.zriver_command_callback_v1_add_listener( - command_callback, - &command_callback_listener, - null, - ) < 0) return error.FailedToAddListener; - - // Loop until our callback is called and we exit. - while (true) if (c.wl_display_dispatch(wl_display) < 0) return error.DispatchFailed; -} - -fn handleGlobal( - data: ?*c_void, - wl_registry: ?*c.wl_registry, - name: u32, - interface: ?[*:0]const u8, - version: u32, -) callconv(.C) void { - // We only care about the river_control global - if (std.mem.eql( - u8, - std.mem.spanZ(interface.?), - std.mem.spanZ(@ptrCast([*:0]const u8, c.zriver_control_v1_interface.name.?)), - )) { - river_control_optional = @ptrCast( - *c.zriver_control_v1, - c.wl_registry_bind(wl_registry, name, &c.zriver_control_v1_interface, 1), - ); - } -} - -/// Ignore the event -fn handleGlobalRemove(data: ?*c_void, wl_registry: ?*c.wl_registry, name: u32) callconv(.C) void {} - -/// On success we simply exit with a clean exit code -fn handleSuccess(data: ?*c_void, callback: ?*c.zriver_command_callback_v1) callconv(.C) void { - std.os.exit(0); -} - -/// Print the failure message and exit non-zero -fn handleFailure( - data: ?*c_void, - callback: ?*c.zriver_command_callback_v1, - failure_message: ?[*:0]const u8, -) callconv(.C) void { - if (failure_message) |message| { - std.debug.warn("Error: {}\n", .{failure_message}); - } - std.os.exit(1); -} diff --git a/src/test_main.zig b/src/test_main.zig deleted file mode 100644 index 2ed8247..0000000 --- a/src/test_main.zig +++ /dev/null @@ -1,20 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -test "river test suite" { - _ = @import("view_stack.zig"); -} diff --git a/src/view_stack.zig b/src/view_stack.zig deleted file mode 100644 index 0543d03..0000000 --- a/src/view_stack.zig +++ /dev/null @@ -1,406 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2020 Isaac Freund -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -const View = @import("View.zig"); - -/// A specialized doubly-linked stack that allows for filtered iteration -/// over the nodes. T must be View or *View. -pub fn ViewStack(comptime T: type) type { - if (!(T == View or T == *View)) { - @compileError("ViewStack: T must be View or *View"); - } - return struct { - const Self = @This(); - - pub const Node = struct { - /// Previous/next nodes in the stack - prev: ?*Node, - next: ?*Node, - - /// The view stored in this node - view: T, - }; - - /// Top/bottom nodes in the stack - first: ?*Node, - last: ?*Node, - - /// Initialize an undefined stack - pub fn init(self: *Self) void { - self.first = null; - self.last = null; - } - - /// Add a node to the top of the stack. - pub fn push(self: *Self, new_node: *Node) void { - // Set the prev/next pointers of the new node - new_node.prev = null; - new_node.next = self.first; - - if (self.first) |first| { - // If the list is not empty, set the prev pointer of the current - // first node to the new node. - first.prev = new_node; - } else { - // If the list is empty set the last pointer to the new node. - self.last = new_node; - } - - // Set the first pointer to the new node - self.first = new_node; - } - - /// Remove a node from the view stack. This removes it from the stack of - /// all views as well as the stack of visible ones. - pub fn remove(self: *Self, target_node: *Node) void { - // Set the previous node/list head to the next pointer - if (target_node.prev) |prev_node| { - prev_node.next = target_node.next; - } else { - self.first = target_node.next; - } - - // Set the next node/list tail to the previous pointer - if (target_node.next) |next_node| { - next_node.prev = target_node.prev; - } else { - self.last = target_node.prev; - } - } - - const Iterator = struct { - it: ?*Node, - tags: u32, - reverse: bool, - pending: bool, - - /// Returns the next node in iteration order, or null if done. - /// This function is horribly ugly, but it's well tested below. - pub fn next(self: *Iterator) ?*Node { - while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) { - if (if (self.pending) - if (node.view.pending_tags) |pending_tags| - self.tags & pending_tags != 0 - else - self.tags & node.view.current_tags != 0 - else - self.tags & node.view.current_tags != 0) { - self.it = if (self.reverse) node.prev else node.next; - return node; - } - } - return null; - } - }; - - /// Returns an iterator starting at the passed node and filtered by - /// checking the passed tags against the current tags of each view. - pub fn iterator(start: ?*Node, tags: u32) Iterator { - return Iterator{ - .it = start, - .tags = tags, - .reverse = false, - .pending = false, - }; - } - - /// Returns a reverse iterator starting at the passed node and filtered by - /// checking the passed tags against the current tags of each view. - pub fn reverseIterator(start: ?*Node, tags: u32) Iterator { - return Iterator{ - .it = start, - .tags = tags, - .reverse = true, - .pending = false, - }; - } - - /// Returns an iterator starting at the passed node and filtered by - /// checking the passed tags against the pending tags of each view. - /// If a view has no pending tags, the current tags are used. - pub fn pendingIterator(start: ?*Node, tags: u32) Iterator { - return Iterator{ - .it = start, - .tags = tags, - .reverse = false, - .pending = true, - }; - } - }; -} - -test "push/remove (*View)" { - const testing = @import("std").testing; - - const allocator = testing.allocator; - - var views: ViewStack(*View) = undefined; - views.init(); - - const one = try allocator.create(ViewStack(*View).Node); - defer allocator.destroy(one); - const two = try allocator.create(ViewStack(*View).Node); - defer allocator.destroy(two); - const three = try allocator.create(ViewStack(*View).Node); - defer allocator.destroy(three); - const four = try allocator.create(ViewStack(*View).Node); - defer allocator.destroy(four); - const five = try allocator.create(ViewStack(*View).Node); - defer allocator.destroy(five); - - views.push(three); // {3} - views.push(one); // {1, 3} - views.push(four); // {4, 1, 3} - views.push(five); // {5, 4, 1, 3} - views.push(two); // {2, 5, 4, 1, 3} - - // Simple insertion - { - var it = views.first; - testing.expect(it == two); - it = it.?.next; - testing.expect(it == five); - it = it.?.next; - testing.expect(it == four); - it = it.?.next; - testing.expect(it == one); - it = it.?.next; - testing.expect(it == three); - it = it.?.next; - - testing.expect(it == null); - - testing.expect(views.first == two); - testing.expect(views.last == three); - } - - // Removal of first - views.remove(two); - { - var it = views.first; - testing.expect(it == five); - it = it.?.next; - testing.expect(it == four); - it = it.?.next; - testing.expect(it == one); - it = it.?.next; - testing.expect(it == three); - it = it.?.next; - - testing.expect(it == null); - - testing.expect(views.first == five); - testing.expect(views.last == three); - } - - // Removal of last - views.remove(three); - { - var it = views.first; - testing.expect(it == five); - it = it.?.next; - testing.expect(it == four); - it = it.?.next; - testing.expect(it == one); - it = it.?.next; - - testing.expect(it == null); - - testing.expect(views.first == five); - testing.expect(views.last == one); - } - - // Remove from middle - views.remove(four); - { - var it = views.first; - testing.expect(it == five); - it = it.?.next; - testing.expect(it == one); - it = it.?.next; - - testing.expect(it == null); - - testing.expect(views.first == five); - testing.expect(views.last == one); - } - - // Reinsertion - views.push(two); - views.push(three); - views.push(four); - { - var it = views.first; - testing.expect(it == four); - it = it.?.next; - testing.expect(it == three); - it = it.?.next; - testing.expect(it == two); - it = it.?.next; - testing.expect(it == five); - it = it.?.next; - testing.expect(it == one); - it = it.?.next; - - testing.expect(it == null); - - testing.expect(views.first == four); - testing.expect(views.last == one); - } - - // Clear - views.remove(four); - views.remove(two); - views.remove(three); - views.remove(one); - views.remove(five); - - testing.expect(views.first == null); - testing.expect(views.last == null); -} - -test "iteration (View)" { - const c = @import("c.zig"); - const testing = @import("std").testing; - - const allocator = testing.allocator; - - var views: ViewStack(View) = undefined; - views.init(); - - const one_a_pb = try allocator.create(ViewStack(View).Node); - defer allocator.destroy(one_a_pb); - one_a_pb.view.current_tags = 1 << 0; - one_a_pb.view.pending_tags = 1 << 1; - - const two_a = try allocator.create(ViewStack(View).Node); - defer allocator.destroy(two_a); - two_a.view.current_tags = 1 << 0; - two_a.view.pending_tags = null; - - const three_b_pa = try allocator.create(ViewStack(View).Node); - defer allocator.destroy(three_b_pa); - three_b_pa.view.current_tags = 1 << 1; - three_b_pa.view.pending_tags = 1 << 0; - - const four_b = try allocator.create(ViewStack(View).Node); - defer allocator.destroy(four_b); - four_b.view.current_tags = 1 << 1; - four_b.view.pending_tags = null; - - const five_b = try allocator.create(ViewStack(View).Node); - defer allocator.destroy(five_b); - five_b.view.current_tags = 1 << 1; - five_b.view.pending_tags = null; - - views.push(three_b_pa); // {3} - views.push(one_a_pb); // {1, 3} - views.push(four_b); // {4, 1, 3} - views.push(five_b); // {5, 4, 1, 3} - views.push(two_a); // {2, 5, 4, 1, 3} - - // Iteration over all tags - { - var it = ViewStack(View).iterator(views.first, 0xFFFFFFFF); - testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); - testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); - testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); - testing.expect(it.next() == null); - } - - // Iteration over 'a' tags - { - var it = ViewStack(View).iterator(views.first, 1 << 0); - testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); - testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); - testing.expect(it.next() == null); - } - - // Iteration over 'b' tags - { - var it = ViewStack(View).iterator(views.first, 1 << 1); - testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); - testing.expect(it.next() == null); - } - - // Iteration over tags that aren't present - { - var it = ViewStack(View).iterator(views.first, 1 << 2); - testing.expect(it.next() == null); - } - - // Reverse iteration over all tags - { - var it = ViewStack(View).reverseIterator(views.last, 0xFFFFFFFF); - testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); - testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); - testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); - testing.expect(it.next() == null); - } - - // Reverse iteration over 'a' tags - { - var it = ViewStack(View).reverseIterator(views.last, 1 << 0); - testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); - testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); - testing.expect(it.next() == null); - } - - // Reverse iteration over 'b' tags - { - var it = ViewStack(View).reverseIterator(views.last, 1 << 1); - testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); - testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); - testing.expect(it.next() == null); - } - - // Reverse iteration over tags that aren't present - { - var it = ViewStack(View).reverseIterator(views.first, 1 << 2); - testing.expect(it.next() == null); - } - - // Iteration over (pending) 'a' tags - { - var it = ViewStack(View).pendingIterator(views.first, 1 << 0); - testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view); - testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view); - testing.expect(it.next() == null); - } - - // Iteration over (pending) 'b' tags - { - var it = ViewStack(View).pendingIterator(views.first, 1 << 1); - testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view); - testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view); - testing.expect(it.next() == null); - } - - // Iteration over (pending) tags that aren't present - { - var it = ViewStack(View).pendingIterator(views.first, 1 << 2); - testing.expect(it.next() == null); - } -} |
