# hy3 i3 / sway like layout for [hyprland](https://github.com/hyprwm/hyprland). [Installation](#installation), [Configuration](#configuration) _Check the [changelog](./CHANGELOG.md) for a list of new features and improvements_ ### Features - [x] i3 like tiling - [x] Node based window manipulation (you can interact with multiple windows at once) - [x] Greatly improved tabbed node groups over base hyprland - [x] Optional autotiling Additional features may be suggested in the repo issues or the [matrix room](https://matrix.to/#/#hy3:outfoxxed.me). ### Demo --- In addition to hy3, I maintain [Quickshell](https://quickshell.outfoxxed.me/?utm_source=hy3-readme), a toolkit for creating custom bars, widgets, lockscreens, and other desktop shell components with first class support for Hyprland. If that sounds interesting, check out the [website](https://quickshell.outfoxxed.me/?utm_source=hy3-readme). --- ### Stability hy3 has a tagged release for each hyprland update, and master tracks hyprland's main branch. If you are running a release version of hyprland then use the matching tagged hy3 version. If you are running an untagged hyprland release then use the `master` branch of hy3. Commits are tested before pushing and will build against the hyprland release **in the flake.lock file**. There may be a mismatch with hyprland's main branch. If hy3 fails to build against hyprland's main branch please make an issue or ping me in the [hy3 matrix room](https://matrix.to/#/#hy3-support:outfoxxed.me). Tagged hy3 versions are always checked against the corresponding hyprland tag. If you encounter any bugs, please report them in the issue tracker. When reporting bugs, please include: - Commit hash of the version you are running. - Steps to reproduce the bug (if you can figure them out) - backtrace of the crash (if applicable) ## Installation > [!IMPORTANT] > The master branch of hy3 follows the master branch of hyprland. > Attempting to use a mismatched hyprland release will result in failure when building or loading hy3. > > To use hy3 against a release version of hyprland, > check out the matching hy3 tag for the hyprland version. > hy3 tags are formatted as `hl{version}` where `{version}` matches the release version of hyprland. ### Nix #### Hyprland home manager module Assuming you use hyprland's home manager module, you can easily integrate hy3 by adding it to the plugins array. ```nix # flake.nix { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; home-manager = { url = "github:nix-community/home-manager"; inputs.nixpkgs.follows = "nixpkgs"; }; hyprland.url = "git+https://github.com/hyprwm/Hyprland?submodules=1&ref={version}"; # where {version} is the hyprland release version # or "github:hyprwm/Hyprland?submodules=1" to follow the development branch hy3 = { url = "github:outfoxxed/hy3?ref=hl{version}"; # where {version} is the hyprland release version # or "github:outfoxxed/hy3" to follow the development branch. # (you may encounter issues if you dont do the same for hyprland) inputs.hyprland.follows = "hyprland"; }; }; outputs = { nixpkgs, home-manager, hyprland, hy3, ... }: { homeConfigurations."user@hostname" = home-manager.lib.homeManagerConfiguration { pkgs = nixpkgs.legacyPackages.x86_64-linux; modules = [ hyprland.homeManagerModules.default { wayland.windowManager.hyprland = { enable = true; plugins = [ hy3.packages.x86_64-linux.hy3 ]; }; } ]; }; }; } ``` #### Manual (Nix) hy3's binary is availible as `${hy3.packages..hy3}/lib/libhy3.so`, so you can also directly use it in your hyprland config like so: ```nix # ... wayland.windowManager.hyprland = { # ... extraConfig = '' plugin = ${hy3.packages.x86_64-linux.hy3}/lib/libhy3.so ''; }; ``` ### hyprpm Hyprland now has a dedicated plugin manager, which should be used when your package manager isn't capable of locking hy3 builds to the correct hyprland version. > [!IMPORTANT] > Make sure hyprpm is activated by putting > > ```conf > exec-once = hyprpm reload -n > ``` > > in your hyprland.conf. (See [the wiki](https://wiki.hyprland.org/Plugins/Using-Plugins/) for details.) To install hy3 via hyprpm run ```sh hyprpm add https://github.com/outfoxxed/hy3 ``` To update hy3 (and all other plugins), run ```sh hyprpm update ``` Sometimes the headers from hyprland are not updated, if this happens run (See [issue #109](https://github.com/outfoxxed/hy3/issues/109) for an example of where this happened) ```sh hyprpm update -f ``` (See [the wiki](https://wiki.hyprland.org/Plugins/Using-Plugins/) for details.) > [!WARNING] > When you are running a tagged hyprland version hyprpm (0.34.0+) will build against hy3's > corrosponding release. However if you are running an untagged build (aka `-git`) hyprpm > will build against hy3's _latest_ commit. This means **if you are running an out of date > untagged build of hyprland, hyprpm may pick an incompatible revision of hy3**. > > To fix this problem you will either need to update hyprland or manually build the correct > version of hy3. ### Manual Install hyprland, including its headers and pkg-config file, then run the following commands: ```sh cmake -DCMAKE_BUILD_TYPE=Release -B build cmake --build build ``` The plugin will be located at `build/libhy3.so`, and you can load it normally (See [the hyprland wiki](https://wiki.hyprland.org/Plugins/Using-Plugins/#installing--using-plugins) for details.) Note that the hyprland headers and pkg-config file **MUST be installed correctly, for the target version of hyprland**. ### Arch (AUR) > [!NOTE] > This method of installation is deprecated and you should use _hyprpm_ instead, > as it is simpler and less error prone. > [!CAUTION] > Pacman is not very reliable when it comes to building packages in the correct order. > If you get a notification saying _hy3 was compiled for a different version of hyprland_ > then your packages likely updated in the wrong order, or you have hyprland headers in `/usr/local`. > > To fix this, remove `/usr/include/hyprland`, `/usr/local/include/hyprland`, `/usr/share/pkgconfig/hyprland.pc` and `/usr/local/share/pkgconfig/hyprland.pc`, > then reinstall hyprland and hy3. > > If you know how to fix this please open an issue or pr, or message `@outfoxxed:outfoxxed.me` in the [matrix room](https://matrix.to/#/#hy3-support:outfoxxed.me). hy3 stable (for arch's `hyprland` package) is availible on the AUR as [hy3](https://aur.archlinux.org/packages/hy3). hy3-git (for `hyprland-git` on the AUR, unofficial package) is availible on the AUR as [hy3-git](https://aur.archlinux.org/packages/hy3-git). Both packages install hy3 as `/usr/lib/libhy3.so`. You can enable it in your hyprland configuration by adding the following line anywhere in your `hyprland.conf` ```conf plugin = /usr/lib/libhy3.so ``` ## Configuration > [!IMPORTANT] > The configuration listed below is for the current hy3 commit. > If you are using a release version of hy3 then make sure you are > reading the tagged revision of this readme. Set your `general:layout` to `hy3` in hyprland.conf. hy3 requires using a few custom dispatchers for normal operation. In your hyprland config replace the following dispatchers: - `movefocus` -> `hy3:movefocus` - `movewindow` -> `hy3:movewindow` You can use `hy3:makegroup` to create a new split. The [dispatcher list](#dispatcher-list) and [config fields](#config-fields) sections have all the configuration options, and some explanation as to what they do. [The hyprland config in my dots](https://git.outfoxxed.me/outfoxxed/nixnew/src/branch/master/modules/hyprland/hyprland.conf) can also be used as a reference. ### Config fields ```conf plugin { hy3 { # policy controlling what happens when a node is removed from a group, # leaving only a group # 0 = remove the nested group # 1 = keep the nested group # 2 = keep the nested group only if its parent is a tab group node_collapse_policy = # default: 2 # offset from group split direction when only one window is in a group group_inset = # default: 10 # if a tab group will automatically be created for the first window spawned in a workspace tab_first_window = # tab group settings tabs { # height of the tab bar height = # default: 22 # padding between the tab bar and its focused node padding = # default: 6 # the tab bar should animate in/out from the top instead of below the window from_top = # default: false # radius of tab bar corners radius = # default: 6 # tab bar border width border_width = # default: 2 # render the window title on the bar render_text = # default: true # center the window title text_center = # default: true # font to render the window title with text_font = # default: Sans # height of the window title text_height = # default: 8 # left padding of the window title text_padding = # default: 3 colors { # active tab bar segment colors active = # default: rgba(33ccff40) active_border = # default: rgba(33ccffee) active_text = # default: rgba(ffffffff) # active tab bar segment colors for bars on an unfocused monitor active_alt_monitor = # default: rgba(60606040) active_alt_monitor_border = # default: rgba(808080ee) active_alt_monitor_text = # default: rgba(ffffffff) # focused tab bar segment colors (focused node in unfocused container) focused = # default: rgba(60606040) focused_border = # default: rgba(808080ee) focused_text = # default: rgba(ffffffff) # inactive tab bar segment colors inactive = # default: rgba(30303020) inactive_border = # default: rgba(606060aa) inactive_text = # default: rgba(ffffffff) # urgent tab bar segment colors urgent = # default: rgba(ff223340) urgent_border = # default: rgba(ff2233ee) urgent_text = # default: rgba(ffffffff) # locked tab bar segment colors locked = # default: rgba(90903340) locked_border = # default: rgba(909033ee) locked_text = # default: rgba(ffffffff) } # if tab backgrounds should be blurred # Blur is only visible when the above colors are not opaque. blur = # default: true # opacity multiplier for tabs # Applies to blur as well as the given colors. opacity = # default: 1.0 } # autotiling settings autotile { # enable autotile enable = # default: false # make autotile-created groups ephemeral ephemeral_groups = # default: true # if a window would be squished smaller than this width, a vertical split will be created # -1 = never automatically split vertically # 0 = always automatically split vertically # = pixel width to split at trigger_width = # default: 0 # if a window would be squished smaller than this height, a horizontal split will be created # -1 = never automatically split horizontally # 0 = always automatically split horizontally # = pixel height to split at trigger_height = # default: 0 # a space or comma separated list of workspace ids where autotile should be enabled # it's possible to create an exception rule by prefixing the definition with "not:" # workspaces = 1,2 # autotiling will only be enabled on workspaces 1 and 2 # workspaces = not:1,2 # autotiling will be enabled on all workspaces except 1 and 2 workspaces = # default: all } } } ``` ### Dispatcher list - `hy3:makegroup, , [toggle], [ephemeral | force_ephemeral]` - make a vertical / horizontal split or tab group - `toggle` - if the focused node is the only child of its parent, which is of the type specified, the node's parent will be removed. - `ephemeral` - the group will be removed once it contains only one node. does not affect existing groups. - `force_ephemeral` - same as ephemeral, but converts existing single windows groups. - `hy3:changegroup, ` - change the group the node belongs to, to a different layout - `untab` will untab the group if it was previously tabbed - `toggletab` will untab if group is tabbed, and tab if group is untabbed - `opposite` will toggle between horizontal and vertical layouts if the group is not tabbed. - `hy3:setephemeral, ` - change the ephemerality of the group the node belongs to - `hy3:movefocus, , [visible], [warp | nowarp]` - move the focus left, up, down, or right - `visible` - only move between visible nodes, not hidden tabs - `warp` - warp the mouse to the selected window, even if `general:no_cursor_warps` is true. - `nowarp` - does not warp the mouse to the selected window, even if `general:no_cursor_warps` is false. - `hy3:warpcursor` - warp the cursor to the center of the focused node - `hy3:movewindow, , [once], [visible]` - move a window left, up, down, or right - `once` - only move directly to the neighboring group, without moving into any of its subgroups - `visible` - only move between visible nodes, not hidden tabs - `hy3:movetoworkspace, , [follow, [warp | nowarp]]` - move the active node to the given workspace - `follow` - change focus to the given workspace when moving the selected node - `warp` - warp the mouse to the selected window, even if `general:no_cursor_warps` is true. - `nowarp` - does not warp the mouse to the selected window, even if `general:no_cursor_warps` is false. - `hy3:killactive` - close all windows in the focused node - `hy3:changefocus, ` - `top` - focus all nodes in the workspace - `bottom` - focus the single root selection window - `raise` - raise focus one level - `lower` - lower focus one level - `tab` - raise focus to the nearest tab - `tabnode` - raise focus to the nearest node under the tab - `hy3:togglefocuslayer, [nowarp]` - toggle focus between tiled and floating layers - `nowarp` - do not warp the mouse to the newly focused window - `hy3:focustab, [l | r | left | right | index, ], [prioritize_hovered | require_hovered], [wrap]` - `l | r | left | right` - direction to change focus towards - `index, ` - select the `index`th tab - `prioritize_hovered` - prioritize the tab group under the mouse when multiple are stacked. use the lowest group if none is under the mouse. - `require_hovered` - affect the tab group under the mouse. do nothing if none are hovered. - `wrap` - wrap to the opposite size of the tab bar if moving off the end - `hy3:locktab, [lock | unlock]` - lock the current tab, makingg it behave like a node - `hy3:debugnodes` - print the node tree into the hyprland log - :warning: **ALPHA QUALITY** `hy3:setswallow, ` - set the containing node's window swallow state - :warning: **ALPHA QUALITY** `hy3:expand, ` - expand the current node to cover other nodes - `expand` - expand by one node - `shrink` - shrink by one node - `base` - undo all expansions - `hy3:equalize, [workspace]` - equalize window sizes in group - no argument: equalizes immediate siblings of the focused window - `workspace`: equalizes all windows across the entire workspace tree ### Lua dispatchers When using Hyprland's Lua config, hy3 exposes dispatcher factories under `hl.plugin.hy3`. The returned functions can be passed to `hl.bind(...)`. ```lua local hy3 = hl.plugin.hy3 -- all factories return dispatcher functions and dispatchers return no values -- option tables are optional except for focus_tab hy3.make_group("h" | "v" | "tab" | "opposite", { toggle = true | false, -- default: false ephemeral = true | false | "force", -- default: false }) hy3.change_group("h" | "v" | "tab" | "untab" | "toggletab" | "opposite") hy3.set_ephemeral(true | false | "true" | "false") hy3.move_focus("l" | "r" | "u" | "d" | "left" | "right" | "up" | "down", { visible = true | false, -- default: false warp = true | false, -- default: follows cursor:no_warps }) hy3.toggle_focus_layer({ warp = true | false, -- default: true }) hy3.warp_cursor() hy3.move_window("l" | "r" | "u" | "d" | "left" | "right" | "up" | "down", { once = true | false, -- default: false visible = true | false, -- default: false }) hy3.move_to_workspace("", { follow = true | false, -- default: false warp = true | false, -- default: follows cursor:no_warps when follow = true }) hy3.change_focus("top" | "bottom" | "raise" | "lower" | "tab" | "tabnode") -- direction and index are mutually exclusive hy3.focus_tab({ direction = "l" | "r" | "left" | "right", mouse = "ignore" | "prioritize_hovered" | "require_hovered", -- default: "ignore" wrap = true | false, -- default: false }) hy3.focus_tab({ index = , mouse = "ignore" | "prioritize_hovered" | "require_hovered", -- default: "ignore" wrap = true | false, -- default: false }) hy3.set_swallow(true | false | "true" | "false" | "toggle") hy3.kill_active() hy3.expand("expand" | "shrink" | "base" | "maximize" | "fullscreen", { fullscreen = "" | "intermediate_maximize" | "fullscreen_maximize" | "maximize_only", }) hy3.lock_tab(nil | "" | "toggle" | "lock" | "unlock") hy3.equalize({ scope = "" | "group" | "workspace", -- default: "group" workspace = true | false, -- overrides scope if present recursive = true | false, -- overrides workspace if present }) hy3.debug_nodes() ```