├── .gitignore ├── LICENSE.md ├── README.md ├── build.zig ├── build.zig.zon ├── config.zig ├── makefile ├── picom.conf └── src ├── Atoms.zig ├── Background.zig ├── Input.zig ├── Layout.zig ├── Manager.zig ├── Statusbar.zig ├── Window.zig ├── Workspace.zig ├── actions.zig ├── main.zig └── x11.zig /.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache 2 | zig-out 3 | 4 | *.log 5 | .vscode 6 | Session.vim 7 | 8 | image 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 isaac-westaway 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | // A Banner or Logo 2 | 3 | ![Screenshot](./image/screenshot2.png) 4 | 5 | Overview 6 | ======= 7 | 8 | **This window manager IS still being worked on, (hence the todo) I just do not have much time right now because im in my final year of schooling** 9 | 10 | Zenith is a tiling window manager written in zig for extensibility and modification, in case you would like to write your own window manager with some niche features. 11 | 12 | The reason the project is laid out as it is, is because most window managers I have came across serve their window manager in some 5 thousand line source file, which there is nothing wrong with, just making a small modification is quite difficult. Especially if it is a really minor change like being able to move around windows in firefox. 13 | Hopefully this window manager is different. 14 | 15 | A lot of inspiration for this window manager came from DWM and Ragnar so check it out! 16 | 17 | Features 18 | ======== 19 | 20 | Ability to define a number of workspaces in the `config.zig`, and dynamically create more using the predefined keybind (or one you change it to) without having to re-compile in case you need more real estate 21 | 22 | Optional integration with picom, though support for picom is ongoing as to become compliant with EWMH and ICCM 23 | 24 | 3 Color hovering, focused and unfocused system 25 | 26 | Complete support for an animated background, using a `gif`. You need `imagemagick` installed to coalesce the gif into a series of frames, though 27 | `magick example.gif -coalesce out.bmp` 28 | 29 | Getting Started 30 | =============== 31 | 32 | Prerequisites 33 | ------------- 34 | 35 | `0.13.0` Zig compiler 36 | 37 | Dependencies: 38 | ``` 39 | glibc 40 | imlib2 41 | x11 42 | xinerama 43 | xft 44 | ``` 45 | 46 | Building 47 | -------- 48 | 49 | ``` 50 | git clone https://github.com/isaac-westaway/Zenith 51 | 52 | cd zenith 53 | 54 | make all 55 | ``` 56 | 57 | Installation 58 | ------------ 59 | 60 | Your `~/.xinitrc` should look as follow: 61 | 62 | ``` 63 | exec Zenith 64 | ``` 65 | 66 | Configuration 67 | ------------- 68 | 69 | Configuration is documented in `config.zig` 70 | 71 | Keybinds 72 | -------- 73 | 74 | Taking a screenshot requires the `scrot` package to be installed 75 | 76 | Every key alphabetic key you see is in lowercase 77 | 78 | | Function | Keybind | 79 | | ---------------- | ------- | 80 | | Open Terminal | Mod4 + Return (Enter) | 81 | | Close Window Manager | Mod4 + Escape | 82 | | Tab cycle focus forward | Mod4 + Tab | 83 | | Tab cycle focus reverse | Mod4 + Shift + Tab | 84 | | Take a screenshot | Mod4 + l | 85 | | Toggle fullscreen| Mod4 + f | 86 | | Close currently focused window | Mod4 + q | 87 | | Push currently focused window forward a workspace | Mod4 + p | 88 | | Push currently ... reverse one | Mod4 + o | 89 | | Cycle right a workspace | Mod4 + d | 90 | | Cycle left a workspace | Mod4 + a | 91 | | Unfocus current window (for picom aesthetics) | Mod4 + grave | 92 | | Move Window | Mod4 + Click and drag left mouse button | 93 | | Resize Window | Mod4 + Click and drag right mouse button | 94 | | Add a workspace (dynamically) to the end | Mod4 + equals | 95 | | Remove a workspace (dynamically) at the end | Mod4 + minus | 96 | | Swap the Left and Right focused (Master, Slave) Windows | Mod4 + 1 | 97 | | Add the current focused window as a Master to the layout | Mod4 + 2 | 98 | | Add the current focused window as a Slave to the layout | Mod4 + 3 | 99 | 100 | 101 | Todo 102 | ==== 103 | 104 | Bug fixes 105 | --------- 106 | - Minor logic errors and Atom support specified in `layout.zig` and `workspace.zig` 107 | 108 | Planned Updates 109 | --------------- 110 | - Statusbar 111 | - Multiple monitor support -> I just don't have another screen to work with 112 | - Write a tutorial in the project Wiki on how to set an animated background 113 | 114 | Currently supported atoms 115 | ------------------------- 116 | 117 | - `_NET_NUMBER_OF_DESKTOPS` 118 | - `_NET_CURRENT_DESKTOP` 119 | - `_NET_CLIENT_LIST` 120 | - `_NET_ACTIVE_WINDOW` 121 | 122 | Contributing 123 | ============ 124 | 125 | All contributions are welcome! If you would like to contribute to this project, fork the repo, clone it, new branch, and pull request. 126 | 127 | License 128 | ======= 129 | 130 | This project is licensed under the MIT License 131 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) void { 4 | const target = b.standardTargetOptions(.{}); 5 | 6 | const optimize = b.standardOptimizeOption(.{}); 7 | 8 | const exe = b.addExecutable(.{ 9 | .name = "Zenith", 10 | .root_source_file = b.path("src/main.zig"), 11 | .target = target, 12 | .optimize = optimize, 13 | }); 14 | 15 | const src_module = b.createModule(.{ 16 | .root_source_file = .{ .cwd_relative = "./config.zig" }, 17 | }); 18 | exe.root_module.addImport("config", src_module); 19 | 20 | exe.linkLibC(); 21 | exe.linkSystemLibrary("Imlib2"); 22 | exe.linkSystemLibrary("X11"); 23 | exe.linkSystemLibrary("Xinerama"); 24 | 25 | // const zlog = b.dependency("zlog", .{ .target = target, .optimize = optimize }); 26 | // exe.root_module.addImport("zlog", zlog.module("zlog")); 27 | 28 | b.installArtifact(exe); 29 | 30 | const run_cmd = b.addRunArtifact(exe); 31 | 32 | run_cmd.step.dependOn(b.getInstallStep()); 33 | 34 | if (b.args) |args| { 35 | run_cmd.addArgs(args); 36 | } 37 | 38 | const run_step = b.step("run", "Run the app"); 39 | run_step.dependOn(&run_cmd.step); 40 | 41 | const exe_unit_tests = b.addTest(.{ 42 | .root_source_file = b.path("src/main.zig"), 43 | .target = target, 44 | .optimize = optimize, 45 | }); 46 | 47 | const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); 48 | 49 | const test_step = b.step("test", "Run unit tests"); 50 | test_step.dependOn(&run_exe_unit_tests.step); 51 | } 52 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "Zenith Window Manager", 3 | .version = "1.0.0", 4 | 5 | .dependencies = .{ 6 | .zlog = .{ 7 | .url = "git+https://github.com/isaac-westaway/zlog#e588935f0f77dbcadf6c4ffef9ec8d3545e768fd", 8 | .hash = "12206672911b474f2aee008700a89e4589823993054f030e1bfc06ce98c08bd36c98", 9 | }, 10 | }, 11 | .paths = .{ 12 | "build.zig", 13 | "build.zig.zon", 14 | "src", 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /config.zig: -------------------------------------------------------------------------------- 1 | /// 2 | /// Once you are done editing this file, run `make all` to rebuild the project 3 | /// 4 | const c = @cImport({ 5 | @cInclude("X11/Xlib.h"); 6 | @cInclude("X11/XF86keysym.h"); 7 | @cInclude("X11/keysym.h"); 8 | @cInclude("X11/XKBlib.h"); 9 | @cInclude("X11/Xatom.h"); 10 | @cInclude("X11/Xutil.h"); 11 | }); 12 | 13 | /// The command to be executed when running picom, if you would like to run picom, if not leave it as: "" 14 | /// A comma separated slice of picom command line arguments, add more as you please 15 | pub const picom_command = &[_][]const u8{ "picom", "--config", "/home/isaacwestaway/picom.conf" }; 16 | 17 | /// Animated Background 18 | /// List of images to use for the animated background 19 | /// To automatically generate these images, use imagemagick and run `magick` 20 | /// the images must be in the naming format 21 | /// This example has the images using the filename out-{n}.bmp 22 | pub const animated_background: bool = true; 23 | /// Example: /home/isaacwestaway/Documents/zig/zwm/image/orange/ 24 | pub const image_directory: []const u8 = ""; 25 | /// Example: out 26 | pub const image_file_name: []const u8 = ""; 27 | /// Example: bmp 28 | pub const image_file_extension: []const u8 = ""; 29 | /// Excluding zero, so for 0-22 images would be 22 30 | /// Example: 249 31 | pub const number_of_images: comptime_int = 0; 32 | 33 | /// The absolute path to the background, leave blank if you do NOT want a background 34 | /// Begins at "/" 35 | /// Example: /home/isaacwestaway/Documents/zig/zwm/image/spacex1.jpg 36 | pub const background_path: []const u8 = ""; 37 | 38 | /// The window that is currently focused 39 | pub const hard_focused: comptime_int = 0xef9f1c; 40 | 41 | /// The window that is being hovered over 42 | /// Set this to zero, or the unfocused or the hard focused if you do not want three-color behaviour 43 | pub const soft_focused: comptime_int = 0xf5c577; 44 | 45 | /// The window that is unfocused 46 | pub const unfocused: comptime_int = 0x483008; 47 | 48 | /// The width of the border 49 | /// Set this to zero if you do not want a border at all 50 | /// Setting a window to fullscreen will set the border width to zero, and this is intended because why do you want a border in fullscreen? 51 | pub const border_width: comptime_int = 2; 52 | 53 | /// The terminal command, for example "kitty" or "alacritty" or "xterm" 54 | pub const terminal_cmd: []const u8 = "kitty"; 55 | 56 | /// The number of workspaces you want to start out with, must be at least one 57 | /// You are also able to dynamically create more workspaces using a keybind 58 | pub const inital_number_of_workspaces: comptime_int = 5; 59 | 60 | /// Set this to true if you would like to see a statusbar 61 | /// Currently the statusbar is a work in progress, so it is best to keep this as false and only true for development purposes 62 | pub const enable_statusbar: bool = false; 63 | 64 | /// The border gap width to separate all windows, will be ignored on a fullscreen window 65 | /// It is best to just trial and error what you like, also in future, there will be a way to dynamically change this value, increasing or decreasing 66 | /// At the most extreme of cases, this integer should be less thaan 500, though you should never really use more than 20 pixels 67 | pub const window_gap_width: comptime_int = 10; 68 | 69 | /// 70 | /// Keybinds 71 | /// 72 | /// You can list out the available super keys on your keyboard by running `xmodmap` in your terminal 73 | /// A super key is a key that is run alongside another key, like ctrl + enter, in this example, ctrl is the super key, the super key for control is c.Control mask 74 | /// The `mask` is just X11's way of saying "this must be fulfilled" 75 | /// You should have intellisense for zig installed if you would like a list of keys, or you can read the source code 76 | /// Open the terminal 77 | pub const terminal_super: c.Mask = c.Mod4Mask; 78 | pub const terminal_key: c.KeySym = c.XK_Return; 79 | 80 | /// Close the window manager 81 | pub const close_super: c.Mask = c.Mod4Mask; 82 | pub const close_key: c.Mask = c.XK_Escape; 83 | 84 | /// Cycle focus in the forward direction 85 | pub const cycle_forward_super: c.Mask = c.Mod4Mask; 86 | pub const cycle_forward_key: c.Mask = c.XK_Tab; 87 | 88 | /// Cycle focus in the reverse direction 89 | /// This is intentional, though might be changed later if people really want it changed 90 | /// Essentially the exact same keybinds as with cycling forward, just with an extra super key 91 | pub const cycle_backward_super_second: c.Mask = c.ShiftMask; 92 | 93 | /// This is just for taking images of the window manager, using scrot 94 | pub const scrot_super: c.Mask = c.Mod4Mask; 95 | pub const scrot_key: c.Mask = c.XK_l; 96 | 97 | /// Set the currently focused window to fullscreen 98 | pub const fullscreen_super: c.Mask = c.Mod4Mask; 99 | pub const fullscreen_key: c.Mask = c.XK_f; 100 | 101 | /// Close the currently focused window, NOT the window manager 102 | pub const close_window_super: c.Mask = c.Mod4Mask; 103 | pub const close_window_key: c.Mask = c.XK_q; 104 | 105 | /// Push the currently focused window forward a workspace 106 | pub const push_forward_super: c.Mask = c.Mod4Mask; 107 | pub const push_forward_key: c.Mask = c.XK_p; 108 | 109 | /// Push the currently focused window back one workspace 110 | pub const push_backward_super: c.Mask = c.Mod4Mask; 111 | pub const push_backward_key: c.Mask = c.XK_o; 112 | 113 | /// Cycle to the nnext workspace 114 | pub const workspace_cycle_forward_super: c.Mask = c.Mod4Mask; 115 | pub const workspace_cycle_forward_key: c.Mask = c.XK_d; 116 | 117 | /// Cycle to the previous workspace 118 | pub const workspace_cycle_backward_super: c.Mask = c.Mod4Mask; 119 | pub const workspace_cycle_backward_key: c.Mask = c.XK_a; 120 | 121 | /// Unfocus the current window by making the window slightly transparent 122 | /// Kinda useless, for aesthetic purposes 123 | pub const unfocus_super: c.Mask = c.Mod4Mask; 124 | pub const unfocus_key: c.Mask = c.XK_grave; 125 | 126 | /// Append a new workspace at the end of the workspace list 127 | pub const worskpace_append_super: c.Mask = c.Mod4Mask; 128 | pub const workspace_append_key: c.Mask = c.XK_equal; 129 | 130 | /// Pop the last workspace in the list of workspaces, 1,2,3,4,5 -> 1,2,3,4 131 | pub const workspace_pop_super: c.Mask = c.Mod4Mask; 132 | pub const workspace_pop_key: c.Mask = c.XK_minus; 133 | 134 | /// Swap the left (master) window with the top right 135 | pub const swap_left_right_master_super: c.Mask = c.Mod4Mask; 136 | pub const swap_left_right_mastker_key: c.Mask = c.XK_1; 137 | 138 | /// Add the currently focused window as the master window in the unmodified layouot 139 | pub const add_focused_master_super: c.Mask = c.Mod4Mask; 140 | pub const add_focused_master_key: c.Mask = c.XK_2; 141 | 142 | /// Add the currently focused window as a "slave" window in the unmodified layout 143 | pub const add_focused_slave_super: c.Mask = c.Mod4Mask; 144 | pub const add_focused_slave_key: c.Mask = c.XK_3; 145 | 146 | /// Add the current 147 | /// Move the window by pressing and dragging the left mouse button 148 | /// The compile time integer must correspond to the integer in mouse_motion_left 149 | /// Only change this if you know that X11 supports your key, I cannot add support on my own, it is up to Xorg (or wayland once Zenith supports it) 150 | /// To handle the necessary mouse drivers 151 | pub const mouse_button_left: comptime_int = 1; 152 | pub const mouse_motion_left: c.Mask = c.Button1MotionMask; 153 | 154 | /// Resize by clicking and dragging 155 | pub const mouse_button_right: comptime_int = 3; 156 | pub const mouse_motion_right: c.Mask = c.Button3MotionMask; 157 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | zig build 4 | 5 | .PHONY: install 6 | install: 7 | @if ls *.log 1> /dev/null 2>&1; then \ 8 | rm *.log; \ 9 | fi 10 | sudo cp -f ./zig-out/bin/Zenith /usr/bin/Zenith 11 | 12 | sudo chmod 755 /usr/bin/Zenith 13 | 14 | all: build install -------------------------------------------------------------------------------- /picom.conf: -------------------------------------------------------------------------------- 1 | fading = true; 2 | fade-in-step = 0.03; 3 | fade-out-step = 0.03; 4 | 5 | frame-opacity = 0.95; 6 | frame-opacity-for-same-colors = true; 7 | inactive-opacity = 0.8; 8 | active-opacity = 1; 9 | inactive-opacity-override = false; 10 | 11 | corner-radius = 0; 12 | 13 | blur-method = "dual_kawase" 14 | blur-background = true; 15 | blur-background-frame = true; 16 | 17 | xrender-sync-fence = false; 18 | use-damage = false; 19 | detect-transient = true; 20 | 21 | backend = "glx" 22 | 23 | dbus = true; 24 | daemon = false; 25 | 26 | mark-wmwin-focused = false; 27 | mark-ovredir-focused = true; 28 | detect-client-opacity = false; 29 | use-ewmh-active-win = true; 30 | 31 | animations = ({ 32 | triggers = [ "close", "hide" ]; 33 | preset = "disappear"; 34 | },{ 35 | triggers = [ "open", "show" ]; 36 | preset = "appear"; 37 | }, { 38 | triggers = [ "geometry" ]; 39 | preset = "geometry-change"; 40 | }) -------------------------------------------------------------------------------- /src/Atoms.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const c = @import("x11.zig").c; 4 | 5 | pub var zenith_main_factor: c.Atom = undefined; 6 | pub var utf8_string: c.Atom = undefined; 7 | pub var wm_protocols: c.Atom = undefined; 8 | pub var wm_delete: c.Atom = undefined; 9 | pub var wm_take_focus: c.Atom = undefined; 10 | pub var wm_state: c.Atom = undefined; 11 | pub var wm_change_state: c.Atom = undefined; 12 | pub var net_supported: c.Atom = undefined; 13 | pub var net_wm_strut: c.Atom = undefined; 14 | pub var net_wm_strut_partial: c.Atom = undefined; 15 | pub var net_wm_window_type: c.Atom = undefined; 16 | pub var net_wm_window_type_dock: c.Atom = undefined; 17 | pub var net_wm_window_type_dialog: c.Atom = undefined; 18 | pub var net_wm_state: c.Atom = undefined; 19 | pub var net_wm_state_fullscreen: c.Atom = undefined; 20 | pub var net_wm_desktop: c.Atom = undefined; 21 | pub var net_number_of_desktops: c.Atom = undefined; 22 | pub var net_current_desktop: c.Atom = undefined; 23 | pub var net_active_window: c.Atom = undefined; 24 | pub var net_client_list: c.Atom = undefined; 25 | pub var net_supporting_wm_check: c.Atom = undefined; 26 | pub var net_wm_window_opacity: c.Atom = undefined; 27 | 28 | pub const Atoms = struct { 29 | allocator: *std.mem.Allocator, 30 | 31 | x_display: *const c.Display, 32 | x_rootwindow: *const c.Window, 33 | 34 | pub fn init(allocator: *std.mem.Allocator, display: *const c.Display, window: *const c.Window) !Atoms { 35 | const atoms: Atoms = Atoms{ 36 | .allocator = allocator, 37 | 38 | .x_display = display, 39 | .x_rootwindow = window, 40 | }; 41 | 42 | zenith_main_factor = c.XInternAtom(@constCast(display), "ZWM_MAIN_FACTOR", 0); 43 | utf8_string = c.XInternAtom(@constCast(display), "UTF8_STRING", 0); 44 | wm_protocols = c.XInternAtom(@constCast(display), "WM_PROTOCOLS", 0); 45 | wm_delete = c.XInternAtom(@constCast(display), "WM_DELETE_WINDOW", 0); 46 | wm_take_focus = c.XInternAtom(@constCast(display), "WM_TAKE_FOCUS", 0); 47 | wm_state = c.XInternAtom(@constCast(display), "WM_STATE", 0); 48 | wm_change_state = c.XInternAtom(@constCast(display), "WM_CHANGE_STATE", 0); 49 | net_supported = c.XInternAtom(@constCast(display), "_NET_SUPPORTED", 0); 50 | net_wm_strut = c.XInternAtom(@constCast(display), "_NET_WM_STRUT", 0); 51 | net_wm_strut_partial = c.XInternAtom(@constCast(display), "_NET_WM_STRUT_PARTIAL", 0); 52 | net_wm_window_type = c.XInternAtom(@constCast(display), "_NET_WM_WINDOW_TYPE", 0); 53 | net_wm_window_type_dock = c.XInternAtom(@constCast(display), "_NET_WM_WINDOW_TYPE_DOCK", 0); 54 | net_wm_window_type_dialog = c.XInternAtom(@constCast(display), "_NET_WM_WINDOW_TYPE_DIALOG", 0); 55 | net_wm_state = c.XInternAtom(@constCast(display), "_NET_WM_STATE", 0); 56 | net_wm_state_fullscreen = c.XInternAtom(@constCast(display), "_NET_WM_STATE_FULLSCREEN", 0); 57 | net_wm_desktop = c.XInternAtom(@constCast(display), "_NET_WM_DESKTOP", 0); 58 | net_number_of_desktops = c.XInternAtom(@constCast(display), "_NET_NUMBER_OF_DESKTOPS", 0); 59 | net_current_desktop = c.XInternAtom(@constCast(display), "_NET_CURRENT_DESKTOP", 0); 60 | net_active_window = c.XInternAtom(@constCast(display), "_NET_ACTIVE_WINDOW", 0); 61 | net_client_list = c.XInternAtom(@constCast(display), "_NET_CLIENT_LIST", 0); 62 | net_supporting_wm_check = c.XInternAtom(@constCast(display), "_NET_SUPPORTING_WM_CHECK", 0); 63 | net_wm_window_opacity = c.XInternAtom(@constCast(display), "_NET_WM_WINDOW_OPACITY", 0); 64 | 65 | const supported_net_atoms = [_]c.Atom{ net_supported, net_wm_strut, net_wm_strut_partial, net_wm_window_type, net_wm_window_type_dock, net_wm_window_type_dialog, net_wm_state, net_wm_state_fullscreen, net_wm_desktop, net_number_of_desktops, net_current_desktop, net_active_window, net_client_list, net_supporting_wm_check, net_wm_window_opacity }; 66 | _ = c.XChangeProperty( 67 | @constCast(atoms.x_display), 68 | c.XDefaultRootWindow(@constCast(atoms.x_display)), 69 | net_supported, 70 | c.XA_ATOM, 71 | 32, 72 | c.PropModeReplace, 73 | @ptrCast(&supported_net_atoms), 74 | supported_net_atoms.len, 75 | ); 76 | 77 | return atoms; 78 | } // init 79 | }; 80 | -------------------------------------------------------------------------------- /src/Background.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const x11 = @import("x11.zig"); 4 | const c = @import("x11.zig").c; 5 | 6 | const A = @import("Atoms.zig"); 7 | const Atoms = @import("Atoms.zig").Atoms; 8 | 9 | const Imlib = @cImport({ 10 | @cInclude("Imlib2.h"); 11 | }); 12 | 13 | const Config = @import("config"); 14 | 15 | pub const Background = struct { 16 | allocator: *std.mem.Allocator, 17 | 18 | x_display: *const c.Display, 19 | x_rootwindow: c.Window, 20 | x_screen: *const c.Screen, 21 | 22 | background: c.Window, 23 | background_image_index: u64, 24 | 25 | pub fn init(allocator: *std.mem.Allocator, display: *const c.Display, rootwindow: c.Window, screen: *const c.Screen) !Background { 26 | var background: Background = Background{ 27 | .allocator = allocator, 28 | .x_display = display, 29 | .x_rootwindow = rootwindow, 30 | .x_screen = screen, 31 | .background = undefined, 32 | .background_image_index = undefined, 33 | }; 34 | 35 | const scr = c.DefaultScreen(@constCast(background.x_display)); 36 | 37 | const blackpixel = c.XBlackPixel(@constCast(display), scr); 38 | 39 | const screen_width: c_uint = @intCast(c.XDisplayWidth(@constCast(display), scr)); 40 | const screen_height: c_uint = @intCast(c.XDisplayHeight(@constCast(display), scr)); 41 | 42 | const window_attributes = c.XSetWindowAttributes{ .override_redirect = 1, .background_pixel = 0xFFFFFFFF }; 43 | 44 | const window = c.XCreateSimpleWindow(@constCast(background.x_display), background.x_rootwindow, 0, 0, screen_width, screen_height, 0, blackpixel, blackpixel); 45 | _ = c.XMapWindow(@constCast(background.x_display), window); 46 | 47 | _ = c.XConfigureWindow(@constCast(background.x_display), window, c.CWOverrideRedirect, @ptrCast(@constCast(&window_attributes))); 48 | 49 | const opacity_atom: c.Atom = c.XInternAtom(@constCast(background.x_display), "_NET_WM_WINDOW_OPACITY", c.False); 50 | 51 | const opacity: c_uint = 0xFFFFFFFF; 52 | 53 | _ = c.XChangeProperty(@constCast(background.x_display), window, opacity_atom, c.XA_CARDINAL, 32, c.PropModeReplace, @ptrCast(&opacity), 1); 54 | 55 | const dp: ?*c.Display = @constCast(background.x_display); 56 | 57 | Imlib.imlib_context_set_display(@ptrCast(dp)); 58 | Imlib.imlib_context_set_visual(@ptrCast(c.DefaultVisual(dp, scr))); 59 | Imlib.imlib_context_set_colormap(c.DefaultColormap(dp, scr)); 60 | 61 | // Insert list of images and switch between them 62 | const image: Imlib.Imlib_Image = Imlib.imlib_load_image(@ptrCast(Config.background_path)); 63 | Imlib.imlib_context_set_image(image); 64 | 65 | const src_width: c_int = Imlib.imlib_image_get_width(); 66 | const src_height: c_int = Imlib.imlib_image_get_height(); 67 | const dst_width: c_int = @intCast(screen_width); 68 | const dst_height: c_int = @intCast(screen_height); 69 | 70 | const scaled_image: Imlib.Imlib_Image = Imlib.imlib_create_cropped_scaled_image(0, 0, src_width, src_height, dst_width, dst_height); 71 | Imlib.imlib_context_set_image(scaled_image); 72 | 73 | const pixmap = c.XCreatePixmap(@constCast(background.x_display), window, screen_width, screen_height, @as(c_uint, @intCast(c.DefaultDepth(@constCast(background.x_display), scr)))); 74 | 75 | Imlib.imlib_context_set_drawable(pixmap); 76 | Imlib.imlib_render_image_on_drawable(0, 0); 77 | 78 | _ = c.XSetWindowBackgroundPixmap(@constCast(background.x_display), window, pixmap); 79 | _ = c.XClearWindow(@constCast(background.x_display), window); 80 | _ = c.XFlush(@constCast(background.x_display)); 81 | 82 | background.background = window; 83 | 84 | return background; 85 | } // init 86 | 87 | pub fn animateWindow(allocator: *std.mem.Allocator, display: *const c.Display, rootwindow: c.Window, screen: *const c.Screen) !Background { 88 | var background = Background{ 89 | .allocator = allocator, 90 | .x_display = display, 91 | .x_rootwindow = rootwindow, 92 | .x_screen = screen, 93 | .background = undefined, 94 | .background_image_index = 0, 95 | }; 96 | 97 | const scr = c.DefaultScreen(@constCast(display)); 98 | 99 | const blackpixel = c.XBlackPixel(@constCast(display), scr); 100 | 101 | const screen_width: c_uint = @intCast(c.XDisplayWidth(@constCast(display), scr)); 102 | const screen_height: c_uint = @intCast(c.XDisplayHeight(@constCast(display), scr)); 103 | 104 | const window = c.XCreateSimpleWindow(@constCast(display), rootwindow, 0, 0, screen_width, screen_height, 0, blackpixel, blackpixel); 105 | 106 | const opacity_atom: c.Atom = c.XInternAtom(@constCast(background.x_display), "_NET_WM_WINDOW_OPACITY", c.False); 107 | 108 | const opacity: c_uint = 0xFFFFFFFF; 109 | 110 | _ = c.XChangeProperty(@constCast(background.x_display), window, opacity_atom, c.XA_CARDINAL, 32, c.PropModeReplace, @ptrCast(&opacity), 1); 111 | 112 | background.background = window; 113 | 114 | return background; 115 | } // animateWindow 116 | 117 | pub fn animateBackground(allocator: *std.mem.Allocator, display: *const c.Display, window: c.Window, rootwindow: c.Window) void { 118 | _ = rootwindow; 119 | const scr = c.DefaultScreen(@constCast(display)); 120 | 121 | Imlib.imlib_context_set_display(@ptrCast(@constCast(display))); 122 | Imlib.imlib_context_set_visual(@ptrCast(c.DefaultVisual(display, scr))); 123 | Imlib.imlib_context_set_colormap(c.DefaultColormap(display, scr)); 124 | 125 | const screen = c.DefaultScreen(@constCast(display)); 126 | 127 | const screen_width: c_uint = @intCast(c.XDisplayWidth(@constCast(display), screen)); 128 | const screen_height: c_uint = @intCast(c.XDisplayHeight(@constCast(display), screen)); 129 | 130 | const pixmap = c.XCreatePixmap(@constCast(display), window, screen_width, screen_height, @as(c_uint, @intCast(c.DefaultDepth(@constCast(display), scr)))); 131 | Imlib.imlib_context_set_drawable(pixmap); 132 | 133 | var timeout: c.timespec = undefined; 134 | timeout.tv_sec = 0; 135 | timeout.tv_nsec = 33000000; 136 | 137 | var images: [Config.number_of_images]Imlib.Imlib_Image = undefined; 138 | 139 | const t1_index_to_load: usize = @divFloor(Config.number_of_images, 3); 140 | const t2_index_to_load: usize = @divFloor(Config.number_of_images, 3) + t1_index_to_load; 141 | var t3_index_to_load: usize = Config.number_of_images - t1_index_to_load; 142 | 143 | _ = t2_index_to_load; 144 | 145 | switch (Config.number_of_images % 3) { 146 | 0 => {}, 147 | 1 => { 148 | t3_index_to_load += 1; 149 | }, 150 | 2 => { 151 | t3_index_to_load += 2; 152 | }, 153 | else => unreachable, 154 | } 155 | 156 | // Create 3 new thread 157 | 158 | for (0..Config.number_of_images) |index| { 159 | const file_path = std.fmt.allocPrint(allocator.*, "{s}{s}-{d}.{s}", .{ Config.image_directory, Config.image_file_name, index, Config.image_file_extension }) catch unreachable; 160 | 161 | const null_term_slice = allocator.dupeZ(u8, file_path[0..file_path.len]) catch unreachable; 162 | // TODO: load in parallel 163 | images[index] = Imlib.imlib_load_image(null_term_slice); 164 | } 165 | 166 | var scaled_images: [Config.number_of_images]Imlib.Imlib_Image = undefined; 167 | const dst_width: c_int = @intCast(screen_width); 168 | const dst_height: c_int = @intCast(screen_height); 169 | 170 | for (0..Config.number_of_images) |index| { 171 | Imlib.imlib_context_set_image(images[index]); 172 | 173 | const src_width: c_int = Imlib.imlib_image_get_width(); 174 | const src_height: c_int = Imlib.imlib_image_get_height(); 175 | 176 | scaled_images[index] = Imlib.imlib_create_cropped_scaled_image(0, 0, src_width, src_height, dst_width, dst_height); 177 | } 178 | 179 | _ = c.XMapWindow(@constCast(display), window); 180 | 181 | while (true) { 182 | for (0..Config.number_of_images) |index| { 183 | Imlib.imlib_context_set_image(scaled_images[index]); 184 | 185 | Imlib.imlib_render_image_on_drawable(0, 0); 186 | 187 | _ = c.XKillClient(@constCast(display), c.AllTemporary); 188 | _ = c.XSetCloseDownMode(@constCast(display), c.RetainTemporary); 189 | _ = c.XSetWindowBackgroundPixmap(@constCast(display), window, pixmap); 190 | _ = c.XClearWindow(@constCast(display), window); 191 | _ = c.XFlush(@constCast(display)); 192 | _ = c.XSync(@constCast(display), c.False); 193 | std.posix.nanosleep(@intCast(timeout.tv_sec), @intCast(timeout.tv_nsec)); 194 | } 195 | } 196 | } // animateBackground 197 | 198 | pub fn async_loader() !void {} // asyncLoader 199 | }; 200 | -------------------------------------------------------------------------------- /src/Input.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const c = @import("x11.zig").c; 4 | 5 | const Config = @import("config"); 6 | 7 | pub const Input = struct { 8 | allocator: *std.mem.Allocator, 9 | 10 | x_display: *const c.Display, 11 | x_rootwindow: c.Window, 12 | 13 | pub fn init(allocator: *std.mem.Allocator, display: *const c.Display, rootwindow: c.Window) !Input { 14 | var input: Input = undefined; 15 | 16 | input.allocator = allocator; 17 | 18 | input.x_display = display; 19 | input.x_rootwindow = rootwindow; 20 | 21 | // Could potentially turn this into a for loop, though config isn't any form of a list 22 | 23 | _ = c.XUngrabKey(@constCast(input.x_display), c.AnyKey, c.AnyModifier, input.x_rootwindow); 24 | 25 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.terminal_key), Config.terminal_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 26 | 27 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.close_key), Config.close_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 28 | 29 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.cycle_forward_key), Config.cycle_forward_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 30 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.cycle_forward_key), Config.cycle_forward_super | Config.cycle_backward_super_second, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 31 | 32 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.scrot_key), Config.scrot_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 33 | 34 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.fullscreen_key), Config.fullscreen_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 35 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.close_window_key), Config.close_window_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 36 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.push_forward_key), Config.push_forward_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 37 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.push_backward_key), Config.push_backward_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 38 | 39 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.workspace_cycle_forward_key), Config.workspace_cycle_forward_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 40 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.workspace_cycle_backward_key), Config.workspace_cycle_backward_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 41 | 42 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.swap_left_right_mastker_key), Config.swap_left_right_master_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 43 | 44 | // " ` " aka tilde aka grave aka backtick 45 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.unfocus_key), Config.unfocus_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 46 | 47 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.workspace_append_key), Config.worskpace_append_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 48 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.workspace_pop_key), Config.workspace_pop_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 49 | 50 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.add_focused_master_key), Config.add_focused_master_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 51 | _ = c.XGrabKey(@constCast(input.x_display), c.XKeysymToKeycode(@constCast(input.x_display), Config.add_focused_slave_key), Config.add_focused_slave_super, input.x_rootwindow, 0, c.GrabModeAsync, c.GrabModeAsync); 52 | 53 | _ = c.XGrabButton(@constCast(input.x_display), Config.mouse_button_left, c.Mod4Mask, input.x_rootwindow, 0, c.ButtonPress | Config.mouse_motion_left | @as(c_uint, @intCast(c.PointerMotionMask)), c.GrabModeAsync, c.GrabModeAsync, 0, 0); 54 | 55 | _ = c.XGrabButton(@constCast(input.x_display), Config.mouse_button_right, c.Mod4Mask, input.x_rootwindow, 0, c.ButtonPress | Config.mouse_motion_right | @as(c_uint, @intCast(c.PointerMotionMask)), c.GrabModeAsync, c.GrabModeAsync, 0, 0); 56 | 57 | return input; 58 | } // init 59 | }; 60 | -------------------------------------------------------------------------------- /src/Layout.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const x11 = @import("x11.zig"); 4 | const c = @import("x11.zig").c; 5 | 6 | const Window = @import("Window.zig").Window; 7 | const Workspace = @import("Workspace.zig").Workspace; 8 | const Statusbar = @import("Statusbar.zig").Statusbar; 9 | const Background = @import("Background.zig").Background; 10 | 11 | const A = @import("Atoms.zig"); 12 | const Atoms = @import("Atoms.zig").Atoms; 13 | 14 | const Actions = @import("actions.zig"); 15 | 16 | const Config = @import("config"); 17 | 18 | pub const Layout = struct { 19 | allocator: *std.mem.Allocator, 20 | 21 | x_display: *const c.Display, 22 | x_screen: *const c.Screen, 23 | x_rootwindow: c.Window, 24 | 25 | screen_w: c_int, 26 | screen_h: c_int, 27 | 28 | statusbar: Statusbar, 29 | background: Background, 30 | workspaces: std.ArrayList(Workspace), 31 | current_ws: u32, 32 | 33 | bg_thread: std.Thread, 34 | 35 | atoms: Atoms, 36 | 37 | fn windowToNode(self: *const Layout, window: c.Window) ?*std.DoublyLinkedList(Window).Node { 38 | var ptr: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.first; 39 | 40 | while (ptr) |node| : (ptr = node.next) { 41 | if (node.data.window == window) { 42 | return node; 43 | } else continue; 44 | } 45 | 46 | return null; 47 | } // windowToNode 48 | 49 | pub fn init(allocator: *std.mem.Allocator, display: *const c.Display, window: c.Window) !Layout { 50 | var layout: Layout = undefined; 51 | 52 | layout.allocator = allocator; 53 | 54 | layout.x_display = display; 55 | layout.x_rootwindow = window; 56 | 57 | const screen = c.DefaultScreen(@constCast(layout.x_display)); 58 | 59 | layout.workspaces = std.ArrayList(Workspace).init(layout.allocator.*); 60 | 61 | for (0..Config.inital_number_of_workspaces) |index| { 62 | _ = index; 63 | 64 | const workspace: Workspace = undefined; 65 | 66 | try layout.workspaces.append(workspace); 67 | } 68 | 69 | layout.screen_w = @intCast(c.XDisplayWidth(@constCast(display), screen)); 70 | layout.screen_h = @intCast(c.XDisplayHeight(@constCast(display), screen)); 71 | 72 | layout.current_ws = 0; 73 | for (layout.workspaces.items) |*workspace| { 74 | workspace.* = Workspace{ .x_display = layout.x_display, .x_rootwindow = layout.x_rootwindow, .windows = std.DoublyLinkedList(Window){}, .fullscreen = false, .fs_window = undefined, .current_focused_window = undefined, .mouse = undefined, .win_x = 0, .win_y = 0, .win_w = 0, .win_h = 0, .screen_w = layout.screen_w, .screen_h = layout.screen_h }; 75 | } 76 | 77 | if (Config.enable_statusbar) { 78 | layout.statusbar = try Statusbar.init(layout.allocator, layout.x_display, &layout.x_rootwindow, layout.x_screen); 79 | } 80 | 81 | layout.atoms = try Atoms.init(layout.allocator, layout.x_display, &layout.x_rootwindow); 82 | 83 | _ = c.XDeleteProperty(@constCast(layout.x_display), layout.x_rootwindow, A.net_client_list); 84 | 85 | x11.setWindowPropertyScalar(@constCast(layout.x_display), layout.x_rootwindow, A.net_number_of_desktops, c.XA_CARDINAL, layout.workspaces.items.len); 86 | x11.setWindowPropertyScalar(@constCast(layout.x_display), layout.x_rootwindow, A.net_current_desktop, c.XA_CARDINAL, layout.current_ws); 87 | x11.setWindowPropertyScalar(@constCast(layout.x_display), layout.x_rootwindow, A.net_active_window, c.XA_WINDOW, layout.x_rootwindow); 88 | 89 | if (Config.animated_background == false) { 90 | layout.background = try Background.init(allocator, layout.x_display, layout.x_rootwindow, layout.x_screen); 91 | } else { 92 | layout.background = try Background.animateWindow(allocator, layout.x_display, layout.x_rootwindow, layout.x_screen); 93 | 94 | layout.bg_thread = try std.Thread.spawn(.{ .allocator = allocator.* }, Background.animateBackground, .{ 95 | allocator, 96 | layout.x_display, 97 | layout.background.background, 98 | layout.x_rootwindow, 99 | // layout.background.background_image_index, 100 | }); 101 | 102 | layout.bg_thread.detach(); 103 | } 104 | 105 | // Begin picom process, if applicable 106 | 107 | if (Config.picom_command.len > 1) { 108 | var process = std.process.Child.init(Config.picom_command, allocator.*); 109 | process.spawn() catch {}; 110 | } 111 | 112 | return layout; 113 | } // init 114 | 115 | pub fn resolveKeyInput(self: *Layout, event: *c.XKeyPressedEvent) !void { 116 | 117 | // Open the Kitty (or what is defined in config.zig) terminal 118 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.terminal_key) { 119 | Actions.openTerminal(self.allocator); 120 | 121 | return; 122 | } 123 | 124 | // Mod4 + lowercase(l) 125 | // Scrot is a package to take screenshots 126 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.scrot_key) { 127 | Actions.scrot(self.allocator); 128 | } 129 | 130 | // Tilde, Grave, Backtick 131 | // Unfocus window, if you want to have a window open but stare at the wallpaper with a blank expression on your face 132 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.unfocus_key) { 133 | x11.setWindowPropertyScalar( 134 | @constCast(self.x_display), 135 | self.x_rootwindow, 136 | A.net_active_window, 137 | c.XA_WINDOW, 138 | @abs(c.None), 139 | ); 140 | } 141 | 142 | // Kill the Window Manager 143 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.close_key) { 144 | std.posix.exit(1); 145 | } 146 | 147 | // Handle the fullscreening 148 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.fullscreen_key) { 149 | if (self.workspaces.items[self.current_ws].windows.len == 0) return; 150 | try self.workspaces.items[self.current_ws].handleFullscreen(); 151 | } 152 | 153 | // Tab list focusing 154 | const cycle_keysym = c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0); 155 | 156 | // Check if the keycode matches the cycle keysym key and other conditions are met 157 | if (cycle_keysym == Config.cycle_forward_key and self.workspaces.items[self.current_ws].windows.len >= 1 and (event.state & Config.cycle_forward_super) != 0) { 158 | const direction: i2 = if ((event.state & Config.cycle_backward_super_second) != 0) 1 else -1; 159 | 160 | if (direction == 1) { 161 | if (self.workspaces.items[self.current_ws].windows.last.?.data.window == self.workspaces.items[self.current_ws].current_focused_window.data.window) { 162 | self.workspaces.items[self.current_ws].current_focused_window = @ptrCast(self.workspaces.items[self.current_ws].windows.first); 163 | } else if (self.workspaces.items[self.current_ws].current_focused_window.next == null) { 164 | self.workspaces.items[self.current_ws].current_focused_window = @ptrCast(self.workspaces.items[self.current_ws].windows.first); 165 | } else { 166 | self.workspaces.items[self.current_ws].current_focused_window = @ptrCast(self.workspaces.items[self.current_ws].current_focused_window.next); 167 | } 168 | } 169 | 170 | if (direction == -1) { 171 | if (self.workspaces.items[self.current_ws].windows.first.?.data.window == self.workspaces.items[self.current_ws].current_focused_window.data.window) { 172 | self.workspaces.items[self.current_ws].current_focused_window = @ptrCast(self.workspaces.items[self.current_ws].windows.last); 173 | } else if (self.workspaces.items[self.current_ws].current_focused_window.prev == null) { 174 | self.workspaces.items[self.current_ws].current_focused_window = @ptrCast(self.workspaces.items[self.current_ws].windows.last); 175 | } else { 176 | self.workspaces.items[self.current_ws].current_focused_window = @ptrCast(self.workspaces.items[self.current_ws].current_focused_window.prev); 177 | } 178 | } 179 | 180 | try self.workspaces.items[self.current_ws].focusOneUnfocusAll(); 181 | 182 | x11.setWindowPropertyScalar( 183 | @constCast(self.x_display), 184 | self.x_rootwindow, 185 | A.net_active_window, 186 | c.XA_WINDOW, 187 | self.workspaces.items[self.current_ws].current_focused_window.data.window, 188 | ); 189 | 190 | return; 191 | } 192 | 193 | // Move right a workspace 194 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.workspace_cycle_forward_key) { 195 | if (self.workspaces.items.len == 1) return; 196 | 197 | var ptr: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.first; 198 | 199 | while (ptr) |node| : (ptr = node.next) { 200 | _ = c.XUnmapWindow(@constCast(self.x_display), node.data.window); 201 | } 202 | 203 | if (self.current_ws == self.workspaces.items.len - 1) { 204 | self.current_ws = 0; 205 | 206 | var windows: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.last; 207 | 208 | while (windows) |node| : (windows = node.prev) { 209 | _ = c.XMapWindow(@constCast(self.x_display), node.data.window); 210 | } 211 | } else { 212 | self.current_ws += 1; 213 | if (self.workspaces.items[self.current_ws].windows.len > 0) { 214 | var windows: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.last; 215 | 216 | while (windows) |node| : (windows = node.prev) { 217 | _ = c.XMapWindow(@constCast(self.x_display), node.data.window); 218 | } 219 | } 220 | } 221 | 222 | try self.workspaces.items[self.current_ws].focusOneUnfocusAll(); 223 | 224 | x11.setWindowPropertyScalar(@constCast(self.x_display), self.x_rootwindow, A.net_current_desktop, c.XA_CARDINAL, self.current_ws); 225 | } 226 | 227 | // Move left a workspace 228 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.workspace_cycle_backward_key) { 229 | if (self.workspaces.items.len == 1) return; 230 | 231 | var ptr: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.first; 232 | 233 | while (ptr) |node| : (ptr = node.next) { 234 | _ = c.XUnmapWindow(@constCast(self.x_display), node.data.window); 235 | } 236 | 237 | if (self.current_ws == 0) { 238 | self.current_ws = @intCast(self.workspaces.items.len - 1); 239 | } else { 240 | self.current_ws -= 1; 241 | } 242 | 243 | var windows: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.last; 244 | 245 | while (windows) |node| : (windows = node.prev) { 246 | _ = c.XMapWindow(@constCast(self.x_display), node.data.window); 247 | } 248 | 249 | try self.workspaces.items[self.current_ws].focusOneUnfocusAll(); 250 | 251 | x11.setWindowPropertyScalar(@constCast(self.x_display), self.x_rootwindow, A.net_current_desktop, c.XA_CARDINAL, self.current_ws); 252 | } 253 | 254 | // Close the currently focused window 255 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.close_window_key) { 256 | try self.workspaces.items[self.current_ws].closeFocusedWindow(); 257 | } 258 | 259 | // Push a window right in a workspace 260 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.push_forward_key) { 261 | if (self.workspaces.items[self.current_ws].windows.len == 0) return; 262 | if (self.workspaces.items.len == 1) return; 263 | 264 | const win = self.windowToNode(self.workspaces.items[self.current_ws].current_focused_window.data.window); 265 | 266 | var ws: u32 = self.current_ws; 267 | 268 | if (ws == self.workspaces.items.len - 1) { 269 | ws = 0; 270 | } else { 271 | ws += 1; 272 | } 273 | 274 | if (win) |w| { 275 | self.workspaces.items[self.current_ws].windows.remove(w); 276 | _ = c.XUnmapWindow(@constCast(self.x_display), w.data.window); 277 | self.workspaces.items[ws].windows.prepend(w); 278 | 279 | self.workspaces.items[ws].current_focused_window = w; 280 | 281 | var window = self.workspaces.items[ws].windows.first; 282 | 283 | // Should come up with a better name 284 | while (window) |_window| : (window = _window.next) { 285 | if (_window.data.window != self.workspaces.items[ws].current_focused_window.data.window) { 286 | _ = c.XSetWindowBorder(@constCast(self.x_display), _window.data.window, Config.unfocused); 287 | } 288 | } 289 | 290 | if (w.data.modified == false) { 291 | if (self.workspaces.items[ws].windows.len - self.workspaces.items[ws].numberOfWindowsModified().number > 1) { 292 | self.workspaces.items[ws].retileAllWindows(); 293 | } else if (self.workspaces.items[ws].windows.len - self.workspaces.items[ws].numberOfWindowsModified().number == 1) { 294 | const unmodified_window = self.workspaces.items[ws].numberOfWindowsModified().last_unmodified; 295 | 296 | _ = c.XResizeWindow(@constCast(self.x_display), unmodified_window.data.window, @abs(self.screen_w) - (2 * Config.window_gap_width), @abs(self.screen_h) - (2 * Config.window_gap_width)); 297 | 298 | _ = c.XMoveWindow(@constCast(self.x_display), unmodified_window.data.window, Config.window_gap_width, Config.window_gap_width); 299 | } else {} 300 | } 301 | } 302 | 303 | if (self.workspaces.items[self.current_ws].windows.len - self.workspaces.items[self.current_ws].numberOfWindowsModified().number > 1) { 304 | self.workspaces.items[self.current_ws].retileAllWindows(); 305 | } else if (self.workspaces.items[self.current_ws].windows.len - self.workspaces.items[self.current_ws].numberOfWindowsModified().number == 1) { 306 | const unmodified_window = self.workspaces.items[self.current_ws].numberOfWindowsModified().last_unmodified; 307 | 308 | _ = c.XResizeWindow(@constCast(self.x_display), unmodified_window.data.window, @abs(self.screen_w) - (2 * Config.window_gap_width), @abs(self.screen_h) - (2 * Config.window_gap_width)); 309 | 310 | _ = c.XMoveWindow(@constCast(self.x_display), unmodified_window.data.window, Config.window_gap_width, Config.window_gap_width); 311 | } else {} 312 | } 313 | 314 | // Push a window left in a workspace 315 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.push_backward_key) { 316 | if (self.workspaces.items[self.current_ws].windows.len == 0) return; 317 | if (self.workspaces.items.len == 1) return; 318 | 319 | const win = self.windowToNode(self.workspaces.items[self.current_ws].current_focused_window.data.window); 320 | 321 | var ws: u32 = self.current_ws; 322 | 323 | if (ws == 0) { 324 | ws = @intCast(self.workspaces.items.len - 1); 325 | } else { 326 | ws -= 1; 327 | } 328 | 329 | if (win) |w| { 330 | self.workspaces.items[self.current_ws].windows.remove(w); 331 | 332 | _ = c.XUnmapWindow(@constCast(self.x_display), w.data.window); 333 | self.workspaces.items[ws].windows.prepend(w); 334 | 335 | self.workspaces.items[ws].current_focused_window = w; 336 | 337 | var window = self.workspaces.items[ws].windows.first; 338 | 339 | while (window) |_window| : (window = _window.next) { 340 | if (_window.data.window != self.workspaces.items[ws].current_focused_window.data.window) { 341 | _ = c.XSetWindowBorder(@constCast(self.x_display), _window.data.window, Config.unfocused); 342 | } 343 | } 344 | 345 | if (w.data.modified == false) { 346 | if (self.workspaces.items[ws].windows.len - self.workspaces.items[ws].numberOfWindowsModified().number > 1) { 347 | self.workspaces.items[ws].retileAllWindows(); 348 | } else if (self.workspaces.items[ws].windows.len - self.workspaces.items[ws].numberOfWindowsModified().number == 1) { 349 | const unmodified_window = self.workspaces.items[ws].numberOfWindowsModified().last_unmodified; 350 | 351 | _ = c.XResizeWindow(@constCast(self.x_display), unmodified_window.data.window, @abs(self.screen_w) - (2 * Config.window_gap_width), @abs(self.screen_h) - (2 * Config.window_gap_width)); 352 | 353 | _ = c.XMoveWindow(@constCast(self.x_display), unmodified_window.data.window, Config.window_gap_width, Config.window_gap_width); 354 | } else {} 355 | } 356 | } 357 | 358 | if (self.workspaces.items[self.current_ws].windows.len - self.workspaces.items[self.current_ws].numberOfWindowsModified().number > 1) { 359 | self.workspaces.items[self.current_ws].retileAllWindows(); 360 | } else if (self.workspaces.items[self.current_ws].windows.len - self.workspaces.items[self.current_ws].numberOfWindowsModified().number == 1) { 361 | const unmodified_window = self.workspaces.items[self.current_ws].numberOfWindowsModified().last_unmodified; 362 | 363 | _ = c.XResizeWindow(@constCast(self.x_display), unmodified_window.data.window, @abs(self.screen_w) - (2 * Config.window_gap_width), @abs(self.screen_h) - (2 * Config.window_gap_width)); 364 | 365 | _ = c.XMoveWindow(@constCast(self.x_display), unmodified_window.data.window, Config.window_gap_width, Config.window_gap_width); 366 | } else {} 367 | } 368 | 369 | // Dynamically append another workspace to the list of workspaces 370 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.workspace_append_key) { 371 | const workspace: Workspace = Workspace{ .x_display = self.x_display, .x_rootwindow = self.x_rootwindow, .windows = std.DoublyLinkedList(Window){}, .fullscreen = false, .fs_window = undefined, .current_focused_window = undefined, .mouse = undefined, .win_x = 0, .win_y = 0, .win_w = 0, .win_h = 0, .screen_w = self.screen_w, .screen_h = self.screen_h }; 372 | 373 | try self.workspaces.append(workspace); 374 | 375 | _ = c.XDeleteProperty(@constCast(self.x_display), self.x_rootwindow, A.net_number_of_desktops); 376 | 377 | x11.setWindowPropertyScalar(@constCast(self.x_display), self.x_rootwindow, A.net_number_of_desktops, c.XA_CARDINAL, self.workspaces.items.len); 378 | 379 | x11.setWindowPropertyScalar(@constCast(self.x_display), self.x_rootwindow, A.net_current_desktop, c.XA_CARDINAL, self.current_ws); 380 | } 381 | 382 | // Dynamically pop all workspaces down to 1, if there is only one, do nothing. Why would you want zero workspaces? 383 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.workspace_pop_key) { 384 | // If popping the last workspace, whilst inside the last workspace 385 | // Move left a workspace 386 | if (self.workspaces.items.len == 1) return; 387 | 388 | if (self.current_ws == self.workspaces.items.len - 1) { 389 | var ptr: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.first; 390 | 391 | while (ptr) |node| : (ptr = node.next) { 392 | _ = c.XUnmapWindow(@constCast(self.x_display), node.data.window); 393 | } 394 | 395 | if (self.current_ws == 0) { 396 | self.current_ws = @intCast(self.workspaces.items.len - 1); 397 | } else { 398 | self.current_ws -= 1; 399 | } 400 | 401 | var windows: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.last; 402 | 403 | while (windows) |node| : (windows = node.prev) { 404 | _ = c.XMapWindow(@constCast(self.x_display), node.data.window); 405 | } 406 | 407 | try self.workspaces.items[self.current_ws].focusOneUnfocusAll(); 408 | 409 | x11.setWindowPropertyScalar(@constCast(self.x_display), self.x_rootwindow, A.net_current_desktop, c.XA_CARDINAL, self.current_ws); 410 | } 411 | _ = self.workspaces.pop(); 412 | 413 | _ = c.XDeleteProperty(@constCast(self.x_display), self.x_rootwindow, A.net_number_of_desktops); 414 | 415 | x11.setWindowPropertyScalar(@constCast(self.x_display), self.x_rootwindow, A.net_number_of_desktops, c.XA_CARDINAL, self.workspaces.items.len); 416 | 417 | x11.setWindowPropertyScalar(@constCast(self.x_display), self.x_rootwindow, A.net_current_desktop, c.XA_CARDINAL, self.current_ws); 418 | } 419 | 420 | // Swap left right master 421 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.swap_left_right_mastker_key) { 422 | if (self.workspaces.items[self.current_ws].windows.len == 0) return; 423 | try self.workspaces.items[self.current_ws].swapLeftRightMaster(); 424 | } 425 | 426 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.add_focused_master_key) { 427 | try self.workspaces.items[self.current_ws].addWindowAsMaster(); 428 | } 429 | 430 | if (c.XkbKeycodeToKeysym(@constCast(self.x_display), @intCast(event.keycode), 0, 0) == Config.add_focused_slave_key) { 431 | try self.workspaces.items[self.current_ws].addWindowAsSlave(); 432 | } 433 | } // resolveKeyInput 434 | 435 | pub fn handleCreateNotify(self: *const Layout, event: *const c.XCreateWindowEvent) !void { 436 | _ = self; 437 | _ = event; 438 | } // handleCreateNotify 439 | 440 | pub fn handleMapRequest(self: *Layout, event: *const c.XMapRequestEvent) !void { 441 | _ = c.XDeleteProperty(@constCast(self.x_display), self.x_rootwindow, A.net_active_window); 442 | _ = c.XSelectInput(@constCast(self.x_display), event.window, c.StructureNotifyMask | c.EnterWindowMask | c.LeaveWindowMask | c.FocusChangeMask); 443 | 444 | const window: Window = Window{ .window = event.window, .modified = false, .fullscreen = false, .w_x = 0, .w_y = 0, .w_w = 0, .w_h = 0, .f_x = 0, .f_y = 0, .f_w = 0, .f_h = 0 }; 445 | var node: *std.DoublyLinkedList(Window).Node = try self.allocator.*.create(std.DoublyLinkedList(Window).Node); 446 | node.data = window; 447 | 448 | var transient_for: c.Window = undefined; 449 | const result = c.XGetTransientForHint(@constCast(self.x_display), event.window, &transient_for); 450 | 451 | if (result != 0) { 452 | node.data.modified = true; 453 | } 454 | 455 | self.workspaces.items[self.current_ws].windows.prepend(node); 456 | 457 | _ = c.XSetInputFocus(@constCast(self.x_display), event.window, c.RevertToParent, c.CurrentTime); 458 | _ = c.XSetWindowBorderWidth(@constCast(self.x_display), event.window, Config.border_width); 459 | _ = c.XMapWindow(@constCast(self.x_display), event.window); 460 | 461 | try self.workspaces.items[self.current_ws].handleWindowMappingTiling(event.window); 462 | 463 | var attributes: c.XWindowAttributes = undefined; 464 | _ = c.XGetWindowAttributes(@constCast(self.x_display), event.window, &attributes); 465 | 466 | self.workspaces.items[self.current_ws].windows.last.?.data.w_x = attributes.x; 467 | self.workspaces.items[self.current_ws].windows.last.?.data.w_y = attributes.y; 468 | 469 | // Again, why can the width and height of the window be negative? 470 | self.workspaces.items[self.current_ws].windows.last.?.data.w_w = @abs(attributes.width); 471 | self.workspaces.items[self.current_ws].windows.last.?.data.w_h = @abs(attributes.height); 472 | 473 | self.workspaces.items[self.current_ws].current_focused_window = @ptrCast(self.workspaces.items[self.current_ws].windows.first); 474 | 475 | var s: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.first; 476 | while (s) |win| : (s = win.next) { 477 | if (win.data.window == self.workspaces.items[self.current_ws].current_focused_window.data.window) { 478 | _ = c.XSetWindowBorder(@constCast(self.x_display), event.window, Config.hard_focused); 479 | } else { 480 | _ = c.XSetWindowBorder(@constCast(self.x_display), win.data.window, Config.unfocused); 481 | } 482 | } 483 | 484 | _ = c.XChangeProperty( 485 | @constCast(self.x_display), 486 | self.x_rootwindow, 487 | A.net_client_list, 488 | c.XA_WINDOW, 489 | 32, 490 | c.PropModeAppend, 491 | @ptrCast(&self.workspaces.items[self.current_ws].current_focused_window.data.window), 492 | 1, 493 | ); 494 | _ = c.XChangeProperty(@constCast(self.x_display), self.x_rootwindow, A.net_active_window, c.XA_WINDOW, 32, c.PropModeReplace, @ptrCast(&self.workspaces.items[self.current_ws].current_focused_window.data.window), 1); 495 | } // handleMapNotify 496 | 497 | pub fn handleDestroyNotify(self: *Layout, event: *const c.XDestroyWindowEvent) !void { 498 | _ = c.XDeleteProperty(@constCast(self.x_display), self.x_rootwindow, A.net_active_window); 499 | 500 | if (event.window == self.background.background) return; 501 | 502 | if (self.workspaces.items[self.current_ws].windows.len == 0) return; 503 | const window: ?*std.DoublyLinkedList(Window).Node = self.windowToNode(event.window); 504 | 505 | if (window) |w| { 506 | self.workspaces.items[self.current_ws].windows.remove(w); 507 | self.allocator.destroy(w); 508 | 509 | if (self.workspaces.items[self.current_ws].windows.len >= 1) { 510 | self.workspaces.items[self.current_ws].current_focused_window = @ptrCast(self.workspaces.items[self.current_ws].windows.last); 511 | _ = c.XSetWindowBorder(@constCast(self.x_display), self.workspaces.items[self.current_ws].current_focused_window.data.window, Config.hard_focused); 512 | _ = c.XSetInputFocus(@constCast(self.x_display), self.workspaces.items[self.current_ws].current_focused_window.data.window, c.RevertToParent, c.CurrentTime); 513 | } else { 514 | _ = c.XSetInputFocus(@constCast(self.x_display), c.DefaultRootWindow(@constCast(self.x_display)), c.RevertToParent, c.CurrentTime); 515 | } 516 | } 517 | 518 | // This is a specific order 519 | 520 | _ = c.XDeleteProperty(@constCast(self.x_display), self.x_rootwindow, A.net_client_list); 521 | 522 | if (self.workspaces.items[self.current_ws].windows.len == 0) return; 523 | 524 | _ = c.XChangeProperty(@constCast(self.x_display), self.x_rootwindow, A.net_active_window, c.XA_WINDOW, 32, c.PropModeReplace, @ptrCast(&self.workspaces.items[self.current_ws].current_focused_window.data.window), 1); 525 | 526 | var it = self.workspaces.items[self.current_ws].windows.first; 527 | while (it) |n| : (it = n.next) { 528 | _ = c.XChangeProperty( 529 | @constCast(self.x_display), 530 | self.x_rootwindow, 531 | A.net_client_list, 532 | c.XA_WINDOW, 533 | 32, 534 | c.PropModeAppend, 535 | @ptrCast(&n.data.window), 536 | 1, 537 | ); 538 | } 539 | 540 | try self.workspaces.items[self.current_ws].handleWindowDestroyTiling(); 541 | } // handleDestroyNotify 542 | 543 | pub fn handleButtonPress(self: *Layout, event: *const c.XButtonPressedEvent) !void { 544 | if (event.window == self.statusbar.x_drawable or event.subwindow == self.statusbar.x_drawable) return; 545 | if (event.subwindow == self.background.background) return; 546 | 547 | if (event.subwindow == 0) return; 548 | var attributes: c.XWindowAttributes = undefined; 549 | _ = c.XGetWindowAttributes(@constCast(self.x_display), event.subwindow, &attributes); 550 | 551 | self.workspaces.items[self.current_ws].win_w = attributes.width; 552 | self.workspaces.items[self.current_ws].win_h = attributes.height; 553 | self.workspaces.items[self.current_ws].win_x = attributes.x; 554 | self.workspaces.items[self.current_ws].win_y = attributes.y; 555 | 556 | self.workspaces.items[self.current_ws].mouse = @constCast(event).*; 557 | 558 | _ = c.XRaiseWindow(@constCast(self.x_display), event.window); 559 | _ = c.XSetWindowBorder(@constCast(self.x_display), event.subwindow, Config.hard_focused); 560 | _ = c.XSetInputFocus(@constCast(self.x_display), event.subwindow, c.RevertToParent, c.CurrentTime); 561 | 562 | const window = self.windowToNode(event.subwindow); 563 | 564 | if (window) |w| { 565 | w.data.modified = true; 566 | self.workspaces.items[self.current_ws].windows.remove(w); 567 | self.workspaces.items[self.current_ws].windows.prepend(w); 568 | 569 | self.workspaces.items[self.current_ws].current_focused_window = @ptrCast(w); 570 | } 571 | 572 | var start: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.first; 573 | 574 | while (start) |win| : (start = win.next) { 575 | if (win.data.window != self.workspaces.items[self.current_ws].current_focused_window.data.window) { 576 | _ = c.XSetWindowBorder(@constCast(self.x_display), win.data.window, Config.unfocused); 577 | } 578 | } 579 | 580 | x11.setWindowPropertyScalar(@constCast(self.x_display), self.x_rootwindow, A.net_active_window, c.XA_WINDOW, event.subwindow); 581 | } // handleButtonPress 582 | 583 | pub fn handleMotionNotify(self: *Layout, event: *const c.XMotionEvent) !void { 584 | if (event.subwindow == self.statusbar.x_drawable) return; 585 | if (event.subwindow == self.background.background) return; 586 | 587 | const diff_mag_x: c_int = event.x - self.workspaces.items[self.current_ws].mouse.x; 588 | const diff_mag_y: c_int = event.y - self.workspaces.items[self.current_ws].mouse.y; 589 | 590 | const new_x: c_int = self.workspaces.items[self.current_ws].win_x + diff_mag_x; 591 | const new_y: c_int = self.workspaces.items[self.current_ws].win_y + diff_mag_y; 592 | 593 | const w_x: c_uint = @abs(self.workspaces.items[self.current_ws].win_w + (event.x - self.workspaces.items[self.current_ws].mouse.x)); 594 | const w_y: c_uint = @abs(self.workspaces.items[self.current_ws].win_h + (event.y - self.workspaces.items[self.current_ws].mouse.y)); 595 | 596 | _ = c.XSetWindowBorder(@constCast(self.x_display), event.subwindow, Config.hard_focused); 597 | 598 | const button: c_uint = self.workspaces.items[self.current_ws].mouse.button; 599 | 600 | const window = self.windowToNode(event.subwindow); 601 | if (window) |w| { 602 | w.data.modified = true; 603 | self.workspaces.items[self.current_ws].windows.remove(w); 604 | self.workspaces.items[self.current_ws].windows.prepend(w); 605 | 606 | self.workspaces.items[self.current_ws].current_focused_window = @ptrCast(w); 607 | } 608 | 609 | var start: ?*std.DoublyLinkedList(Window).Node = self.workspaces.items[self.current_ws].windows.first; 610 | 611 | while (start) |win| : (start = win.next) { 612 | if (win.data.window == self.workspaces.items[self.current_ws].current_focused_window.data.window) { 613 | continue; 614 | } else if (win.data.window != event.window) { 615 | _ = c.XSetWindowBorder(@constCast(self.x_display), win.data.window, Config.unfocused); 616 | } 617 | } 618 | 619 | if (event.window != self.workspaces.items[self.current_ws].current_focused_window.data.window) { 620 | if (button == 1 and self.workspaces.items[self.current_ws].fullscreen == false) { 621 | _ = c.XMoveWindow(@constCast(self.x_display), event.subwindow, new_x, new_y); 622 | } 623 | 624 | if (button == 3 and self.workspaces.items[self.current_ws].fullscreen == false) { 625 | self.workspaces.items[self.current_ws].fullscreen = false; 626 | 627 | _ = c.XResizeWindow(@constCast(self.x_display), event.subwindow, w_x, w_y); 628 | } 629 | } 630 | 631 | if (self.workspaces.items[self.current_ws].windows.len - self.workspaces.items[self.current_ws].numberOfWindowsModified().number > 1) { 632 | self.workspaces.items[self.current_ws].retileAllWindows(); 633 | } else if (self.workspaces.items[self.current_ws].windows.len - self.workspaces.items[self.current_ws].numberOfWindowsModified().number == 1) { 634 | const unmodified_window = self.workspaces.items[self.current_ws].numberOfWindowsModified().last_unmodified; 635 | 636 | _ = c.XResizeWindow(@constCast(self.x_display), unmodified_window.data.window, @abs(self.screen_w) - (2 * Config.window_gap_width), @abs(self.screen_h) - (2 * Config.window_gap_width)); 637 | 638 | _ = c.XMoveWindow(@constCast(self.x_display), unmodified_window.data.window, Config.window_gap_width, Config.window_gap_width); 639 | } else {} 640 | x11.setWindowPropertyScalar(@constCast(self.x_display), self.x_rootwindow, A.net_active_window, c.XA_WINDOW, event.subwindow); 641 | _ = c.XRaiseWindow(@constCast(self.x_display), event.subwindow); 642 | } // handleMotionNotify 643 | 644 | pub fn handleEnterNotify(self: *Layout, event: *const c.XCrossingEvent) !void { 645 | if (event.window == self.statusbar.x_drawable) return; 646 | 647 | if (self.workspaces.items.len == 0) return; 648 | if (self.workspaces.items.len == 1) return; 649 | 650 | const win = self.windowToNode(event.window); 651 | 652 | if (event.window == self.workspaces.items[self.current_ws].current_focused_window.data.window) { 653 | _ = c.XSetInputFocus(@constCast(self.x_display), event.window, c.RevertToParent, c.CurrentTime); 654 | } 655 | 656 | var attributes: c.XWindowAttributes = undefined; 657 | _ = c.XGetWindowAttributes(@constCast(self.x_display), event.window, &attributes); 658 | 659 | self.workspaces.items[self.current_ws].win_x = attributes.x; 660 | self.workspaces.items[self.current_ws].win_y = attributes.y; 661 | self.workspaces.items[self.current_ws].win_w = attributes.width; 662 | self.workspaces.items[self.current_ws].win_h = attributes.height; 663 | 664 | if (win) |w| { 665 | if (w.data.window != self.workspaces.items[self.current_ws].current_focused_window.data.window) { 666 | _ = c.XSetWindowBorder(@constCast(self.x_display), event.window, Config.soft_focused); 667 | } 668 | } 669 | } // handleEnterNotify 670 | 671 | pub fn handleLeaveNotify(self: *Layout, event: *const c.XCrossingEvent) !void { 672 | const win = self.windowToNode(event.window); 673 | 674 | if (self.workspaces.items[self.current_ws].windows.len == 0) return; 675 | 676 | if (win) |w| { 677 | _ = c.XSetInputFocus(@constCast(self.x_display), c.DefaultRootWindow(@constCast(self.x_display)), c.RevertToParent, c.CurrentTime); 678 | if (w.data.window != self.workspaces.items[self.current_ws].current_focused_window.data.window) { 679 | _ = c.XSetWindowBorder(@constCast(self.x_display), event.window, Config.unfocused); 680 | } 681 | } 682 | } // handleLeaveNotify 683 | }; 684 | 685 | // TODO: when popping a window from a workspace, destroy ALL the windows in that workspace, this is the reason for bad window error below 686 | // TODO: fix when pushing window left and right can tiling can lead to bad window error occasionally 687 | // TODO: add the ability to create a initially modified floating window 688 | // TODO: EWMH _NET_WM_FULLSCREEN atom 689 | // TODO: when resizing, set minimum window size 690 | // TODO: fix logic for retileAllWindows when deleting windows and a master is deleted so the top right (second last) slave must be promoted 691 | // Ideas: add the ability to control window x and y position using mod4+Arrow Keys 692 | // Ideas: add the ability to swap to windows (X|Y) -> (Y|X) 693 | // Ideas: add some custom keybind commands such as opening tock and centering it to the screen with a specific width and height 694 | // Ideas: add the ability to create a terminal pad without any particular resizing or modification, just a small scratchpad near the cursor 695 | // -- then you could press mod4+Spsace to tile the workspace 696 | // Also add the ability to hide the cursor 697 | // Iideas: command to hide the border 698 | // Ideas: add the ability to grow and change the border using the mouse button 2, the scroll wheel 699 | -------------------------------------------------------------------------------- /src/Manager.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const root = @import("root"); 4 | 5 | const c = @import("x11.zig").c; 6 | 7 | const Layout = @import("Layout.zig").Layout; 8 | const Input = @import("Input.zig").Input; 9 | 10 | pub const Manager = struct { 11 | allocator: *std.mem.Allocator, 12 | 13 | x_display: *const c.Display, 14 | x_screen: *c.Screen, 15 | x_rootwindow: c.Window, 16 | 17 | layout: Layout, 18 | input: Input, 19 | 20 | pub fn init(allocator: *std.mem.Allocator) !Manager { 21 | var manager: Manager = undefined; 22 | 23 | manager.allocator = allocator; 24 | 25 | manager.x_display = c.XOpenDisplay(null) orelse std.posix.exit(1); 26 | manager.x_screen = c.XDefaultScreenOfDisplay(@constCast(manager.x_display)); 27 | manager.x_rootwindow = c.XDefaultRootWindow(@constCast(manager.x_display)); 28 | 29 | manager.layout = try Layout.init(manager.allocator, manager.x_display, manager.x_rootwindow); 30 | manager.input = try Input.init(manager.allocator, manager.x_display, manager.x_rootwindow); 31 | 32 | _ = c.XSetErrorHandler(Manager.handleError); 33 | 34 | var window_attributes: c.XSetWindowAttributes = undefined; 35 | window_attributes.event_mask = c.SubstructureRedirectMask | c.SubstructureNotifyMask; 36 | 37 | _ = c.XSelectInput(@constCast(manager.x_display), manager.x_rootwindow, window_attributes.event_mask); 38 | 39 | _ = c.XSync(@constCast(manager.x_display), 0); 40 | 41 | return manager; 42 | } // init 43 | 44 | pub fn run(self: *Manager) !void { 45 | // try Logger.Log.info("ZWM_RUN", "Running the window manager", .{}); 46 | while (true) { 47 | var event: c.XEvent = undefined; 48 | _ = c.XNextEvent(@constCast(self.x_display), &event); 49 | 50 | switch (event.type) { 51 | c.KeyPress => { 52 | try self.layout.resolveKeyInput(&event.xkey); 53 | }, 54 | 55 | c.ButtonPress => { 56 | try self.layout.handleButtonPress(@constCast(&event.xbutton)); 57 | }, 58 | 59 | c.PointerMotionMask => { 60 | // try Logger.Log.info("ZWM_RUN", "Pointer Motion Event: {any}", .{event.xmotion}); 61 | }, 62 | 63 | c.MotionNotify => { 64 | try self.layout.handleMotionNotify(&event.xmotion); 65 | }, 66 | 67 | c.CreateNotify => { 68 | try self.layout.handleCreateNotify(&event.xcreatewindow); 69 | }, 70 | 71 | c.DestroyNotify => { 72 | try self.layout.handleDestroyNotify(&event.xdestroywindow); 73 | }, 74 | 75 | c.MapRequest => { 76 | try self.layout.handleMapRequest(&event.xmaprequest); 77 | }, 78 | 79 | c.EnterNotify => { 80 | try self.layout.handleEnterNotify(&event.xcrossing); 81 | }, 82 | 83 | c.LeaveNotify => { 84 | try self.layout.handleLeaveNotify(&event.xcrossing); 85 | }, 86 | 87 | c.FocusIn => { 88 | // try Logger.Log.info("ZWM_RUN", "Focus In Event", .{}); 89 | }, 90 | 91 | else => {}, 92 | } 93 | } 94 | } // run 95 | 96 | fn handleError(_: ?*c.Display, event: [*c]c.XErrorEvent) callconv(.C) c_int { 97 | const evt: *c.XErrorEvent = @ptrCast(event); 98 | switch (evt.error_code) { 99 | c.BadMatch => { 100 | // _ = Logger.Log.err("ZWM_RUN", "BadMatch", .{}) catch { 101 | // return undefined; 102 | // }; 103 | return 0; 104 | }, 105 | c.BadWindow => { 106 | // _ = Logger.Log.err("ZWM_RUN", "BadWindow: {any}", .{event.*}) catch { 107 | // return undefined; 108 | // }; 109 | return 0; 110 | }, 111 | c.BadDrawable => { 112 | // _ = Logger.Log.err("ZWM_RUN", "BadDrawable", .{}) catch { 113 | // return undefined; 114 | // }; 115 | return 0; 116 | }, 117 | else => { 118 | // _ = Logger.Log.err("ZWM_RUN", "Unhandled Error", .{}) catch { 119 | // return undefined; 120 | // }; 121 | }, 122 | } 123 | 124 | return 0; 125 | } // handleError 126 | 127 | /// Invalidates the contents of the display 128 | pub fn deinit(self: *const Manager) void { 129 | _ = c.XCloseDisplay(@constCast(self.x_display)); 130 | } 131 | }; 132 | -------------------------------------------------------------------------------- /src/Statusbar.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const c = @import("x11.zig").c; 4 | 5 | /// This status bar is still in development, currently is just aa boilerplate window to write to 6 | pub const Statusbar = struct { 7 | allocator: *std.mem.Allocator, 8 | 9 | x_display: *const c.Display, 10 | x_rootwindow: *const c.Window, 11 | x_screen: *const c.Screen, 12 | 13 | x_drawable: c.Drawable, 14 | x_gc: c.GC, 15 | 16 | pub fn init(allocator: *std.mem.Allocator, display: *const c.Display, rootwindow: *const c.Window, screen: *const c.Screen) !Statusbar { 17 | var statusbar: Statusbar = Statusbar{ 18 | .allocator = allocator, 19 | .x_display = display, 20 | .x_rootwindow = rootwindow, 21 | .x_screen = screen, 22 | 23 | .x_drawable = undefined, 24 | .x_gc = undefined, 25 | }; 26 | 27 | const scr = c.DefaultScreen(@constCast(statusbar.x_display)); 28 | 29 | const dpy: *const c.Display = statusbar.x_display; 30 | const rw = @constCast(statusbar.x_rootwindow).*; 31 | const x_start: c_int = @intCast(c.XDisplayWidth(@constCast(dpy), scr) - 50); 32 | const y_start = 0; 33 | 34 | const x_end: c_uint = @intCast(c.XDisplayWidth(@constCast(dpy), scr)); 35 | const y_end = 20; 36 | const border = 3; 37 | const blackpixel = c.XBlackPixel(@constCast(dpy), scr); 38 | const whitepixel = c.XWhitePixel(@constCast(dpy), scr); 39 | 40 | statusbar.x_drawable = c.XCreateSimpleWindow(@constCast(dpy), rw, x_start, y_start, x_end, y_end, border, blackpixel, whitepixel); 41 | 42 | _ = c.XMapWindow(@constCast(dpy), statusbar.x_drawable); 43 | 44 | var values: c.XGCValues = c.XGCValues{ 45 | .foreground = 0xef9f1c, 46 | .background = 0xef9f1c, 47 | .line_width = 2, 48 | .line_style = c.LineSolid, 49 | .fill_style = c.FillSolid, 50 | }; 51 | 52 | statusbar.x_gc = c.XCreateGC(@constCast(statusbar.x_display), statusbar.x_drawable, 0, &values); 53 | 54 | _ = c.XDrawRectangle(@constCast(statusbar.x_display), statusbar.x_drawable, statusbar.x_gc, 0, 0, x_end, 50); 55 | 56 | return statusbar; 57 | } // init 58 | }; 59 | -------------------------------------------------------------------------------- /src/Window.zig: -------------------------------------------------------------------------------- 1 | const c = @import("x11.zig").c; 2 | 3 | pub const Window = struct { 4 | window: c.Window, 5 | fullscreen: bool, 6 | modified: bool, 7 | 8 | f_x: i32, 9 | f_y: i32, 10 | 11 | f_w: u32, 12 | f_h: u32, 13 | 14 | w_x: i32, 15 | w_y: i32, 16 | 17 | w_w: u32, 18 | w_h: u32, 19 | }; 20 | -------------------------------------------------------------------------------- /src/Workspace.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const x11 = @import("x11.zig"); 4 | const c = @import("x11.zig").c; 5 | 6 | const Window = @import("Window.zig").Window; 7 | 8 | const Atoms = @import("Atoms.zig"); 9 | 10 | const Actions = @import("actions.zig"); 11 | 12 | const Config = @import("config"); 13 | 14 | pub const Workspace = struct { 15 | x_display: *const c.Display, 16 | x_rootwindow: c.Window, 17 | 18 | windows: std.DoublyLinkedList(Window), 19 | current_focused_window: *std.DoublyLinkedList(Window).Node, 20 | 21 | fullscreen: bool, 22 | fs_window: *std.DoublyLinkedList(Window).Node, 23 | 24 | mouse: c.XButtonEvent, 25 | 26 | win_x: i32, 27 | win_y: i32, 28 | win_w: i32, 29 | win_h: i32, 30 | 31 | screen_w: i32, 32 | screen_h: i32, 33 | 34 | pub fn moveToStart(self: *Workspace, node: *std.DoublyLinkedList(Window).Node) void { 35 | self.windows.remove(node); 36 | self.windows.prepend(node); 37 | } // moveToStart 38 | 39 | pub fn moveToEnd(self: *Workspace, node: *std.DoublyLinkedList(Window).Node) void { 40 | self.windows.remove(node); 41 | self.windows.append(node); 42 | } // moveToEnd 43 | 44 | pub fn numberOfWindowsModified(self: *const Workspace) struct { number: u64, last_unmodified: *std.DoublyLinkedList(Window).Node } { 45 | if (self.windows.len == 0) return .{ 46 | .number = 0, 47 | .last_unmodified = undefined, 48 | }; 49 | 50 | var start: ?*std.DoublyLinkedList(Window).Node = self.windows.last; 51 | 52 | var number_of_windows_modified: usize = 0; 53 | var last_unmodified: *std.DoublyLinkedList(Window).Node = undefined; 54 | while (start) |win| : (start = win.prev) { 55 | if (win.data.modified) { 56 | number_of_windows_modified += 1; 57 | } else { 58 | last_unmodified = win; 59 | } 60 | } 61 | 62 | return .{ 63 | .number = number_of_windows_modified, 64 | .last_unmodified = last_unmodified, 65 | }; 66 | } // numberOfWindowsModified 67 | 68 | fn windowToNode(self: *const Workspace, window: c.Window) ?*std.DoublyLinkedList(Window).Node { 69 | var ptr: ?*std.DoublyLinkedList(Window).Node = self.windows.first; 70 | 71 | while (ptr) |node| : (ptr = node.next) { 72 | if (node.data.window == window) { 73 | return node; 74 | } else continue; 75 | } 76 | 77 | return null; 78 | } // windowToNode 79 | 80 | pub fn handleFullscreen(self: *Workspace) !void { 81 | if (self.fullscreen == false) { 82 | var attributes: c.XWindowAttributes = undefined; 83 | 84 | _ = c.XGetWindowAttributes(@constCast(self.x_display), self.current_focused_window.data.window, &attributes); 85 | 86 | const window = self.windowToNode(self.current_focused_window.data.window); 87 | 88 | if (window) |win| { 89 | win.data.f_x = attributes.x; 90 | win.data.f_y = attributes.y; 91 | 92 | // Why is it possible that the width and height of the window be negative???? 93 | win.data.f_w = @abs(attributes.width); 94 | win.data.f_h = @abs(attributes.height); 95 | 96 | // Border width is zero as it is fullscreen 97 | _ = c.XSetWindowBorderWidth(@constCast(self.x_display), win.data.window, 0); 98 | 99 | _ = c.XRaiseWindow(@constCast(self.x_display), win.data.window); 100 | _ = c.XMoveWindow(@constCast(self.x_display), win.data.window, 0, 0); 101 | _ = c.XResizeWindow(@constCast(self.x_display), win.data.window, @as(c_uint, @intCast(self.screen_w)), @as(c_uint, @intCast(self.screen_h))); 102 | 103 | _ = c.XSetInputFocus(@constCast(self.x_display), win.data.window, c.RevertToPointerRoot, c.CurrentTime); 104 | 105 | self.fs_window = @ptrCast(window); 106 | } 107 | 108 | self.fullscreen = true; 109 | 110 | return; 111 | } 112 | 113 | if (self.fullscreen == true) { 114 | _ = c.XSetWindowBorderWidth(@constCast(self.x_display), self.fs_window.data.window, Config.border_width); 115 | 116 | _ = c.XMoveWindow(@constCast(self.x_display), self.fs_window.data.window, self.fs_window.data.f_x, self.fs_window.data.f_y); 117 | _ = c.XResizeWindow(@constCast(self.x_display), self.fs_window.data.window, @as(c_uint, @intCast(self.fs_window.data.f_w)), @as(c_uint, @intCast(self.fs_window.data.f_h))); 118 | 119 | _ = c.XSetInputFocus(@constCast(self.x_display), self.fs_window.data.window, c.RevertToPointerRoot, c.CurrentTime); 120 | 121 | self.fullscreen = false; 122 | 123 | return; 124 | } 125 | } // handleFullscreen 126 | 127 | pub fn focusOneUnfocusAll(self: *Workspace) !void { 128 | if (self.windows.len > 0) { 129 | _ = c.XRaiseWindow(@constCast(self.x_display), self.current_focused_window.data.window); 130 | _ = c.XSetInputFocus(@constCast(self.x_display), self.current_focused_window.data.window, c.RevertToPointerRoot, c.CurrentTime); 131 | _ = c.XSetWindowBorder(@constCast(self.x_display), self.current_focused_window.data.window, Config.hard_focused); 132 | 133 | _ = c.XSetInputFocus(@constCast(self.x_display), self.current_focused_window.data.window, c.RevertToPointerRoot, c.CurrentTime); 134 | 135 | x11.setWindowPropertyScalar(@constCast(self.x_display), self.x_rootwindow, Atoms.net_active_window, c.XA_WINDOW, self.current_focused_window.data.window); 136 | 137 | var ptr: ?*std.DoublyLinkedList(Window).Node = self.windows.first; 138 | while (ptr) |node| : (ptr = node.next) { 139 | if (node.data.window != self.current_focused_window.data.window) { 140 | _ = c.XSetWindowBorder(@constCast(self.x_display), node.data.window, Config.unfocused); 141 | } else continue; 142 | } 143 | } else return; 144 | } // focuseOneUnfocusAll 145 | 146 | pub fn closeFocusedWindow(self: *Workspace) !void { 147 | if (self.windows.len == 0) return; 148 | 149 | _ = c.XDestroyWindow(@constCast(self.x_display), self.current_focused_window.data.window); 150 | } // closeFocusedWindow 151 | 152 | pub fn retileAllWindows(self: *Workspace) void { 153 | if (self.windows.len == 1) return; 154 | 155 | const left_width: u32 = @abs(@divFloor(self.screen_w, 2) - (Config.window_gap_width + @divFloor(Config.window_gap_width, 2))); 156 | 157 | if (!self.windows.last.?.data.modified) { 158 | _ = c.XResizeWindow(@constCast(self.x_display), self.windows.last.?.data.window, left_width, @abs(self.screen_h - (2 * Config.window_gap_width))); 159 | _ = c.XMoveWindow(@constCast(self.x_display), self.windows.last.?.data.window, Config.window_gap_width, Config.window_gap_width); 160 | } 161 | 162 | const number_of_windows_modified = self.numberOfWindowsModified(); 163 | const total_windows_to_be_modified = self.windows.len - number_of_windows_modified.number; 164 | 165 | const right_width = left_width; 166 | const remaining_height: u32 = @abs(self.screen_h - (Config.window_gap_width)); 167 | 168 | var right_window_height: u64 = 0; 169 | 170 | if (total_windows_to_be_modified <= 1) { 171 | right_window_height = @abs(self.screen_h) - (2 * Config.window_gap_width); 172 | } else { 173 | right_window_height = @divFloor(remaining_height - (total_windows_to_be_modified - 1) * Config.window_gap_width, (total_windows_to_be_modified - 1)); 174 | } 175 | 176 | const last = self.windows.first; 177 | 178 | if (last) |win| { 179 | if (total_windows_to_be_modified == 1) { 180 | // cache the window 181 | const window = win; 182 | self.windows.remove(win); 183 | 184 | self.windows.append(window); 185 | } 186 | } 187 | 188 | var start: ?*std.DoublyLinkedList(Window).Node = self.windows.last.?.prev.?; 189 | var index: u64 = 0; 190 | while (start) |win| : (start = win.prev) { 191 | if (win.data.modified == false) { 192 | _ = c.XResizeWindow(@constCast(self.x_display), win.data.window, right_width, @intCast(right_window_height)); 193 | 194 | _ = c.XMoveWindow(@constCast(self.x_display), win.data.window, @intCast(left_width + 2 * Config.window_gap_width), @intCast(Config.window_gap_width + (index * (right_window_height + Config.window_gap_width)))); 195 | index += 1; 196 | } 197 | } 198 | } // retileAllWindows 199 | 200 | pub fn handleWindowMappingTiling(self: *Workspace, window: c.Window) !void { 201 | const number_of_windows_modified = self.numberOfWindowsModified(); 202 | const total_windows_to_be_modified = self.windows.len - number_of_windows_modified.number; 203 | if (total_windows_to_be_modified >= 2) { 204 | self.retileAllWindows(); 205 | } else { 206 | // THE RIGHT SIDE WIDTH DOES NOT LOOK EQUAL BUT MATHEMATICALLY IT IS!!! 207 | _ = c.XResizeWindow(@constCast(self.x_display), window, @abs(self.screen_w - (2 * Config.window_gap_width)), @abs(self.screen_h - (2 * Config.window_gap_width))); 208 | _ = c.XMoveWindow(@constCast(self.x_display), window, Config.window_gap_width, Config.window_gap_width); 209 | } 210 | } // handleWindowMapTiling 211 | 212 | pub fn handleWindowDestroyTiling(self: *Workspace) !void { 213 | if (self.windows.len == 0) return; 214 | 215 | const window = self.windows.last; 216 | if (window) |win| { 217 | if (self.windows.len == 1 and win.data.modified == false) { 218 | _ = c.XMoveWindow(@constCast(self.x_display), win.data.window, Config.window_gap_width, Config.window_gap_width); 219 | _ = c.XResizeWindow(@constCast(self.x_display), win.data.window, @intCast(self.screen_w - (2 * Config.window_gap_width)), @abs(self.screen_h - (2 * Config.window_gap_width))); 220 | } 221 | } 222 | 223 | if (self.windows.len >= 2) { 224 | self.retileAllWindows(); 225 | } 226 | } // handleWindowDestroyTiling 227 | 228 | // TODO: minor update, swapping left right master, when more than two initially modified windows, creating 2 unmodified windows and swapping leads 229 | // To odd behavious, where the window goes off screen 230 | pub fn swapLeftRightMaster(self: *Workspace) !void { 231 | if (self.current_focused_window.data.modified) return; 232 | if (self.windows.len == 1 or self.windows.len == 0) return; 233 | 234 | const total_to_be_modified = self.windows.len - self.numberOfWindowsModified().number; 235 | if (total_to_be_modified == 1 or total_to_be_modified == 0) return; 236 | 237 | if (self.windows.len == 2) { 238 | if (self.current_focused_window.data.window == self.windows.first.?.data.window) { 239 | self.moveToEnd(@ptrCast(self.windows.first)); 240 | 241 | self.retileAllWindows(); 242 | 243 | return; 244 | } else if (self.current_focused_window.data.window == self.windows.last.?.data.window) { 245 | self.moveToStart(@ptrCast(self.windows.last)); 246 | 247 | self.retileAllWindows(); 248 | 249 | return; 250 | } 251 | } 252 | 253 | // If the master window, then swap the master with the topright 254 | // TODO: to update to the first window that is NOT modifiable 255 | // do it in a function of find first unmodified 256 | if (self.current_focused_window.data.window == self.windows.last.?.data.window) { 257 | const top_right_window = self.windows.last.?.prev; 258 | 259 | self.moveToEnd(@ptrCast(top_right_window)); 260 | 261 | self.windows.remove(self.current_focused_window); 262 | self.windows.insertBefore(@ptrCast(self.windows.last), self.current_focused_window); 263 | 264 | self.retileAllWindows(); 265 | } else if (self.current_focused_window.data.window == self.windows.last.?.prev.?.data.window) { 266 | self.moveToEnd(self.current_focused_window); 267 | 268 | self.retileAllWindows(); 269 | } else { 270 | const current_focused_window = self.current_focused_window.data.window; 271 | 272 | var previous_window_node: *std.DoublyLinkedList(Window).Node = undefined; 273 | var start = self.windows.last; 274 | while (start) |win| : (start = win.prev) { 275 | if (win.data.window == current_focused_window) break else { 276 | previous_window_node = win; 277 | } 278 | } 279 | 280 | self.moveToEnd(self.current_focused_window); 281 | 282 | const now_second_last = self.windows.last.?.prev; 283 | 284 | self.windows.remove(@ptrCast(now_second_last)); 285 | self.windows.insertBefore(@constCast(previous_window_node), @ptrCast(now_second_last)); 286 | 287 | self.retileAllWindows(); 288 | } 289 | 290 | return; 291 | } // swapLeftRightMaster 292 | 293 | pub fn addWindowAsMaster(self: *Workspace) !void { 294 | if (self.windows.len == 0) return; 295 | 296 | if (self.windows.len == 1) { 297 | _ = c.XMoveWindow(@constCast(self.x_display), self.current_focused_window.data.window, Config.window_gap_width, Config.window_gap_width); 298 | _ = c.XResizeWindow(@constCast(self.x_display), self.current_focused_window.data.window, @abs(self.screen_w) - 2 * Config.window_gap_width, @abs(self.screen_h) - 2 * Config.window_gap_width); 299 | return; 300 | } 301 | 302 | if (self.current_focused_window.data.modified == false) { 303 | if (self.current_focused_window.data.window != self.windows.last.?.data.window) { 304 | try self.swapLeftRightMaster(); 305 | } else return; // if the current focused window already is the unmodified master, do nothing 306 | } else { 307 | self.moveToEnd(self.current_focused_window); 308 | self.current_focused_window.data.modified = false; 309 | 310 | self.retileAllWindows(); 311 | } 312 | } // addWindowAsMaster 313 | 314 | pub fn addWindowAsSlave(self: *Workspace) !void { 315 | if (self.windows.len == 0) return; 316 | 317 | if (self.windows.len == 1) { 318 | _ = c.XMoveWindow(@constCast(self.x_display), self.current_focused_window.data.window, Config.window_gap_width, Config.window_gap_width); 319 | _ = c.XResizeWindow(@constCast(self.x_display), self.current_focused_window.data.window, @abs(self.screen_w) - 2 * Config.window_gap_width, @abs(self.screen_h) - 2 * Config.window_gap_width); 320 | return; 321 | } 322 | if (self.current_focused_window.data.modified == false) { 323 | if (self.current_focused_window.data.window == self.windows.last.?.data.window) { 324 | try self.swapLeftRightMaster(); 325 | } else return; // it is already a "slave" window 326 | } else { 327 | self.moveToEnd(self.current_focused_window); 328 | self.moveToEnd(@ptrCast(self.windows.last.?.prev)); 329 | self.current_focused_window.data.modified = false; 330 | 331 | self.retileAllWindows(); 332 | } 333 | } // addWindowAsSlave 334 | 335 | pub fn swapLeftRightRightLeft(self: *Workspace) !void { 336 | _ = self; 337 | } 338 | }; 339 | -------------------------------------------------------------------------------- /src/actions.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Config = @import("config"); 4 | 5 | pub fn openTerminal(allocator: *std.mem.Allocator) void { 6 | const argv: []const []const u8 = &[_][]const u8{Config.terminal_cmd}; 7 | 8 | var process = std.process.Child.init(argv, allocator.*); 9 | 10 | process.spawn() catch return; 11 | } 12 | 13 | // Take screenshot 14 | pub fn scrot(allocator: *std.mem.Allocator) void { 15 | const argv: []const []const u8 = &[_][]const u8{"scrot"}; 16 | 17 | var process = std.process.Child.init(argv, allocator.*); 18 | 19 | process.spawn() catch return; 20 | } 21 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Manager = @import("Manager.zig").Manager; 4 | 5 | pub fn main() !void { 6 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = std.heap.GeneralPurposeAllocator(.{}){}; 7 | defer std.debug.assert(gpa.deinit() == .ok); 8 | const allocator = gpa.allocator(); 9 | 10 | var manager: Manager = try Manager.init(@constCast(&allocator)); 11 | defer manager.deinit(); 12 | 13 | try manager.run(); 14 | } // main 15 | -------------------------------------------------------------------------------- /src/x11.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const c = @cImport({ 4 | @cInclude("X11/Xlib.h"); 5 | @cInclude("X11/XF86keysym.h"); 6 | @cInclude("X11/keysym.h"); 7 | @cInclude("X11/XKBlib.h"); 8 | @cInclude("X11/Xatom.h"); 9 | @cInclude("X11/Xutil.h"); 10 | }); 11 | 12 | pub fn setWindowPropertyScalar(d: *c.Display, w: c.Window, property: c.Atom, property_type: c.Atom, data: anytype) void { 13 | const long_data = @as(c_ulong, data); 14 | _ = c.XChangeProperty(d, w, property, property_type, 32, c.PropModeReplace, std.mem.asBytes(&long_data), 1); 15 | } 16 | --------------------------------------------------------------------------------