aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Box.zig34
-rw-r--r--src/Config.zig284
-rw-r--r--src/Control.zig139
-rw-r--r--src/Cursor.zig412
-rw-r--r--src/Decoration.zig53
-rw-r--r--src/DecorationManager.zig57
-rw-r--r--src/InputManager.zig146
-rw-r--r--src/Keyboard.zig180
-rw-r--r--src/LayerSurface.zig196
-rw-r--r--src/Mapping.zig46
-rw-r--r--src/Output.zig744
-rw-r--r--src/Root.zig247
-rw-r--r--src/Seat.zig312
-rw-r--r--src/Server.zig279
-rw-r--r--src/View.zig271
-rw-r--r--src/VoidView.zig48
-rw-r--r--src/XdgPopup.zig82
-rw-r--r--src/XdgToplevel.zig201
-rw-r--r--src/XwaylandUnmanaged.zig136
-rw-r--r--src/XwaylandView.zig159
-rw-r--r--src/c.zig60
-rw-r--r--src/command.zig122
-rw-r--r--src/command/close.zig37
-rw-r--r--src/command/declare_mode.zig51
-rw-r--r--src/command/enter_mode.zig43
-rw-r--r--src/command/exit.zig34
-rw-r--r--src/command/focus.zig67
-rw-r--r--src/command/focus_all_tags.zig32
-rw-r--r--src/command/focus_output.zig54
-rw-r--r--src/command/focus_tag.zig37
-rw-r--r--src/command/layout.zig37
-rw-r--r--src/command/map.zig110
-rw-r--r--src/command/mod_master_count.zig42
-rw-r--r--src/command/mod_master_factor.zig45
-rw-r--r--src/command/send_to_output.zig62
-rw-r--r--src/command/spawn.zig47
-rw-r--r--src/command/tag_view.zig43
-rw-r--r--src/command/tag_view_all_tags.zig38
-rw-r--r--src/command/toggle_float.zig38
-rw-r--r--src/command/toggle_tag_focus.zig43
-rw-r--r--src/command/toggle_view_tag.zig44
-rw-r--r--src/command/zoom.zig54
-rw-r--r--src/log.zig41
-rw-r--r--src/render.zig315
-rw-r--r--src/river.zig42
-rw-r--r--src/riverctl.zig109
-rw-r--r--src/test_main.zig20
-rw-r--r--src/view_stack.zig406
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);
- }
-}