├── .gitignore ├── lib ├── default.nix └── hyprnix │ ├── lists.nix │ ├── ordering.nix │ ├── types.nix │ └── hyprlang.nix ├── hm-module ├── default.nix ├── devices.nix ├── compat.nix ├── devicesFormat.nix ├── configFormat.nix ├── uwsm.nix ├── keybindsFormat.nix ├── keybinds.nix ├── animations.nix ├── portal.nix ├── environment.nix ├── configRenames.nix ├── rules.nix ├── events.nix ├── monitors.nix └── config.nix ├── examples ├── npins │ ├── sources.json │ └── default.nix ├── default.nix └── vertical-monitors.nix ├── tests └── lib │ └── hyprnix │ ├── lists.nix │ └── ordering.nix ├── README.md ├── flake.nix └── flake.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Nix 2 | /result 3 | /result-man 4 | 5 | -------------------------------------------------------------------------------- /lib/default.nix: -------------------------------------------------------------------------------- 1 | lib: lib0: 2 | let libAttrs = lib.mapAttrs (_: fn: fn lib lib0) (lib.importDir ./hyprnix null); 3 | in lib0 // { 4 | hyprnix = libAttrs; 5 | generators = lib0.generators or { } // { 6 | toHyprlang = lib.hyprnix.hyprlang.toConfigString; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /lib/hyprnix/lists.nix: -------------------------------------------------------------------------------- 1 | lib: _: 2 | let 3 | # Given `pattern` as a list of regular expressions, and `list` as a list of strings, 4 | # check that the `list` matches the `pattern`. 5 | # Always returns `false` if the `list` and `pattern` are different lengths. 6 | elemsMatch = pattern: list: 7 | lib.length pattern == lib.length list 8 | && lib.all ({ fst, snd }: builtins.match fst snd != null) 9 | (lib.zipLists pattern list); 10 | in { # # 11 | inherit elemsMatch; 12 | } 13 | -------------------------------------------------------------------------------- /hm-module/default.nix: -------------------------------------------------------------------------------- 1 | self: 2 | args@{ lib, ... }: 3 | let lib = args.lib.extend (self.lib.overlay); 4 | in { 5 | disabledModules = [ 6 | # module in Home Manager conflicts with this one 7 | "services/window-managers/hyprland.nix" 8 | ]; 9 | 10 | imports = map (nix: lib.modules.importApply nix { inherit self lib; }) [ 11 | ./config.nix 12 | ./compat.nix 13 | ./events.nix 14 | ./environment.nix 15 | ./rules.nix # windowrulev2, layerrule, workspace 16 | ./animations.nix 17 | ./keybinds.nix 18 | ./monitors.nix 19 | ./devices.nix 20 | ./portal.nix 21 | ./uwsm.nix 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /lib/hyprnix/ordering.nix: -------------------------------------------------------------------------------- 1 | lib: _: 2 | let 3 | inherit (lib.lists) findFirstIndex; 4 | inherit (lib.hyprnix.lists) elemsMatch; 5 | 6 | # Given a list of strings as `path` and a list of `patterns`, 7 | # return the index of the first most-specific match. 8 | # If no match is found, the resulting order is one more than the length of 9 | # `patterns`, positioning it last. 10 | orderOfPath = path: patterns: 11 | let 12 | exact = findFirstIndex (pattern: pattern == path) null patterns; 13 | inexact = findFirstIndex (pattern: elemsMatch pattern path) null patterns; 14 | in if exact != null then 15 | exact 16 | else if inexact != null then 17 | inexact 18 | else 19 | lib.length patterns; 20 | in { # # 21 | inherit orderOfPath; 22 | } 23 | -------------------------------------------------------------------------------- /hm-module/devices.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { config, pkgs, ... }: 3 | let 4 | cfg = config.wayland.windowManager.hyprland; 5 | 6 | hyprlang = pkgs.callPackage ./devicesFormat.nix { inherit lib; }; 7 | devicesFormat = hyprlang cfg.configFormatOptions; 8 | in { 9 | options = { 10 | wayland.windowManager.hyprland.deviceConfig = lib.mkOption { 11 | type = devicesFormat.type; 12 | default = { }; 13 | description = lib.mdDoc "\n"; 14 | example = lib.literalExpression "\n"; 15 | }; 16 | }; 17 | 18 | config = { 19 | wayland.windowManager.hyprland.configFile."devices.conf".text = 20 | devicesFormat.toConfigString cfg.deviceConfig; 21 | 22 | wayland.windowManager.hyprland.config.source = 23 | [ "${config.xdg.configHome}/hypr/devices.conf" ]; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /examples/npins/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "pins": { 3 | "home-manager": { 4 | "type": "Git", 5 | "repository": { 6 | "type": "GitHub", 7 | "owner": "nix-community", 8 | "repo": "home-manager" 9 | }, 10 | "branch": "master", 11 | "submodules": false, 12 | "revision": "c9d8158bc56923013fa9a4346ba3e273f3b956b3", 13 | "url": "https://github.com/nix-community/home-manager/archive/c9d8158bc56923013fa9a4346ba3e273f3b956b3.tar.gz", 14 | "hash": "1a320kg5scjsbj87jbw8yc350jc5ixmciwc57hs685sp36h37yai" 15 | }, 16 | "nixpkgs": { 17 | "type": "Channel", 18 | "name": "nixos-unstable", 19 | "url": "https://releases.nixos.org/nixos/unstable/nixos-25.11pre819493.4206c4cb5675/nixexprs.tar.xz", 20 | "hash": "0kx6y5gxdqh6izwxnhlkv348dq8l7bvwksj6pqijs5diwi4pvcy3" 21 | } 22 | }, 23 | "version": 5 24 | } 25 | -------------------------------------------------------------------------------- /tests/lib/hyprnix/lists.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | lib.bird.mkTestSuite { 3 | elemsMatch = let inherit (lib.hyprnix.lists) elemsMatch; 4 | in [ 5 | { 6 | name = "no match if list is longer than pattern"; 7 | expr = elemsMatch [ "a" "b" ] [ "a" "b" "c" ]; 8 | expect = false; 9 | } 10 | { 11 | name = "no match if pattern is longer than list"; 12 | expr = elemsMatch [ "a" "b" "c" ] [ "a" "b" ]; 13 | expect = false; 14 | } 15 | { 16 | name = "matches if identical strings"; 17 | expr = elemsMatch [ "foo" "bar" "baz" ] [ "foo" "bar" "baz" ]; 18 | expect = true; 19 | } 20 | { 21 | name = "matches if middle regex matches"; 22 | expr = lib.all (elemsMatch [ "foo" ".*" "baz" ]) [ 23 | [ "foo" "bar" "baz" ] 24 | [ "foo" "rab" "baz" ] 25 | ]; 26 | expect = true; 27 | } 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /hm-module/compat.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { config, pkgs, ... }: 3 | let 4 | inherit (lib) types; 5 | 6 | cfg = config.wayland.windowManager.hyprland; 7 | in { 8 | options = { 9 | wayland.windowManager.hyprland = { 10 | fufexan.enable = lib.mkOption { 11 | type = types.bool; 12 | default = false; 13 | description = '' 14 | Enable compatibility for Home Manager modules which depend on Fufexan's HM-native module. 15 | ''; 16 | }; 17 | 18 | settings.source = lib.mkOption { 19 | type = with types; listOf (either path package); 20 | default = [ ]; 21 | internal = true; 22 | visible = false; 23 | description = '' 24 | Please use {option}`wayland.windowManager.hyprland.config.source` instead! 25 | ''; 26 | }; 27 | }; 28 | }; 29 | config = lib.mkIf cfg.fufexan.enable { 30 | wayland.windowManager.hyprland.config.source = cfg.settings.source; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /tests/lib/hyprnix/ordering.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | lib.bird.mkTestSuite { 3 | orderOfPath = let inherit (lib.hyprnix.ordering) orderOfPath; 4 | in [ 5 | { 6 | name = "trivial ordering"; 7 | expr = let patterns = [ [ "a" ] [ "b" ] [ "c" ] ]; 8 | in map (path: orderOfPath path patterns) [ [ "c" ] [ "b" ] [ "a" ] ]; 9 | expect = [ 2 1 0 ]; 10 | } 11 | { 12 | name = "ordering by specificity"; 13 | expr = let patterns = [ [ "a" ".*" ] [ "a" "c" ] ]; 14 | in map (path: orderOfPath path patterns) [ 15 | [ "a" "c" ] 16 | [ "a" "b" ] 17 | [ "a" "a" ] 18 | ]; 19 | expect = [ 1 0 0 ]; 20 | } 21 | { 22 | name = "keep the first inexact match"; 23 | expr = let patterns = [ [ ".*" ] [ "b" ] [ ".*" ] ]; 24 | in orderOfPath [ "a" ] patterns; 25 | expect = 0; 26 | } 27 | { 28 | name = "no match will order last"; 29 | expr = let patterns = [ [ "b" ] [ "c" ] ]; 30 | in orderOfPath [ "a" ] patterns; 31 | expect = 2; 32 | } 33 | ]; 34 | } 35 | -------------------------------------------------------------------------------- /hm-module/devicesFormat.nix: -------------------------------------------------------------------------------- 1 | { lib, pkgs, ... }: 2 | formatOptions: 3 | let 4 | inherit (lib.hyprnix.hyprlang) mkSectionNode mkVariableNode attrsToNodeList; 5 | 6 | toConfigString = lib.generators.toHyprlang 7 | (formatOptions // { astBuilder = deviceAttrsToNodeList [ ]; }); 8 | 9 | deviceAttrsToNodeList = path: attrs: 10 | lib.mapAttrsToList (deviceName: deviceConfig: 11 | let 12 | nameNode = mkVariableNode [ "device" ] "name" deviceName; 13 | configNodes = attrsToNodeList [ "device" ] deviceConfig; 14 | sectionNodes = [ nameNode ] ++ configNodes; 15 | in mkSectionNode path "device" sectionNodes) attrs; 16 | in { 17 | # freeformType = types.attrsOf types.anything; 18 | # type = with lib.types; 19 | # let 20 | # valueType = oneOf [ bool number singleLineStr listOfValueTypes ]; 21 | # listOfValueTypes = listOf valueType; 22 | # in attrsOf valueType; 23 | type = with lib.types; attrsOf anything; 24 | 25 | lib = lib.hyprnix.hyprlang; 26 | 27 | inherit toConfigString; 28 | generate = name: value: pkgs.writeText name (toConfigString value); 29 | } 30 | -------------------------------------------------------------------------------- /hm-module/configFormat.nix: -------------------------------------------------------------------------------- 1 | # This file copies the style of: 2 | # 3 | { lib, pkgs, ... }: 4 | formatOptions@{ 5 | # Required ordering of attribute paths, required from `config`. 6 | configOrder, 7 | # The predicate for sorting nodes in the Hyprlang AST. 8 | # Returns `true` if `next` should be placed before `prev`, false otherwise. 9 | # A default `sortPred` is provided based on the `configOrder` list. 10 | sortPred ? prev: next: 11 | let 12 | inherit (lib.hyprnix.ordering) orderOfPath; 13 | prevOrder = orderOfPath prev configOrder; 14 | nextOrder = orderOfPath next configOrder; 15 | in nextOrder > prevOrder, 16 | # 17 | ... }: 18 | let 19 | toConfigString = lib.generators.toHyprlang 20 | (removeAttrs formatOptions [ "configOrder" ] // { inherit sortPred; }); 21 | in { 22 | # freeformType = types.attrsOf types.anything; 23 | type = with lib.types; 24 | let 25 | valueType = 26 | oneOf [ bool number singleLineStr attrsOfValueTypes listOfValueTypes ]; 27 | attrsOfValueTypes = attrsOf valueType; 28 | listOfValueTypes = listOf valueType; 29 | in attrsOfValueTypes; 30 | 31 | lib = lib.hyprnix.hyprlang; 32 | 33 | inherit toConfigString; 34 | generate = name: value: pkgs.writeText name (toConfigString value); 35 | } 36 | -------------------------------------------------------------------------------- /hm-module/uwsm.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { config, osConfig ? null, ... }: 3 | let 4 | cfg = config.programs.uwsm; 5 | 6 | hmSessionVars = 7 | "${config.home.sessionVariablesPackage}/etc/profile.d/hm-session-vars.sh"; 8 | in { 9 | options = { 10 | programs.uwsm = { 11 | env = lib.mkOption { 12 | type = lib.types.lines; 13 | default = ""; 14 | description = '' 15 | Shell script lines to write to `$XDG_CONFIG_HOME/uwsm/env` to bootstrap environment. 16 | ''; 17 | }; 18 | sourceSessionVariables = lib.mkOption { 19 | type = lib.types.bool; 20 | default = osConfig.programs.uwsm.enable or true; 21 | defaultText = '' 22 | Enabled by default if: 23 | - you are using Home Manager as a NixOS module and have `programs.uwsm.enable = true`, 24 | - or if Home Manager is running "standalone" (can't know if UWSM is enabled). 25 | ''; 26 | description = '' 27 | Whether to link Home Manager's session variables script 28 | (`hm-session-vars.sh`) to `$XDG_CONFIG_HOME/uwsm/env`. 29 | 30 | Disable this option if you do not use UWSM as a NixOS module. 31 | ''; 32 | }; 33 | }; 34 | }; 35 | 36 | config = lib.mkMerge [ 37 | (lib.mkIf (cfg.env != "") { xdg.configFile."uwsm/env".text = cfg.env; }) 38 | (lib.mkIf (cfg.env != "" && cfg.sourceSessionVariables) { 39 | xdg.configFile."uwsm/env".text = lib.mkOrder 200 '' 40 | source ${hmSessionVars} 41 | ''; 42 | }) 43 | (lib.mkIf (cfg.env == "" && cfg.sourceSessionVariables) { 44 | xdg.configFile."uwsm/env".source = hmSessionVars; 45 | }) 46 | ]; 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyprnix 2 | 3 | > **WORK IN PROGRESS** - Feel free to browse the source code, obscure things should have comments. 4 | 5 | This flake was `filter-repo`'d out from [spikespaz/dotfiles]. 6 | 7 | ~~We have yet to determine a permanent home for this code. 8 | See [this issue comment](https://github.com/hyprland-community/hyprnix/issues/1) 9 | for an explanation.~~ 10 | 11 | Endorsed by other Nix users, but I have to finish it. 12 | 13 | ## Usage 14 | 15 | Add the flake as an input to your own. 16 | 17 | ```nix 18 | { 19 | inputs = { 20 | hyprnix.url = "github:hyprland-community/hyprnix"; 21 | # ... 22 | }; 23 | # ... 24 | } 25 | ``` 26 | 27 | Assuming that you know Nix well enough to have your flake's `inputs` passed 28 | around to your Home Manager configuration, you can use the module in `imports` 29 | somewhere. 30 | 31 | ```nix 32 | { lib, pkgs, inputs, ... }: { 33 | imports = [ inputs.hyprnix.homeManagerModules.default ]; 34 | 35 | wayland.windowManager.hyprland = { 36 | enable = true; 37 | reloadConfig = true; 38 | systemdIntegration = true; 39 | # recommendedEnvironment = false; 40 | # nvidiaPatches = true; 41 | 42 | config = { 43 | # ... 44 | }; 45 | # ... 46 | }; 47 | # ... 48 | } 49 | ``` 50 | 51 | ## Documentation 52 | 53 | Because there is no documentation for module options yet, it is recommended to 54 | browse others' configurations as examples. 55 | 56 | - [@spikespaz/dotfiles](https://github.com/spikespaz/dotfiles/tree/master/users/jacob/hyprland) 57 | 58 | Remember that these are personal configurations, 59 | which is under constant revision, so it may be a mess at times. 60 | 61 | 62 | 63 | [hyprwm/hyprland]: https://github.com/hyprwm/hyprland 64 | [spikespaz/dotfiles]: https://github.com/spikespaz/dotfiles 65 | -------------------------------------------------------------------------------- /lib/hyprnix/types.nix: -------------------------------------------------------------------------------- 1 | lib: _: 2 | let 3 | inherit (lib) types; 4 | 5 | configFile = pkgs: basePath: 6 | types.submodule ({ config, name, ... }: { 7 | options = { 8 | target = lib.mkOption { 9 | type = types.singleLineStr; 10 | readOnly = true; 11 | description = lib.mdDoc '' 12 | The path of which to write {option}`source` or {option}`text`. 13 | 14 | This is always specified as the attribute name of this 15 | object in its parent attribute set. 16 | ''; 17 | }; 18 | source = lib.mkOption { 19 | type = types.path; 20 | default = null; 21 | description = lib.mdDoc '' 22 | If {option}`text` is not specified, this path will be used. 23 | 24 | If this is a directory, contents will be linked recursively. 25 | ''; 26 | }; 27 | text = lib.mkOption { 28 | type = types.nullOr types.lines; 29 | default = null; 30 | description = lib.mdDoc '' 31 | The text contents of the file. 32 | 33 | This option can be set mutiple times, 34 | and new text will be appended after previous lines. 35 | Use `lib.mkOrder` to ensure lines are written 36 | in the order you desire. 37 | 38 | This option takes precedence over {option}`source`. 39 | ''; 40 | }; 41 | executable = lib.mkOption { 42 | type = types.bool; 43 | default = false; 44 | description = lib.mdDoc '' 45 | If this file should be marked as executable. 46 | 47 | Useful for scripts used by the Hyprland config itself, 48 | for example keybinds using the `exec` dispatcher. 49 | 50 | Only works if {option}`text` is set. 51 | ''; 52 | }; 53 | }; 54 | config = { 55 | target = name; 56 | source = lib.mkIf (config.text != null) (pkgs.writeTextFile { 57 | name = "${basePath}/${config.target}"; 58 | destination = "/${config.target}"; 59 | inherit (config) text executable; 60 | }); 61 | }; 62 | }); 63 | in { # # 64 | inherit configFile; 65 | } 66 | -------------------------------------------------------------------------------- /examples/default.nix: -------------------------------------------------------------------------------- 1 | # This file is not to be considered example code. 2 | # It is responsible for instantiating the example modules, which are siblings 3 | # of this file, for the sake of using them as flake checks. 4 | # 5 | # `npins` is used to lock the inputs which are used to instantiate 6 | # a matrix of checks. Users should stick to the flake inputs interface. 7 | { system, hyprnix }: 8 | let 9 | # TODO: In the future, module checks should be instantiated for both 10 | # stable and unstable pairs of `nixpkgs` and `home-manager`. 11 | # Currently only the unstable branches of each is pinned, 12 | # because that is all that is currently used. 13 | sources = import ./npins; 14 | inherit (sources) nixpkgs; 15 | home-manager = import sources.home-manager { 16 | pkgs = import nixpkgs { localSystem.system = system; }; 17 | }; 18 | 19 | mkExampleHome = system: exampleModule: 20 | home-manager.lib.homeManagerConfiguration { 21 | modules = [ 22 | { 23 | home.stateVersion = "25.11"; 24 | home.username = "example"; 25 | home.homeDirectory = "/home/example"; 26 | } 27 | ({ lib, ... }: { 28 | imports = [ hyprnix.homeManagerModules.hyprland ]; 29 | wayland.windowManager.hyprland.enable = true; 30 | # This is specified to avoid building twice; these examples are meant 31 | # to verify functionality of the Home Manager module only. 32 | wayland.windowManager.hyprland.package = 33 | lib.mkDefault hyprnix.packages.${system}.hyprland; 34 | }) 35 | exampleModule 36 | ]; 37 | pkgs = import nixpkgs { 38 | localSystem.system = system; 39 | overlays = [ hyprnix.overlays.default ]; 40 | }; 41 | }; 42 | in { 43 | # This example has an empty module (`{ }`) because it is only intended to 44 | # check the generation of a minimal/default configuration. 45 | hyprland-enable = (mkExampleHome system { }).activationPackage; 46 | 47 | # For debugging purposes, 48 | # `nix build path:.#checks.x86_64-linux.example-vertical-monitors` may be of use. 49 | vertical-monitors = (mkExampleHome system ./vertical-monitors.nix) # # 50 | .config.wayland.windowManager.hyprland.configFile."hyprland.conf".source; 51 | } 52 | -------------------------------------------------------------------------------- /hm-module/keybindsFormat.nix: -------------------------------------------------------------------------------- 1 | { lib, pkgs, ... }: 2 | formatOptions: 3 | let 4 | inherit (lib.hyprnix.hyprlang) isRepeatNode mkVariableNode mkRepeatNode; 5 | 6 | toConfigString = lib.generators.toHyprlang (formatOptions // { 7 | astBuilder = keyBindsToNodeList [ ]; 8 | indentChars = ""; 9 | lineBreakPred = prev: next: 10 | let 11 | isSubmap = node: isRepeatNode prev && node.name == "submap"; 12 | betweenSubmaps = isSubmap prev && isSubmap next; 13 | betweenRepeats = isRepeatNode prev && isRepeatNode next; 14 | in prev != null && (betweenRepeats || betweenSubmaps); 15 | }); 16 | 17 | keyBindsToNodeList = path: attrs: 18 | let 19 | default = lib.pipe attrs [ 20 | (attrs: 21 | if attrs ? submap then removeAttrs attrs [ "submap" ] else attrs) 22 | (bindAttrsToNodeList [ ]) 23 | ]; 24 | submaps = lib.pipe attrs [ 25 | (attrs: if attrs ? submap then attrs.submap else { }) 26 | (lib.mapAttrs (name: bindAttrsToNodeList [ "submap" ])) 27 | (lib.mapAttrsToList (name: nodes: 28 | let 29 | nameNode = mkVariableNode [ "submap" name ] "submap" name; 30 | resetNode = mkVariableNode [ "submap" name ] "submap" "reset"; 31 | nodes' = [ nameNode ] ++ nodes ++ [ resetNode ]; 32 | in mkRepeatNode [ "submap" ] "submap" nodes')) 33 | ]; 34 | in lib.concatLists [ default submaps ]; 35 | 36 | bindAttrsToNodeList = path: 37 | (lib.mapAttrsToList (bindKw: chordAttrs: 38 | mkRepeatNode path bindKw (chordAttrsToNodeList path bindKw chordAttrs))); 39 | 40 | chordAttrsToNodeList = path: bindKw: attrs: 41 | lib.concatLists (lib.mapAttrsToList (chord: value: 42 | if lib.isList value then 43 | (map (dispatcher: mkVariableNode path bindKw "${chord}, ${dispatcher}") 44 | value) 45 | else 46 | [ (mkVariableNode path bindKw "${chord}, ${value}") ]) attrs); 47 | in { 48 | # freeformType = types.attrsOf types.anything; 49 | # type = with lib.types; 50 | # let 51 | # valueType = 52 | # oneOf [ bool number singleLineStr attrsOfValueTypes listOfValueTypes ]; 53 | # attrsOfValueTypes = attrsOf valueType; 54 | # listOfValueTypes = listOf valueType; 55 | # in attrsOfValueTypes; 56 | # TODO 57 | type = with lib.types; attrsOf anything; 58 | 59 | lib = lib.hyprnix.hyprlang; 60 | 61 | inherit toConfigString; 62 | generate = name: value: pkgs.writeText name (toConfigString value); 63 | } 64 | -------------------------------------------------------------------------------- /hm-module/keybinds.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { config, pkgs, ... }: 3 | let 4 | cfg = config.wayland.windowManager.hyprland; 5 | 6 | hyprlang = pkgs.callPackage ./keybindsFormat.nix { inherit lib; }; 7 | keybindsFormat = hyprlang cfg.configFormatOptions; 8 | in { 9 | options = { 10 | wayland.windowManager.hyprland.keyBinds = lib.mkOption { 11 | type = keybindsFormat.type; 12 | default = { }; 13 | description = lib.mdDoc '' 14 | First-level attribute name is the type of bind to use, 15 | for example: `bindm` for repeated mouse movements, 16 | or `bindr` to trigger on release. [See the wiki]. 17 | 18 | Second-level attribute name is a keychord in the form of `[MOD_KEYS],`, 19 | with the comma optionally followed by a space. 20 | 21 | Use the names from that header without the `XKB_KEY_` prefix here. 22 | 23 | Replace `[MOD_KEYS]` with a list of key names in UPPERCASE, 24 | separated by a space, underscore, or nothing. 25 | 26 | Replace `` with a single key name in lower snake case, 27 | or as it should appear in the [`keysyms` header][1]. 28 | 29 | For key names use the [`xkbcommon-keysms.h` header][1]. 30 | 31 | Second-level attribute value is a dispatcher command, 32 | either a string or a list. A list will be concatenated by commas. 33 | 34 | [0]: https://wiki.hyprland.org/Configuring/Binds/#basic] 35 | [1]: https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h 36 | ''; 37 | example = lib.literalExpression '' 38 | { 39 | bindm."SUPER, ''${MOUSE_LMB}" = "movewindow"; 40 | bindm."SUPER, ''${MOUSE_RMB}" = "resizewindow"; 41 | 42 | bindm.", ''${MOUSE_EX2}" = "movewindow"; 43 | bindm.", ''${MOUSE_EX1}" = "resizewindow"; 44 | 45 | bind."SUPER_SHIFT, left" = "movewindow, l"; 46 | bind."SUPER_SHIFT, right" = "movewindow, r"; 47 | bind."SUPER_SHIFT, up" = "movewindow, u"; 48 | bind."SUPER_SHIFT, down" = "movewindow, d"; 49 | 50 | bind."SUPER, slash" = "submap, resize"; 51 | submap.resize = { 52 | binde.", right" = "resizeactive, 10 0"; 53 | binde.", left" = "resizeactive, -10 0"; 54 | binde.", up" = "resizeactive, 0 -10"; 55 | binde.", down" = "resizeactive, 0 10"; 56 | binde."SHIFT, right" = "resizeactive, 30 0"; 57 | binde."SHIFT, left" = "resizeactive, -30 0"; 58 | binde."SHIFT, up" = "resizeactive, 0 -30"; 59 | binde."SHIFT, down" = "resizeactive, 0 30"; 60 | bind.", escape" = "submap, reset"; 61 | bind."CTRL, C" = "submap, reset"; 62 | }; 63 | } 64 | ''; 65 | }; 66 | }; 67 | 68 | config = lib.mkIf (cfg.keyBinds != null) { 69 | wayland.windowManager.hyprland.configFile."keybinds.conf".text = 70 | keybindsFormat.toConfigString cfg.keyBinds; 71 | 72 | wayland.windowManager.hyprland.config.source = 73 | [ "${config.xdg.configHome}/hypr/keybinds.conf" ]; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /examples/vertical-monitors.nix: -------------------------------------------------------------------------------- 1 | # This example shows how to configure the size and position of vertically 2 | # stacked monitors, taken from @spikespaz' personal configuration. 3 | { lib, config, ... }: { 4 | wayland.windowManager.hyprland.monitors = 5 | with config.wayland.windowManager.hyprland.monitors; { 6 | # This display is always present, since it's built in to the laptop. 7 | laptop-internal = { 8 | # Either `output` or `description` must be defined. 9 | # 10 | # You should probably prefer `description` because for most DRM output 11 | # nodes (for example `HDMI-A-1` or `DP-5`), hard-coded settings aren't 12 | # necessarily correct for any arbitrary monitor attached to that output. 13 | # 14 | # Your system probably also makes no guarantee that the name of the 15 | # DRM node is consistent between reboots or re-connections. 16 | # 17 | # Since this display is built-in, in this specific circumstance, 18 | # using `output = "eDP-1"` would also work fine. 19 | description = "Samsung Display Corp. 0x4193"; 20 | resolution = { 21 | x = 2880; 22 | y = 1800; 23 | }; 24 | # When positioning monitors, `size` is provided as a convenience. 25 | # For `laptop-internal`, it will be `{ x = 1920; y = 1200; }` 26 | # (that's `{ x = resolution.x / scale; y = resolution.y / scale; }`). 27 | scale = 1.5; 28 | refreshRate = 90; 29 | # The position of this monitor is calculated, taking into account its 30 | # own scaled size, and the scaled width and height of other monitors. 31 | # 32 | # The virtual `size` is not intended to be set explicitly. 33 | # 34 | # If the `transform` option causes a monitor to be rotated, the 35 | # `x` and `y` coordinates of `size` will be swapped for you. 36 | position = lib.mapAttrs (_: builtins.floor) { 37 | # Offset to be horizontally centered relative to `desktop-ultrawide`. 38 | x = desktop-ultrawide.position.x 39 | + (desktop-ultrawide.size.x - laptop-internal.size.x) / 2; 40 | # Vertically shifted down according to the position and scaled height 41 | # of `desktop-ultrawide`. 42 | y = desktop-ultrawide.position.y + desktop-ultrawide.size.y; 43 | }; 44 | bitdepth = 10; 45 | }; 46 | # This display is positioned above and center relative to 47 | # `laptop-internal`, in a vertical stack configuration. 48 | desktop-ultrawide = { 49 | description = "ASUSTek COMPUTER INC ASUS VG34V S8LMTF062111"; 50 | resolution = { 51 | x = 3440; 52 | y = 1440; 53 | }; 54 | refreshRate = 165; 55 | # This monitor's position (top-left corner) is at the origin point 56 | # of virtual screen space (also top-left corner). 57 | # The widest and top-most monitor gets the honor of being positioned 58 | # at the origin, because while negative coordinates are supported by 59 | # Hyprland, some popups and tooltips (specifically Qt) don't render 60 | # correctly if they're in a different quadrant from other monitors. 61 | position = { 62 | x = 0; 63 | y = 0; 64 | }; 65 | }; 66 | # Here is an entry that will handle arbitrary monitors, 67 | # setting the position to the right side of `laptop-internal`. 68 | default = { 69 | output = ""; 70 | resolution = "preferred"; 71 | position = lib.mapAttrs (_: builtins.floor) { 72 | x = laptop-internal.position.x + laptop-internal.size.x; 73 | y = laptop-internal.position.y; 74 | }; 75 | }; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = '' 3 | A Nix flake for the Hyprland window manager. 4 | 5 | ''; 6 | 7 | inputs = { 8 | nixpkgs.follows = "hyprland/nixpkgs"; 9 | 10 | # 11 | systems.url = "github:nix-systems/default-linux"; 12 | 13 | # 14 | hyprland = { 15 | url = "github:hyprwm/hyprland"; 16 | inputs.systems.follows = "systems"; 17 | }; 18 | 19 | # Extensions to `nixpkgs.lib` required by the Hyprlang serializer. 20 | # 21 | bird-nix-lib = { 22 | url = "github:spikespaz/bird-nix-lib"; 23 | inputs.systems.follows = "systems"; 24 | inputs.nixpkgs.follows = "nixpkgs"; 25 | }; 26 | }; 27 | 28 | outputs = { self, nixpkgs, systems, hyprland, bird-nix-lib }: 29 | let 30 | inherit (self) lib; 31 | 32 | eachSystem = lib.genAttrs (import systems); 33 | pkgsFor = 34 | eachSystem (system: import nixpkgs { localSystem.system = system; }); 35 | 36 | prefixAttrs = prefix: 37 | lib.mapAttrs' (name: value: { 38 | name = "${prefix}${name}"; 39 | inherit value; 40 | }); 41 | in { 42 | lib = let 43 | overlay = nixpkgs.lib.composeManyExtensions [ 44 | bird-nix-lib.lib.overlay 45 | (import ./lib) 46 | ]; 47 | in nixpkgs.lib.extend overlay // { inherit overlay; }; 48 | 49 | # Packages with the `-cross` suffix are removed, 50 | # prefer using `--all-systems` and providing remote builders which have `binfmt` configured. 51 | # For GitHub CI, use an `aarch64` runner. 52 | packages = lib.mapAttrs 53 | (name: lib.filterAttrs (name: _: !(lib.hasSuffix "-cross" name))) 54 | hyprland.packages; 55 | 56 | overlays = hyprland.overlays; 57 | 58 | homeManagerModules = { 59 | default = self.homeManagerModules.hyprland; 60 | hyprland = import ./hm-module self; 61 | }; 62 | 63 | checks = lib.mapAttrs (system: pkgs: 64 | let 65 | examples = import ./examples { 66 | inherit system; 67 | hyprnix = self; 68 | }; 69 | in self.packages.${system} // prefixAttrs "example-" examples // { 70 | check-formatting = let excludes = [ "examples/npins/default.nix" ]; 71 | in pkgs.stdenvNoCC.mkDerivation { 72 | name = "check-formatting"; 73 | src = ./.; 74 | phases = [ "checkPhase" "installPhase" ]; 75 | doCheck = true; 76 | nativeCheckInputs = [ pkgs.fd self.formatter.${system} ]; 77 | checkPhase = '' 78 | cd $src 79 | echo 'Checking Nix code formatting with Nixfmt:' 80 | fd --hidden --type file --extension nix ${ 81 | lib.concatMapStrings (path: " --exclude '${path}'") excludes 82 | } --exec nixfmt --check {} 83 | ''; 84 | installPhase = "touch $out"; 85 | }; 86 | }) pkgsFor; 87 | 88 | # $ nix flake check 89 | # or 90 | # $ nix eval 'path:.#tests' 91 | tests = lib.bird.runTestsRecursive ./tests { inherit lib; } { 92 | lib = { inherit (lib) hyprnix; }; 93 | }; 94 | 95 | devShells = lib.mapAttrs (system: pkgs: { 96 | default = pkgs.mkShellNoCC { # # 97 | packages = [ pkgs.npins self.formatter.${system} ]; 98 | }; 99 | }) pkgsFor; 100 | 101 | formatter = 102 | eachSystem (system: nixpkgs.legacyPackages.${system}.nixfmt-classic); 103 | 104 | # Should be kept in sync with upstream. 105 | # 106 | nixConfig = { 107 | extra-substituters = [ "https://hyprland.cachix.org" ]; 108 | extra-trusted-public-keys = [ 109 | "hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc=" 110 | ]; 111 | }; 112 | }; 113 | } 114 | -------------------------------------------------------------------------------- /hm-module/animations.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { config, ... }: 3 | let 4 | inherit (lib) types; 5 | 6 | cfg = config.wayland.windowManager.hyprland.animations; 7 | 8 | defaultBezierCurves = { 9 | linear = [ 0 0 1 1 ]; 10 | 11 | easeInSine = [ 0.12 0 0.39 0 ]; 12 | easeOutSine = [ 0.61 1 0.88 1 ]; 13 | easeInOutSine = [ 0.37 0 0.63 1 ]; 14 | 15 | easeInQuad = [ 0.11 0 0.5 0 ]; 16 | easeOutQuad = [ 0.5 1 0.89 1 ]; 17 | easeInOutQuad = [ 0.45 0 0.55 1 ]; 18 | 19 | easeInCubic = [ 0.32 0 0.67 0 ]; 20 | easeOutCubic = [ 0.33 1 0.68 1 ]; 21 | easeInOutCubic = [ 0.65 0 0.35 1 ]; 22 | 23 | easeInQuart = [ 0.5 0 0.75 0 ]; 24 | easeOutQuart = [ 0.25 1 0.5 1 ]; 25 | easeInOutQuart = [ 0.76 0 0.24 1 ]; 26 | 27 | easeInQuint = [ 0.64 0 0.78 0 ]; 28 | easeOutQuint = [ 0.22 1 0.36 1 ]; 29 | easeInOutQuint = [ 0.83 0 0.17 1 ]; 30 | 31 | easeInExpo = [ 0.7 0 0.84 0 ]; 32 | easeOutExpo = [ 0.16 1 0.3 1 ]; 33 | easeInOutExpo = [ 0.87 0 0.13 1 ]; 34 | 35 | easeInCirc = [ 0.55 0 1 0.45 ]; 36 | easeOutCirc = [ 0 0.55 0.45 1 ]; 37 | easeInOutCirc = [ 0.85 0 0.15 1 ]; 38 | 39 | easeInBack = [ 0.36 0 0.66 (-0.56) ]; 40 | easeOutBack = [ 0.34 1.56 0.64 1 ]; 41 | easeInOutBack = [ 0.68 (-0.6) 0.32 1.6 ]; 42 | }; 43 | in { 44 | options = { 45 | wayland.windowManager.hyprland.animations = { 46 | animation = lib.mkOption { 47 | type = types.attrsOf (types.submodule ({ name, ... }: { 48 | options = { 49 | event = lib.mkOption { 50 | type = types.singleLineStr; 51 | default = name; 52 | }; 53 | enable = lib.mkOption { 54 | type = types.bool; 55 | default = true; 56 | }; 57 | duration = lib.mkOption { type = types.ints.positive; }; 58 | curve = lib.mkOption { 59 | type = types.enum 60 | ((builtins.attrNames cfg.bezierCurve) ++ [ "default" ]); 61 | default = "default"; 62 | }; 63 | style = lib.mkOption { 64 | type = types.nullOr types.singleLineStr; 65 | default = null; 66 | }; 67 | }; 68 | })); 69 | default = { }; 70 | description = lib.mdDoc '' 71 | An attribute set of animations rules. 72 | ''; 73 | example = lib.literalExpression '' 74 | { 75 | workspaces = { 76 | effect = "slide"; 77 | enable = true; 78 | duration = 8; 79 | curve = "default"; 80 | } 81 | } 82 | ''; 83 | }; 84 | 85 | defaultBezierCurves = lib.mkOption { 86 | type = 87 | types.attrsOf (types.listOf (types.oneOf [ types.float types.int ])); 88 | readOnly = true; 89 | default = defaultBezierCurves; 90 | defaultText = lib.literalExpression 91 | (lib.generators.toPretty { newlines = true; } defaultBezierCurves); 92 | description = lib.mdDoc '' 93 | Read-only value containing an attrset of bezier curves to be included in 94 | the `bezierCurve` option by default. 95 | 96 | Curves are copied from . 97 | ''; 98 | }; 99 | 100 | bezierCurve = lib.mkOption { 101 | type = 102 | types.attrsOf (types.listOf (types.oneOf [ types.float types.int ])); 103 | default = defaultBezierCurves; 104 | description = lib.mdDoc '' 105 | An attribute set of bezier curves. 106 | ''; 107 | example = lib.literalExpression '' 108 | { 109 | easeInSine = [0.12 0 0.39 0]; 110 | easeOutSine = [0.61 1 0.88 1]; 111 | easeInOutSine = [0.37 0 0.63 1]; 112 | 113 | easeInQuad = [0.11 0 0.5 0]; 114 | easeOutQuad = [0.5 1 0.89 1]; 115 | easeInOutQuad = [0.45 0 0.55 1]; 116 | } 117 | ''; 118 | }; 119 | }; 120 | }; 121 | 122 | config = let 123 | millisToDecis = x: x / 100; 124 | 125 | stringifyAnimation = 126 | { event, enable ? true, duration, curve ? "default", style ? null, }: 127 | "${event}, ${if enable then "1" else "0"}, ${ 128 | toString (millisToDecis duration) 129 | }, ${curve}${lib.optionalString (style != null) ", ${style}"}"; 130 | 131 | stringifyBezier = name: points: 132 | "${name}, ${lib.concatStringsSep ", " (map toString points)}"; 133 | in { 134 | wayland.windowManager.hyprland.config.animations = { 135 | bezier = lib.mapAttrsToList stringifyBezier cfg.bezierCurve; 136 | animation = lib.mapAttrsToList (_: stringifyAnimation) cfg.animation; 137 | }; 138 | }; 139 | } 140 | -------------------------------------------------------------------------------- /hm-module/portal.nix: -------------------------------------------------------------------------------- 1 | { self, lib, ... }: 2 | { config, pkgs, ... }: 3 | let 4 | inherit (lib) types; 5 | 6 | cfg = config.wayland.windowManager.hyprland.portal; 7 | 8 | # If the overlay is applied to Nixpkgs, `xdg-desktop-portal-hyprland` in 9 | # `pkgs` is probably newer. Otherwise, the package provided by the flake is 10 | # guaranteed to be newer. 11 | defaultPackage = let 12 | inNixpkgs = pkgs.xdg-desktop-portal-hyprland.version; 13 | inFlake = self.packages.${pkgs.system}.xdg-desktop-portal-hyprland.version; 14 | in if lib.versionOlder inNixpkgs inFlake then 15 | pkgs.xdg-desktop-portal-hyprland 16 | else 17 | self.packages.${pkgs.system}.xdg-desktop-portal-hyprland; 18 | in { 19 | options = { 20 | wayland.windowManager.hyprland.portal = { 21 | enable = lib.mkOption { 22 | type = types.bool; 23 | example = true; 24 | description = '' 25 | Whether to enable {option}`xdg.portal.enable` and configure 26 | to use `xdg-desktop-portal-hyprland`. 27 | 28 | ::: {.note} 29 | Enabling this option only configures XDPH, but does not 30 | change {option}`xdg.portal.config` or add any fallback implementations 31 | to {option}`xdg.portal.extraPortals`. Thus, you still need to configure 32 | other portals to handle other interfaces such as 33 | `org.freedesktop.impl.portal.FileChooser`. 34 | ::: 35 | 36 | See {manpage}`portals.conf(5)` and . 37 | ''; 38 | }; 39 | 40 | package = lib.mkOption { 41 | type = types.package; 42 | default = defaultPackage; 43 | defaultText = '' 44 | The package with the highest version number, chosen from: 45 | - `pkgs.xdg-desktop-portal-hyprland` (if the overlay is used, always this) 46 | - `self.packages.''${pkgs.system}.xdg-desktop-portal-hyprland`, from the Hyprnix flake. 47 | ''; 48 | example = lib.literalExpression '' 49 | pkgs.xdg-desktop-portal-hyprland # if you use the overlay 50 | ''; 51 | description = '' 52 | The XDPH package to use. This package's `hyprland` input will be overridden 53 | with {option}`wayland.windowManager.hyprland.finalPackage` to ensure 54 | that the wrapper will add the correct version of `hyprctl` to `PATH`. 55 | ''; 56 | }; 57 | 58 | finalPackage = lib.mkOption { 59 | type = types.package; 60 | readOnly = true; 61 | description = '' 62 | The final XDPH package to install, with necessary overrides applied. 63 | ''; 64 | }; 65 | 66 | config = lib.mkOption { 67 | # If this ever gets more complicated, just instantiate `configFormat.nix`. 68 | type = with types; 69 | let 70 | valueType = oneOf [ bool number singleLineStr attrsOfValueTypes ]; 71 | attrsOfValueTypes = attrsOf valueType; 72 | in attrsOfValueTypes; 73 | default = { }; 74 | description = '' 75 | XDPH configuration attributes. 76 | 77 | This will be serialized to Hyprlang at 78 | {path}`$XDG_CONFIG_HOME/hypr/xdph.conf`. 79 | 80 | ::: {.note} 81 | The configuration file will be generated as long as this option 82 | has been set to some meaningful value. It is not dependent upon 83 | {option}`wayland.windowManager.hyprland.portal.enable`. 84 | ::: 85 | 86 | For available options, see . 87 | ''; 88 | }; 89 | }; 90 | }; 91 | 92 | config = lib.mkMerge [ 93 | { 94 | # If the Hyprland package is `null`, it is assumed that the user is configuring 95 | # things using NixOS options (discouraged). We leave it up to them whether 96 | # they want Home Manager to configure the portal implementations. 97 | wayland.windowManager.hyprland.portal.enable = 98 | lib.mkDefault (config.wayland.windowManager.hyprland.package != null); 99 | 100 | wayland.windowManager.hyprland.portal.finalPackage = 101 | cfg.package.override { 102 | hyprland = config.wayland.windowManager.hyprland.finalPackage; 103 | }; 104 | } 105 | (lib.mkIf cfg.enable { 106 | xdg.portal = { 107 | enable = lib.mkDefault true; 108 | extraPortals = [ cfg.finalPackage ]; 109 | configPackages = [ config.wayland.windowManager.hyprland.finalPackage ]; 110 | }; 111 | }) 112 | (lib.mkIf (cfg.config != { }) { 113 | wayland.windowManager.hyprland.configFile."xdph.conf".text = 114 | lib.generators.toHyprlang 115 | config.wayland.windowManager.hyprland.configFormatOptions cfg.config; 116 | }) 117 | ]; 118 | } 119 | -------------------------------------------------------------------------------- /examples/npins/default.nix: -------------------------------------------------------------------------------- 1 | /* 2 | This file is provided under the MIT licence: 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | # Generated by npins. Do not modify; will be overwritten regularly 11 | let 12 | data = builtins.fromJSON (builtins.readFile ./sources.json); 13 | version = data.version; 14 | 15 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 16 | range = 17 | first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); 18 | 19 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 20 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 21 | 22 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 23 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 24 | concatMapStrings = f: list: concatStrings (map f list); 25 | concatStrings = builtins.concatStringsSep ""; 26 | 27 | # If the environment variable NPINS_OVERRIDE_${name} is set, then use 28 | # the path directly as opposed to the fetched source. 29 | # (Taken from Niv for compatibility) 30 | mayOverride = 31 | name: path: 32 | let 33 | envVarName = "NPINS_OVERRIDE_${saneName}"; 34 | saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; 35 | ersatz = builtins.getEnv envVarName; 36 | in 37 | if ersatz == "" then 38 | path 39 | else 40 | # this turns the string into an actual Nix path (for both absolute and 41 | # relative paths) 42 | builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" ( 43 | if builtins.substring 0 1 ersatz == "/" then 44 | /. + ersatz 45 | else 46 | /. + builtins.getEnv "PWD" + "/${ersatz}" 47 | ); 48 | 49 | mkSource = 50 | name: spec: 51 | assert spec ? type; 52 | let 53 | path = 54 | if spec.type == "Git" then 55 | mkGitSource spec 56 | else if spec.type == "GitRelease" then 57 | mkGitSource spec 58 | else if spec.type == "PyPi" then 59 | mkPyPiSource spec 60 | else if spec.type == "Channel" then 61 | mkChannelSource spec 62 | else if spec.type == "Tarball" then 63 | mkTarballSource spec 64 | else 65 | builtins.throw "Unknown source type ${spec.type}"; 66 | in 67 | spec // { outPath = mayOverride name path; }; 68 | 69 | mkGitSource = 70 | { 71 | repository, 72 | revision, 73 | url ? null, 74 | submodules, 75 | hash, 76 | branch ? null, 77 | ... 78 | }: 79 | assert repository ? type; 80 | # At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository 81 | # In the latter case, there we will always be an url to the tarball 82 | if url != null && !submodules then 83 | builtins.fetchTarball { 84 | inherit url; 85 | sha256 = hash; # FIXME: check nix version & use SRI hashes 86 | } 87 | else 88 | let 89 | url = 90 | if repository.type == "Git" then 91 | repository.url 92 | else if repository.type == "GitHub" then 93 | "https://github.com/${repository.owner}/${repository.repo}.git" 94 | else if repository.type == "GitLab" then 95 | "${repository.server}/${repository.repo_path}.git" 96 | else 97 | throw "Unrecognized repository type ${repository.type}"; 98 | urlToName = 99 | url: rev: 100 | let 101 | matched = builtins.match "^.*/([^/]*)(\\.git)?$" url; 102 | 103 | short = builtins.substring 0 7 rev; 104 | 105 | appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else ""; 106 | in 107 | "${if matched == null then "source" else builtins.head matched}${appendShort}"; 108 | name = urlToName url revision; 109 | in 110 | builtins.fetchGit { 111 | rev = revision; 112 | inherit name; 113 | # hash = hash; 114 | inherit url submodules; 115 | }; 116 | 117 | mkPyPiSource = 118 | { url, hash, ... }: 119 | builtins.fetchurl { 120 | inherit url; 121 | sha256 = hash; 122 | }; 123 | 124 | mkChannelSource = 125 | { url, hash, ... }: 126 | builtins.fetchTarball { 127 | inherit url; 128 | sha256 = hash; 129 | }; 130 | 131 | mkTarballSource = 132 | { 133 | url, 134 | locked_url ? url, 135 | hash, 136 | ... 137 | }: 138 | builtins.fetchTarball { 139 | url = locked_url; 140 | sha256 = hash; 141 | }; 142 | in 143 | if version == 5 then 144 | builtins.mapAttrs mkSource data.pins 145 | else 146 | throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`" 147 | -------------------------------------------------------------------------------- /hm-module/environment.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { config, pkgs, ... }: 3 | let 4 | inherit (lib) types; 5 | 6 | cfg = config.wayland.windowManager.hyprland; 7 | in { 8 | imports = [ 9 | (lib.mkRenamedOptionModule # \ 10 | [ "wayland" "windowManager" "hyprland" "systemdIntegration" ] # \ 11 | [ "wayland" "windowManager" "hyprland" "systemd" "enable" ]) 12 | ]; 13 | 14 | options = { 15 | wayland.windowManager.hyprland = { 16 | systemd = { 17 | enable = lib.mkEnableOption null // { 18 | default = true; 19 | description = '' 20 | Whether to enable {file}`hyprland-session.target` on 21 | hyprland startup. This links to {file}`graphical-session.target`. 22 | Some important environment variables will be imported to systemd 23 | and D-Bus user environment before reaching the target, including: 24 | - {env}`DISPLAY` 25 | - {env}`HYPRLAND_INSTANCE_SIGNATURE` 26 | - {env}`WAYLAND_DISPLAY` 27 | - {env}`XDG_CURRENT_DESKTOP` 28 | ''; 29 | }; 30 | 31 | variables = lib.mkOption { 32 | type = with lib.types; listOf singleLineStr; 33 | default = [ ]; 34 | example = [ "--all" ]; 35 | description = '' 36 | Names of environment variables to be exported for the systemd 37 | user environment and D-Bus session services. 38 | 39 | Note that this option is provided for compatibility with the 40 | default Hyprland Home Manager module, and only has effect if 41 | {option}`wayland.windowManager.hyprland.enable` is `true`. 42 | 43 | Variables just for the D-Bus session services should be set by 44 | {option}`wayland.windowManager.hyprland.dbusEnvironment` and 45 | {option}`wayland.windowManager.hyprland.extraDbusEnvironment`. 46 | ''; 47 | }; 48 | 49 | extraCommands = lib.mkOption { 50 | type = with lib.types; listOf str; 51 | default = [ 52 | "systemctl --user stop hyprland-session.target" 53 | "systemctl --user start hyprland-session.target" 54 | ]; 55 | description = "Extra commands to be run after D-Bus activation."; 56 | }; 57 | }; 58 | 59 | environment = lib.mkOption { 60 | type = with types; lazyAttrsOf (oneOf [ str path int float ]); 61 | default = { }; 62 | description = lib.mdDoc '' 63 | Set environment variables for the Hyprland session, 64 | similar to {option}`home.sessionVariables`. 65 | 66 | This is a convenience option that sets 67 | {option}`wayland.windowManager.hyprland.config.env`. 68 | 69 | Environment variables here are not used in any session other 70 | than Hyprland. 71 | ''; 72 | }; 73 | 74 | recommendedEnvironment = lib.mkOption { 75 | type = types.bool; 76 | default = pkgs.stdenv.isLinux; 77 | description = lib.mdDoc '' 78 | Whether to set some recommended environment variables. 79 | 80 | These are specific to the Hyprland session and are not exported 81 | through {option}`home.sessionVariables`. 82 | 83 | This is because those variables would be used by all sessions, 84 | graphical or not, no matter the specific window manager. 85 | ''; 86 | }; 87 | 88 | dbusEnvironment = lib.mkOption { 89 | type = types.listOf types.singleLineStr; 90 | default = [ 91 | "DISPLAY" 92 | "WAYLAND_DISPLAY" 93 | "HYPRLAND_INSTANCE_SIGNATURE" 94 | "XDG_CURRENT_DESKTOP" 95 | ]; 96 | description = lib.mkDoc '' 97 | Names of environment variables to be exported for 98 | all D-Bus session services. 99 | 100 | These variables will also be exported for the systemd user session 101 | if {option}`wayland.windowManager.hyprland.systemd.enable` 102 | is `true`. 103 | ''; 104 | }; 105 | 106 | extraDbusEnvironment = lib.mkOption { 107 | type = types.listOf types.singleLineStr; 108 | default = [ ]; 109 | description = lib.mdDoc '' 110 | Extra names of environment variables to be added to 111 | {option}`wayland.windowManager.hyprland.dbusEnvironment`. 112 | 113 | It is recommended to use this option instead of modifying 114 | the option mentioned above. 115 | ''; 116 | }; 117 | }; 118 | }; 119 | 120 | config = let 121 | dbusVariables = cfg.dbusEnvironment ++ cfg.extraDbusEnvironment; 122 | systemdVariables = dbusVariables ++ cfg.systemd.variables; 123 | in lib.mkMerge [ 124 | { 125 | wayland.windowManager.hyprland.config.exec_once = lib.mkOrder 10 [ 126 | "${pkgs.dbus}/bin/dbus-update-activation-environment ${ 127 | lib.concatStringsSep " " dbusVariables 128 | }" 129 | ]; 130 | 131 | wayland.windowManager.hyprland.config.env = 132 | lib.mapAttrsToList (name: value: "${name},${toString value}") 133 | cfg.environment; 134 | } 135 | 136 | (lib.mkIf cfg.systemd.enable { 137 | systemd.user.targets.hyprland-session = { 138 | Unit = { 139 | Description = "hyprland compositor session"; 140 | Documentation = [ "man:systemd.special(7)" ]; 141 | BindsTo = [ "graphical-session.target" ]; 142 | Wants = [ "graphical-session-pre.target" ]; 143 | After = [ "graphical-session-pre.target" ]; 144 | }; 145 | }; 146 | 147 | wayland.windowManager.hyprland.config.exec_once = lib.mkOrder 11 ([ 148 | "${pkgs.dbus}/bin/dbus-update-activation-environment ${ 149 | lib.concatStringsSep " " ([ "--systemd" ] ++ systemdVariables) 150 | }" 151 | ] ++ cfg.systemd.extraCommands); 152 | }) 153 | 154 | (lib.mkIf cfg.recommendedEnvironment { 155 | wayland.windowManager.hyprland.environment = { NIXOS_OZONE_WL = 1; }; 156 | }) 157 | ]; 158 | } 159 | -------------------------------------------------------------------------------- /hm-module/configRenames.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | let 3 | mkPathValue = path: name: value: { 4 | path = path ++ [ name ]; 5 | inherit value; 6 | }; 7 | 8 | # Turn a recursive attrset into a list of 9 | # `{ path = [...]; value = ...; }` where `path` and `value` are analogous 10 | # to a name value pair. 11 | attrsToPathValueList = let 12 | recurse = path: attrs: 13 | lib.flatten (lib.mapAttrsToList (name: value: 14 | if lib.isAttrs value then 15 | (recurse (path ++ [ name ]) value) 16 | else 17 | mkPathValue path name value) attrs); 18 | in recurse [ ]; 19 | 20 | # Inverse operation for `attrsToPathValueList`. 21 | pathValueListToAttrs = lib.foldl' (acc: attr: 22 | lib.recursiveUpdate acc (lib.setAttrByPath attr.path attr.value)) { }; 23 | 24 | # Given two lists, `from` and `two` where both is a list of 25 | # attrpaths (list of keys), and an attrset. 26 | # Create a new attrset where every value corresponding to a path listed in 27 | # the `from` list is transposed to a new attribute path provided by 28 | # the value at the corresponding index of `to`. 29 | renameAttrs = from: to: attrs: 30 | lib.throwIfNot (builtins.length from == builtins.length to) '' 31 | expected renameAttrs from and to params to be same length 32 | '' (lib.pipe attrs [ 33 | attrsToPathValueList 34 | (map (attr: 35 | let idx = lib.indexOf attr.path from; 36 | in if idx == null then 37 | attr 38 | else { 39 | path = builtins.elemAt to idx; 40 | inherit (attr) value; 41 | })) 42 | pathValueListToAttrs 43 | ]); 44 | in { 45 | inherit renameAttrs; 46 | 47 | renames = (l: { 48 | from = lib.catAttrs "prefer" l; 49 | to = lib.catAttrs "original" l; 50 | }) [ 51 | { 52 | prefer = [ "exec_once" ]; 53 | original = [ "exec-once" ]; 54 | } 55 | { 56 | prefer = [ "general" "gaps_inside" ]; 57 | original = [ "general" "gaps_in" ]; 58 | } 59 | { 60 | prefer = [ "general" "gaps_outside" ]; 61 | original = [ "general" "gaps_out" ]; 62 | } 63 | { 64 | prefer = [ "general" "active_border_color" ]; 65 | original = [ "general" "col.active_border" ]; 66 | } 67 | { 68 | prefer = [ "general" "inactive_border_color" ]; 69 | original = [ "general" "col.inactive_border" ]; 70 | } 71 | { 72 | prefer = [ "general" "active_group_border_color" ]; 73 | original = [ "general" "col.group_border_active" ]; 74 | } 75 | { 76 | prefer = [ "general" "inactive_group_border_color" ]; 77 | original = [ "general" "col.group_border" ]; 78 | } 79 | { 80 | prefer = [ "decoration" "active_shadow_color" ]; 81 | original = [ "decoration" "col.shadow" ]; 82 | } 83 | { 84 | prefer = [ "decoration" "inactive_shadow_color" ]; 85 | original = [ "decoration" "col.shadow_inactive" ]; 86 | } 87 | { 88 | prefer = [ "decoration" "shadow" "inactive_color" ]; 89 | original = [ "decoration" "shadow" "color_inactive" ]; 90 | } 91 | { 92 | prefer = [ "input" "touchpad" "tap_to_click" ]; 93 | original = [ "input" "touchpad" "tap-to-click" ]; 94 | } 95 | { 96 | prefer = [ "input" "touchpad" "tap_and_drag" ]; 97 | original = [ "input" "touchpad" "tap-and-drag" ]; 98 | } 99 | { 100 | prefer = [ "gestures" "workspace_swipe" "enable" ]; 101 | original = [ "gestures" "workspace_swipe" ]; 102 | } 103 | { 104 | prefer = [ "gestures" "workspace_swipe" "fingers" ]; 105 | original = [ "gestures" "workspace_swipe_fingers" ]; 106 | } 107 | { 108 | prefer = [ "gestures" "workspace_swipe" "distance" ]; 109 | original = [ "gestures" "workspace_swipe_distance" ]; 110 | } 111 | { 112 | prefer = [ "gestures" "workspace_swipe" "invert" ]; 113 | original = [ "gestures" "workspace_swipe_invert" ]; 114 | } 115 | { 116 | prefer = [ "gestures" "workspace_swipe" "min_speed_to_force" ]; 117 | original = [ "gestures" "workspace_swipe_min_speed_to_force" ]; 118 | } 119 | { 120 | prefer = [ "gestures" "workspace_swipe" "cancel_ratio" ]; 121 | original = [ "gestures" "workspace_swipe_cancel_ratio" ]; 122 | } 123 | { 124 | prefer = [ "gestures" "workspace_swipe" "create_new" ]; 125 | original = [ "gestures" "workspace_swipe_create_new" ]; 126 | } 127 | { 128 | prefer = [ "gestures" "workspace_swipe" "direction_lock" ]; 129 | original = [ "gestures" "workspace_swipe_direction_lock" ]; 130 | } 131 | { 132 | prefer = [ "gestures" "workspace_swipe" "direction_lock_threshold" ]; 133 | original = [ "gestures" "workspace_swipe_direction_lock_threshold" ]; 134 | } 135 | { 136 | prefer = [ "gestures" "workspace_swipe" "forever" ]; 137 | original = [ "gestures" "workspace_swipe_forever" ]; 138 | } 139 | { 140 | prefer = [ "gestures" "workspace_swipe" "numbered" ]; 141 | original = [ "gestures" "workspace_swipe_numbered" ]; 142 | } 143 | { 144 | prefer = [ "gestures" "workspace_swipe" "use_r" ]; 145 | original = [ "gestures" "workspace_swipe_use_r" ]; 146 | } 147 | { 148 | prefer = [ "gestures" "workspace_swipe" "through_empty" ]; 149 | original = [ "gestures" "workspace_swipe_use_r" ]; 150 | } 151 | { 152 | prefer = [ "group" "active_border_color" ]; 153 | original = [ "group" "col.border_active" ]; 154 | } 155 | { 156 | prefer = [ "group" "inactive_border_color" ]; 157 | original = [ "group" "col.border_inactive" ]; 158 | } 159 | { 160 | prefer = [ "group" "locked_active_border_color" ]; 161 | original = [ "group" "col.border_locked_active" ]; 162 | } 163 | { 164 | prefer = [ "group" "locked_inactive_border_color" ]; 165 | original = [ "group" "col.border_locked_inactive" ]; 166 | } 167 | { 168 | prefer = [ "group" "groupbar" "active_color" ]; 169 | original = [ "group" "groupbar" "col.active" ]; 170 | } 171 | { 172 | prefer = [ "group" "groupbar" "inactive_color" ]; 173 | original = [ "group" "groupbar" "col.inactive" ]; 174 | } 175 | { 176 | prefer = [ "group" "groupbar" "locked_active_color" ]; 177 | original = [ "group" "groupbar" "col.locked_active" ]; 178 | } 179 | { 180 | prefer = [ "group" "groupbar" "locked_inactive_color" ]; 181 | original = [ "group" "groupbar" "col.locked_inactive" ]; 182 | } 183 | { 184 | prefer = [ "misc" "variable_framerate" ]; 185 | original = [ "misc" "vfr" ]; 186 | } 187 | { 188 | prefer = [ "misc" "variable_refresh" ]; 189 | original = [ "misc" "vrr" ]; 190 | } 191 | ]; 192 | } 193 | -------------------------------------------------------------------------------- /lib/hyprnix/hyprlang.nix: -------------------------------------------------------------------------------- 1 | lib: _: 2 | let 3 | toConfigString = { 4 | # If a custom Nix structure is desired, the parser may be replaced. 5 | astBuilder ? attrsToNodeList [ ], 6 | # Given two attribute paths, return `true` if the 7 | # first should precede the second. 8 | sortPred ? _: _: false, 9 | # String to use for indentation characters. 10 | indentChars ? " ", 11 | # Given two nodes (from the AST) return `true` if a line-break 12 | # should be inserted between them. 13 | lineBreakPred ? prev: next: 14 | let 15 | betweenDifferent = nodeType prev != nodeType next; 16 | betweenRepeats = isRepeatNode prev && isRepeatNode next; 17 | betweenSections = isSectionNode prev && isSectionNode next; 18 | in prev != null && (betweenDifferent || betweenRepeats || betweenSections) 19 | , 20 | # Whether the output should be formatted with spaces around 21 | # the `=` character in a keyword assignment. 22 | spaceAroundEquals ? true, 23 | # 24 | }: 25 | attrs: 26 | lib.pipe attrs [ 27 | astBuilder 28 | pruneEmptyNodesRecursive 29 | (sortNodeListRecursive sortPred) 30 | (insertLineBreakNodesRecursive lineBreakPred) 31 | (insertIndentNodesRecursive indentChars) 32 | (renderNodeList { inherit indentChars spaceAroundEquals; }) 33 | ]; 34 | 35 | toPrettyM = lib.generators.toPretty { multiline = true; }; 36 | 37 | renderNodeList = opts: nodes: lib.concatStrings (map (renderNode opts) nodes); 38 | 39 | isNode = node: lib.isAttrs node && node ? _node_type; 40 | isNodeType = type: node: isNode node && node._node_type == type; 41 | isStringNode = isNodeType "string"; 42 | isIndentNode = isNodeType "indent"; 43 | isVariableNode = isNodeType "variable"; 44 | isRepeatNode = isNodeType "repeatBlock"; 45 | isSectionNode = isNodeType "configDocument"; 46 | 47 | mkNodeType = type: path: name: value: { 48 | _node_type = type; 49 | inherit name value; 50 | path = path ++ [ name ]; 51 | }; 52 | mkStringNode = mkNodeType "string"; 53 | mkIndentNode = mkNodeType "indent"; 54 | mkVariableNode = mkNodeType "variable"; 55 | mkRepeatNode = mkNodeType "repeatBlock"; 56 | mkSectionNode = mkNodeType "configDocument"; 57 | 58 | nodeType = builtins.getAttr "_node_type"; 59 | mapValue = fn: node: node // { value = fn node.value; }; 60 | 61 | # concatListsSep = sep: lib.foldl' (a: b: a ++ [sep] ++ b) []; 62 | 63 | attrsToNodeList = path: attrs: 64 | let 65 | variables = lib.pipe attrs [ 66 | (lib.filterAttrs (_: v: !(lib.isAttrs v || lib.isList v))) 67 | (lib.mapAttrsToList (mkVariableNode path)) 68 | ]; 69 | repeats = lib.pipe attrs [ 70 | (lib.filterAttrs (_: lib.isList)) 71 | (lib.mapAttrsToList (name: values: 72 | mkRepeatNode path name (map (value: 73 | if lib.isAttrs value then 74 | mkSectionNode path name (attrsToNodeList (path ++ [ name ]) value) 75 | else 76 | mkVariableNode path name value) values))) 77 | ]; 78 | sections = lib.pipe attrs [ 79 | (lib.filterAttrs (_: lib.isAttrs)) 80 | (lib.mapAttrsToList (name: value: 81 | mkSectionNode path name (attrsToNodeList (path ++ [ name ]) value))) 82 | ]; 83 | in lib.concatLists [ variables repeats sections ]; 84 | 85 | pruneEmptyNodesRecursive = lib.foldl' (nodes: next: 86 | let 87 | next' = if isRepeatNode next || isSectionNode next then 88 | mapValue pruneEmptyNodesRecursive next 89 | else 90 | next; 91 | in if next'.value == [ ] then nodes else nodes ++ [ next' ]) [ ]; 92 | 93 | sortNodeListRecursive = sortPred: 94 | let 95 | recurse = l: 96 | lib.pipe l [ 97 | (map (node: 98 | if isRepeatNode node || isSectionNode node then 99 | mapValue recurse node 100 | else 101 | node)) 102 | (lib.sort (a: b: sortPred a.path b.path)) 103 | ]; 104 | in recurse; 105 | 106 | insertLineBreakNodesRecursive = breakPred: 107 | let 108 | recurse = lib.foldl' (nodes: next: 109 | let 110 | prev = if nodes == [ ] then 111 | null 112 | else 113 | builtins.elemAt nodes (builtins.length nodes - 1); 114 | next' = if isRepeatNode next || isSectionNode next then 115 | mapValue recurse next 116 | else 117 | next; 118 | newline = mkStringNode next.path "newline" "\n"; 119 | in if breakPred prev next' then 120 | nodes ++ [ newline ] ++ [ next' ] 121 | else 122 | nodes ++ [ next' ]) [ ]; 123 | in recurse; 124 | 125 | insertIndentNodesRecursive = indentChars: 126 | let 127 | recurse = lib.foldl' (nodes: next: 128 | let 129 | level = builtins.length next.path - 1; 130 | indent = mkIndentNode next.path "indent" level; 131 | in if isVariableNode next then 132 | nodes ++ [ indent ] ++ [ next ] 133 | else if isRepeatNode next then 134 | nodes ++ [ (mapValue recurse next) ] 135 | else if isSectionNode next then 136 | nodes ++ [ indent (mapValue (v: (recurse v) ++ [ indent ]) next) ] 137 | else 138 | nodes ++ [ next ]) [ ]; 139 | in recurse; 140 | 141 | # Creates a string with chars repeated N times. 142 | repeatChars = chars: level: 143 | lib.concatStrings (map (_: chars) (lib.range 1 level)); 144 | 145 | renderNode = opts: node: 146 | if isStringNode node then 147 | node.value 148 | else if isIndentNode node then 149 | repeatChars opts.indentChars node.value 150 | else if isVariableNode node then 151 | let equals = if opts.spaceAroundEquals then " = " else "="; 152 | in '' 153 | ${node.name}${equals}${valueToString node.value} 154 | '' 155 | else if isRepeatNode node then 156 | lib.concatStrings (map (renderNode opts) node.value) 157 | else if isSectionNode node then '' 158 | ${node.name} { 159 | ${renderNodeList opts node.value}} 160 | '' else 161 | abort '' 162 | value is not of any known node type: 163 | ${toPrettyM node} 164 | ''; 165 | 166 | # Converts a single value to a valid Hyprland config RHS 167 | valueToString = value: 168 | if value == null then 169 | "" 170 | else if lib.isBool value then 171 | lib.boolToString value 172 | else if lib.isInt value || lib.isFloat value then 173 | toString value 174 | else if lib.isString value then 175 | value 176 | else if lib.isList value then 177 | lib.concatMapStringsSep " " valueToString value 178 | else 179 | abort '' 180 | could not convert value of type '${ 181 | builtins.typeOf value 182 | }' to config string: 183 | ${toPrettyM value} 184 | ''; 185 | in { 186 | inherit 187 | # Transforms 188 | toConfigString attrsToNodeList pruneEmptyNodesRecursive renderNodeList 189 | insertLineBreakNodesRecursive insertIndentNodesRecursive 190 | # Checks 191 | nodeType isNode isNodeType isStringNode isIndentNode isVariableNode 192 | isRepeatNode isSectionNode 193 | # Node Factories 194 | mkStringNode mkIndentNode mkVariableNode mkRepeatNode mkSectionNode 195 | # Utilities 196 | mapValue renderNode valueToString; 197 | } 198 | -------------------------------------------------------------------------------- /hm-module/rules.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { config, pkgs, ... }: 3 | let 4 | inherit (lib) types; 5 | inherit (lib.hyprnix.hyprlang) valueToString; 6 | 7 | cfg = config.wayland.windowManager.hyprland; 8 | in { 9 | options = { 10 | wayland.windowManager.hyprland = { 11 | ### LAYER RULES ### 12 | layerRules = lib.mkOption { 13 | type = types.listOf (types.submodule { 14 | options = { 15 | namespace = lib.mkOption { 16 | type = types.nullOr (types.listOf types.singleLineStr); 17 | default = null; 18 | description = lib.mdDoc '' 19 | A list of layersurface namespaces to match against. 20 | ''; 21 | }; 22 | rules = lib.mkOption { 23 | type = types.nullOr (types.listOf types.singleLineStr); 24 | default = null; 25 | description = lib.mdDoc '' 26 | A list of window rules to apply to a window matching 27 | both the class names and titles given. 28 | ''; 29 | }; 30 | }; 31 | }); 32 | default = [ ]; 33 | description = lib.mdDoc '' 34 | List of sets containing: 35 | 36 | - `rules` = List of rules to apply to matched windows. 37 | - `namespace` = List of patterns to test the layersurface namespace against. 38 | 39 | See the example for more information. 40 | ``` 41 | ''; 42 | example = lib.literalExpression '' 43 | [ 44 | { 45 | namespace = ["rofi" "notification"]; 46 | rules = ["blur" "ignorezero"]; 47 | } 48 | ] 49 | ''; 50 | }; 51 | 52 | ### WINDOW RULES ### 53 | windowRules = lib.mkOption { 54 | type = types.listOf (types.submodule { 55 | options = { 56 | class = lib.mkOption { 57 | type = types.nullOr (types.listOf types.singleLineStr); 58 | default = null; 59 | description = lib.mdDoc '' 60 | A list of class names to match against. 61 | ''; 62 | }; 63 | title = lib.mkOption { 64 | type = types.nullOr (types.listOf types.singleLineStr); 65 | default = null; 66 | description = lib.mdDoc '' 67 | A list of window titles to match against. 68 | ''; 69 | }; 70 | rules = lib.mkOption { 71 | type = types.nullOr (types.listOf types.singleLineStr); 72 | default = null; 73 | description = lib.mdDoc '' 74 | A list of window rules to apply to a window matching 75 | both the class names and titles given. 76 | ''; 77 | }; 78 | }; 79 | }); 80 | default = [ ]; 81 | description = lib.mdDoc '' 82 | List of sets containing: 83 | 84 | - `rules` = List of rules to apply to matched windows. 85 | - `class` = List of patterns to test the window class against. 86 | - `title` = List of patterns to test the window title against. 87 | 88 | See the example for more information. 89 | 90 | As an addendum, something you may want to use is this: 91 | 92 | ```nix 93 | let 94 | rule = { 95 | class ? null, 96 | title ? null, 97 | }: rules: {inherit class title rules;}; 98 | in 99 | with patterns; 100 | lib.concatLists [ 101 | (rule obsStudio ["size 1200 800" "workspace 10"]) 102 | 103 | (map (rule ["float"]) [ 104 | printerConfig 105 | audioControl 106 | bluetoothControl 107 | kvantumConfig 108 | filePickerPortal 109 | polkitAgent 110 | mountDialog 111 | calculator 112 | obsStudio 113 | steam 114 | ]) 115 | ] 116 | ] 117 | ``` 118 | ''; 119 | example = lib.literalExpression '' 120 | let 121 | obsStudio = { 122 | class = ["com.obsproject.Studio"]; 123 | title = ["OBS\s[\d\.]+.*"]; 124 | }; 125 | # match both WebCord and Discord clients 126 | # by two class names this will end up as 127 | # ^(WebCord|discord)$ 128 | # in the config file. 129 | discord.class = ["WebCord" "discord"]; 130 | in [ 131 | # open OBS Studio on a specific workspace with an initial size 132 | (obsStudio // {rules = ["size 1200 800" "workspace 10"];}) 133 | # make WebCord and Discord slightly transparent 134 | (discord // {rules = ["opacity 0.93 0.93"];}) 135 | ] 136 | ''; 137 | }; 138 | 139 | ### WORKSPACE RULES ### 140 | workspaceRules = lib.mkOption { 141 | type = with types; 142 | let unitType = oneOf [ singleLineStr bool int ]; 143 | in attrsOf (attrsOf (either unitType (listOf unitType))); 144 | default = { }; 145 | description = lib.mdDoc '' 146 | Attribute set of workspace rules. 147 | The name of the attribute must be a valid workspace identifier 148 | as specified on the [Dispatchers: Workspace wiki page]. 149 | 150 | The value of a workspace rule is an attribute set of rule names 151 | and values. 152 | 153 | Valid rule names are listed in the 154 | [Rules section of the Workspace Rules wiki page]. 155 | 156 | [Dispatchers: Workspace wiki page]: https://wiki.hyprland.org/Configuring/Dispatchers/#workspaces 157 | [Rules section of the Workspace Rules wiki page]: https://wiki.hyprland.org/Configuring/Workspace-Rules/#rules 158 | ''; 159 | }; 160 | }; 161 | }; 162 | 163 | config = let 164 | # Given a window rule (one with a `rules` list) 165 | # convert it into a list of first-form rules 166 | # (one with a single string value for the `rule` attr). 167 | expandRuleToList = rule2: 168 | let rule1 = removeAttrs rule2 [ "rules" ]; 169 | in map (rule: rule1 // { inherit rule; }) rule2.rules; 170 | # Given a window rule in second- or first-form 171 | # (with `rule` or `rules` attr respectively), 172 | # turn the `class` and `title` list into regex patterns 173 | # matching any string in each list. 174 | compileWindowRulePatterns = rule: 175 | rule // { 176 | class = lib.mapNullable (x: "class:^(${lib.concatStringsSep "|" x})$") 177 | rule.class; 178 | title = lib.mapNullable (x: "title:^(${lib.concatStringsSep "|" x})$") 179 | rule.title; 180 | }; 181 | compileLayerRulePattern = rule: 182 | rule // { 183 | namespace = "^(${lib.concatStringsSep "|" rule.namespace})"; 184 | }; 185 | 186 | # Convert a rule (first-form) into a valid string value for 187 | # a Hyprland config variable. 188 | windowRuleToString = rule: 189 | lib.concatStringsSep ", " ([ rule.rule ] 190 | ++ (lib.optional (rule.class != null) rule.class) 191 | ++ (lib.optional (rule.title != null) rule.title)); 192 | layerRuleToString = rule: "${rule.rule}, ${rule.namespace}"; 193 | 194 | compileWorkspaceRules = rules: 195 | lib.concatStringsSep ", " (lib.mapAttrsToList (rule: value: 196 | if lib.isList value then 197 | (lib.concatStringsSep ", " 198 | (map (it: "${rule}:${valueToString it}") value)) 199 | else 200 | "${rule}:${valueToString value}") rules); 201 | in { 202 | wayland.windowManager.hyprland.config.layerrule = lib.pipe cfg.layerRules [ 203 | # very similar to windowrulev2 204 | (map compileLayerRulePattern) 205 | (map expandRuleToList) 206 | lib.concatLists 207 | (map layerRuleToString) 208 | ]; 209 | 210 | wayland.windowManager.hyprland.config.windowrulev2 = 211 | lib.pipe cfg.windowRules [ 212 | # right now we have window rules in second-form, 213 | # each "rule" has a `rules` attr which is a list of string-rules. 214 | # 215 | # the first step is to turn `class` and `title` into regex strings 216 | # for each second-form rule. 217 | (map compileWindowRulePatterns) 218 | # then expand each into a list of rules in first-form, 219 | # with `rule` instead of `rules`. 220 | (map expandRuleToList) 221 | # combine the intermediate lists to a single list of first-form rules. 222 | lib.concatLists 223 | # map each to a Hyprland config variable value string. 224 | (map windowRuleToString) 225 | ]; 226 | 227 | wayland.windowManager.hyprland.config.workspace = 228 | lib.pipe cfg.workspaceRules [ 229 | (builtins.mapAttrs (_: compileWorkspaceRules)) 230 | (lib.mapAttrsToList (workspace: rules: "${workspace}, ${rules}")) 231 | ]; 232 | }; 233 | } 234 | -------------------------------------------------------------------------------- /hm-module/events.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { config, pkgs, ... }: 3 | let 4 | inherit (lib) types; 5 | 6 | cfg = config.wayland.windowManager.hyprland.eventListener; 7 | 8 | description = '' 9 | Event listener for Hyprland's `socket2` IPC protocol. 10 | ''; 11 | 12 | # Takes a list of event handler name/variable mappings, 13 | # and prefixes each string in `vars` with `prefix`. 14 | # 15 | # For example, when `prefix` is `"HL_"`, 16 | # the `vars` list `["WINDOW_CLASS" "WINDOW_TITLE"]` 17 | # will be turned into `["HL_WINDOW_CLASS" "HL_WINDOW_TITLE"]`. 18 | # 19 | # This prefix is added to reduce the chance of collisions in 20 | # bash scripts. 21 | addEventVarPrefixes = prefix: 22 | lib.mapAttrs 23 | (_: attrs@{ vars, ... }: attrs // { vars = map (v: prefix + v) vars; }); 24 | 25 | # prefix to avoid any potential collisions 26 | eventHandlers = addEventVarPrefixes "HL_" { 27 | ### WINDOWS ### 28 | windowFocus = { 29 | event = "activewindow"; 30 | vars = [ "WINDOW_CLASS" "WINDOW_TITLE" ]; 31 | }; 32 | windowFocusV2 = { 33 | event = "activewindowv2"; 34 | vars = [ "WINDOW_ADDRESS" ]; 35 | }; 36 | windowOpen = { 37 | event = "openwindow"; 38 | vars = 39 | [ "WINDOW_ADDRESS" "WORKSPACE_NAME" "WINDOW_CLASS" "WINDOW_TITLE" ]; 40 | }; 41 | windowClose = { 42 | event = "movewindow"; 43 | vars = [ "WINDOW_ADDRESS" ]; 44 | }; 45 | windowMove = { 46 | event = "movewindow"; 47 | vars = [ "WINDOW_ADDRESS" "WORKSPACE_NAME" ]; 48 | }; 49 | windowFloat = { 50 | event = "changefloatingmode"; 51 | vars = [ "WINDOW_ADDRESS" "FLOAT_STATE" ]; 52 | }; 53 | windowFullscreen = { 54 | event = "fullscreen"; 55 | vars = [ "FULLSCREEN_STATE" ]; 56 | }; 57 | windowMinimize = { 58 | event = "minimize"; 59 | vars = [ "WINDOW_ADDRESS" "MINIMIZE_STATE" ]; 60 | }; 61 | windowUrgent = { 62 | event = "urgent"; 63 | vars = [ "WINDOW_ADDRESS" ]; 64 | }; 65 | windowTitle = { 66 | event = "windowtitle"; 67 | vars = [ "WINDOW_ADDRESS" ]; 68 | }; 69 | 70 | ### LAYERS ### 71 | layerOpen = { 72 | event = "openlayer"; 73 | vars = [ "LAYER_NAMESPACE" ]; 74 | }; 75 | layerClose = { 76 | event = "closelayer"; 77 | vars = [ "LAYER_NAMESPACE" ]; 78 | }; 79 | 80 | ### WORKSPACES ### 81 | workspaceFocus = { 82 | event = "workspace"; 83 | vars = [ "WORKSPACE_NAME" ]; 84 | }; 85 | workspaceCreate = { 86 | event = "createworkspace"; 87 | vars = [ "WORKSPACE_NAME" ]; 88 | }; 89 | workspaceDestroy = { 90 | event = "destroyworkspace"; 91 | vars = [ "WORKSPACE_NAME" ]; 92 | }; 93 | workspaceMove = { 94 | event = "moveworkspace"; 95 | vars = [ "WORKSPACE_NAME" "MONITOR_NAME" ]; 96 | }; 97 | 98 | ### MONITORS ### 99 | monitorFocus = { 100 | event = "focusedmon"; 101 | vars = [ "MONITOR_NAME" "WORKSPACE_NAME" ]; 102 | }; 103 | monitorAdd = { 104 | event = "monitoradded"; 105 | vars = [ "MONITOR_NAME" ]; 106 | }; 107 | monitorAddV2 = { 108 | event = "monitoraddedv2"; 109 | vars = [ "MONITOR_ID" "MONITOR_NAME" "MONITOR_DESC" ]; 110 | }; 111 | monitorRemove = { 112 | event = "monitorremoved"; 113 | vars = [ "MONITOR_NAME" ]; 114 | }; 115 | 116 | ### MISCELLANEOUS ### 117 | layoutChange = { 118 | event = "activelayout"; 119 | vars = [ "KEYBOARD_NAME" "LAYOUT_NAME" ]; 120 | }; 121 | submapChange = { 122 | event = "submap"; 123 | vars = [ "SUBMAP_NAME" ]; 124 | }; 125 | screencastChange = { 126 | event = "screencast"; 127 | vars = [ "SCREENCAST_STATE" "SCREENCAST_OWNER" ]; 128 | }; 129 | }; 130 | in { 131 | options = { 132 | wayland.windowManager.hyprland.eventListener = { 133 | enable = lib.mkOption { 134 | type = types.bool; 135 | default = true; 136 | description = lib.mdDoc '' 137 | Enable the Hyprland IPC listener & handlers configuration. 138 | 139 | See the documentation for each event on the Hyprland wiki: 140 | . 141 | 142 | Events and variables have been renamed 143 | to satisfy your mental disorders. 144 | 145 | ${description} 146 | ''; 147 | }; 148 | 149 | systemdService = lib.mkOption { 150 | type = types.bool; 151 | default = true; 152 | description = lib.mdDoc '' 153 | Enable the IPC event listener to be run as a systemd service 154 | wanted by `hyprland-sesion.target` instead of running as an 155 | `exec-start` entry in the Hyprland config file. 156 | 157 | This is recommended if you would like to be able to 158 | `systemctl --user restart hyprland-event-handler` 159 | (for instance, when testing changes) instead of restarting 160 | the entire Hyprland session. 161 | ''; 162 | }; 163 | 164 | handler = builtins.mapAttrs (_: 165 | { event, vars, ... }: 166 | lib.mkOption { 167 | type = types.nullOr types.lines; 168 | default = null; 169 | description = lib.mdDoc '' 170 | IPC socket event name: `${event}` 171 | 172 | Environment variables: 173 | 174 | ${lib.concatMapStrings (v: " - `${v}`\n") vars} 175 | 176 | The above environment variables can be used in lines of 177 | shell code declared by this option. They are exported such that 178 | any subprocesses called by this handler 179 | script will also recieve them. The order they are listed in 180 | agrees with the positional data shown for the `${event}` event 181 | as shown on the wiki. 182 | 183 | 184 | ''; 185 | }) eventHandlers; 186 | }; 187 | }; 188 | 189 | config = let 190 | # Turn each value in `cfg.handler` into an attrset 191 | # saturated with `event` and `vars` from `eventHandlers`. 192 | # The existing value (string) will be kept as `script`. 193 | handlerInfos = lib.pipe cfg.handler [ 194 | # remove handlers which are null 195 | (lib.filterAttrs (_: v: v != null)) 196 | # map each handler name and value 197 | # to have a value of `{event, vars, script}` 198 | # where `event` and `vars` are from the attribute in 199 | # `eventHandlers` of the same name. 200 | (lib.mapAttrs (n: text: { 201 | inherit (builtins.getAttr n eventHandlers) event vars; 202 | script = pkgs.writeShellScript "hyprland-${n}-handler" text; 203 | })) 204 | ]; 205 | 206 | # Given an attrset from `handlerInfos`, create a regex pattern 207 | # to match the expected socket message, the entire line including parameters. 208 | # TODO vaxry, window titles can have commas in them... 209 | mkEventRegex = { event, vars, ... }: 210 | "^${event}\\>\\>${ 211 | lib.concatStringsSep "," 212 | (builtins.genList (_: "(.+)") (builtins.length vars)) 213 | }$"; 214 | 215 | # This script is used in a systemd service that is `PartOf` 216 | # `hyprland-session.target`. 217 | # It is itself the event listener. It opens the socket, and 218 | # forever reads the lines, branching out to a handler script 219 | # if the line matches a pattern created from an event's name 220 | # and parameter list. 221 | listenerScript = pkgs.writeShellScript "hyprland-event-listener" '' 222 | set -o pipefail 223 | 224 | socket="$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" 225 | echo "INFO: opening socket: $socket" 226 | 227 | ${pkgs.netcat}/bin/nc -U "$socket" | while read -r line; do 228 | ${ 229 | lib.concatStrings (lib.mapAttrsToList (_: info: '' 230 | if [[ "$line" =~ ${mkEventRegex info} ]]; then 231 | ${ 232 | lib.concatStringsSep "\n " (lib.imap0 (i: v: 233 | ''export ${v}="''${BASH_REMATCH[${toString (i + 1)}]}"'') 234 | info.vars) 235 | } 236 | ${info.script} 237 | exit=$? 238 | if [[ $exit -ne 0 ]]; then 239 | echo "ERROR: exited $exit: ''${BASH_REMATCH[0]}" 240 | else 241 | echo "SUCCESS: handled: ''${BASH_REMATCH[0]}" 242 | fi 243 | continue 244 | fi 245 | '') handlerInfos) 246 | } 247 | 248 | echo "INFO: unhandled event: $line" 249 | done || echo "ERROR: main pipeline failed, exit: $?" 250 | ''; 251 | in lib.mkIf (cfg.enable && cfg.handler != { }) (lib.mkMerge [ 252 | # If it is a systemd service, 253 | (lib.mkIf cfg.systemdService { 254 | systemd.user.services.hyprland-event-listener = { 255 | Unit = { 256 | Description = description; 257 | PartOf = "hyprland-session.target"; 258 | }; 259 | Service = { 260 | Type = "simple"; 261 | ExecStart = "${listenerScript}"; 262 | Restart = "on-failure"; 263 | RestartSec = 5; 264 | }; 265 | Install.WantedBy = [ "hyprland-session.target" ]; 266 | }; 267 | }) 268 | # Otherwise use an `execOnce` line in the Hyprland config. 269 | # Requires the `extraInitConfig` to be specified by my (Jacob) 270 | # `nix/hm-module/config.nix` in my Hyprland fork. 271 | (lib.mkIf (!cfg.systemdService) { 272 | wayland.windowManager.hyprland = { 273 | # extraInitConfig = '' 274 | extraConfig = '' 275 | exec-once = ${listenerScript} 276 | ''; 277 | }; 278 | }) 279 | ]); 280 | } 281 | -------------------------------------------------------------------------------- /hm-module/monitors.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { config, ... }: 3 | let 4 | inherit (lib) types; 5 | 6 | cfg = config.wayland.windowManager.hyprland.monitors; 7 | 8 | mkIntEnum = mapping: { 9 | inherit mapping; 10 | # Given `needle`, which is either the variant name or an integer value, 11 | # return the corresponding variant name. 12 | variantName = needle: 13 | (lib.findFirst ({ name, value }: needle == name || needle == value) null 14 | (lib.attrsToList mapping)).name; 15 | # Given `needle`, which is either the variant name or an integer value, 16 | # return the corresponding integer value. 17 | variantValue = needle: 18 | (lib.find ({ name, value }: needle == name || needle == value) null 19 | (lib.attrsToList mapping)).value; 20 | type = 21 | types.enum (builtins.attrValues mapping ++ builtins.attrNames mapping); 22 | apply = x: if lib.isString x then mapping.${x} else x; 23 | }; 24 | 25 | # See docs of option `transform`. 26 | transformEnum = mkIntEnum { 27 | "Normal" = 0; 28 | "Degrees90" = 1; 29 | "Degrees180" = 2; 30 | "Degrees270" = 3; 31 | "Flipped" = 4; 32 | "FlippedDegrees90" = 5; 33 | "FlippedDegrees180" = 6; 34 | "FlippedDegrees270" = 7; 35 | }; 36 | 37 | # See docs of option `vrrMode`. 38 | vrrModeEnum = mkIntEnum { 39 | "default" = null; 40 | "on" = 1; 41 | "off" = 0; 42 | "fullscreen" = 2; 43 | }; 44 | 45 | # For position and resolution. 46 | point2DType = numType: 47 | types.submodule { 48 | options = { 49 | x = lib.mkOption { 50 | type = numType; 51 | description = 52 | "The X-coordinate of a point, or the width of a rectangle."; 53 | }; 54 | y = lib.mkOption { 55 | type = numType; 56 | description = 57 | "The Y-coordinate of a point, or the height of a rectangle."; 58 | }; 59 | }; 60 | }; 61 | 62 | monitorDefType = types.submodule ({ config, ... }: { 63 | # 64 | imports = [ 65 | (lib.mkRenamedOptionModule [ "name" ] [ "output" ]) 66 | # @Ujinn34 wanted this option to be named `disable`. 67 | # 68 | (lib.mkRenamedOptionModule [ "disabled" ] [ "disable" ]) 69 | ]; 70 | 71 | options = { 72 | output = lib.mkOption { 73 | type = types.singleLineStr; 74 | description = '' 75 | The name of the output node that the monitor is connected to, 76 | as shown in the output of `hyprctl monitors`, for example `eDP-1` or `HDMI-A-1`. 77 | ''; 78 | }; 79 | description = lib.mkOption { 80 | type = types.nullOr types.singleLineStr; 81 | default = null; 82 | description = '' 83 | The description of a monitor as shown in the output of 84 | `hyprctl monitors` (without the parenthesized name at the end). 85 | ''; 86 | }; 87 | disable = lib.mkOption { 88 | type = types.bool; 89 | default = false; 90 | description = '' 91 | Disable this monitor, removing it from the layout. 92 | ''; 93 | }; 94 | position = lib.mkOption { 95 | type = types.either (point2DType types.ints.unsigned) (types.enum [ 96 | "auto" 97 | "auto-up" 98 | "auto-down" 99 | "auto-left" 100 | "auto-right" 101 | "auto-center-up" 102 | "auto-center-down" 103 | "auto-center-left" 104 | "auto-center-right" 105 | ]); 106 | default = "auto"; 107 | description = '' 108 | The position of the monitor as `{ x, y }` attributes, 109 | or the name of some automatic behavior. 110 | 111 | ::: {.note} 112 | Coordinates are offsets relative to the top-left corner of virtual 113 | screen space. 114 | ::: 115 | ''; 116 | }; 117 | resolution = lib.mkOption { 118 | type = types.either (point2DType types.ints.positive) 119 | (types.enum [ "preferred" "highrr" "highres" "maxwidth" ]); 120 | default = "preferred"; 121 | description = '' 122 | The physical size of the display as `{ x, y }` attributes, 123 | or the name of some automatic behavior. 124 | 125 | ::: {.note} 126 | If you want define your `position` attributes relative to 127 | each other, use the value of {option}`scale` recursively. 128 | ::: 129 | ''; 130 | }; 131 | scale = lib.mkOption { 132 | type = types.either types.float (types.enum [ "auto" ]); 133 | default = 1.0; 134 | description = '' 135 | The fractional scaling factor to use for Wayland-native programs. 136 | The virtual size of the display will be each dimension divided by 137 | this float. For example, the virtual size of a monitor with a physical 138 | size of 2880x1800 pixels would be 1920x1200 virtual pixels. 139 | ''; 140 | }; 141 | refreshRate = lib.mkOption { 142 | type = types.nullOr (types.either types.ints.positive types.float); 143 | default = null; 144 | description = '' 145 | The refresh rate of the monitor, if unspecified will choose 146 | a default mode for your specified resolution. 147 | ''; 148 | }; 149 | vrrMode = lib.mkOption { 150 | inherit (vrrModeEnum) type apply; 151 | default = null; 152 | description = '' 153 | Whether to enable variable refresh rate (FreeSync/AdaptiveSync/GSync). 154 | This option is specifically for a monitor. There is a global option also, 155 | `null`/`default` is for deferring. 156 | ''; 157 | }; 158 | bitdepth = lib.mkOption { 159 | type = types.enum [ 8 10 ]; 160 | default = 8; 161 | description = '' 162 | The color bit-depth of the monitor (8-bit or 10-bit color). 163 | ''; 164 | }; 165 | transform = lib.mkOption { 166 | inherit (transformEnum) type apply; 167 | default = "Normal"; 168 | description = '' 169 | Attribute names (enum identifiers) and values (repr) from the 170 | following ~~enum~~ attribute set are accepted as variants 171 | in this option `lib.types.enum`. 172 | 173 | ```nix 174 | ${lib.generators.toPretty { multiline = true; } transformEnum.mapping} 175 | ``` 176 | ''; 177 | }; 178 | mirror = lib.mkOption { 179 | type = types.nullOr types.singleLineStr; 180 | default = null; 181 | description = "The output name of the monitor to mirror."; 182 | example = lib.mdDoc '' 183 | The "output" of the monitor is named after its DRM output node, 184 | usually the connector, for example: `eDP-1`, `HDMI-A-1`, `DP-5`, `DP-6`. 185 | ''; 186 | }; 187 | 188 | size = lib.mkOption { 189 | type = types.nullOr (point2DType types.float); 190 | readOnly = true; 191 | description = '' 192 | The virtual display size after scaling, 193 | intended for use in recursive Nix configurations. 194 | 195 | This value can be `null` if {option}`resolution` is not an 196 | attribute set of coordinates. 197 | ''; 198 | }; 199 | modeString = lib.mkOption { 200 | type = types.singleLineStr; 201 | readOnly = true; 202 | internal = true; 203 | }; 204 | positionString = lib.mkOption { 205 | type = types.singleLineStr; 206 | readOnly = true; 207 | internal = true; 208 | }; 209 | }; 210 | 211 | config = let 212 | positionIsPoint = (point2DType types.ints.unsigned).check config.position; 213 | resolutionIsPoint = 214 | (point2DType types.ints.positive).check config.resolution; 215 | in { 216 | output = 217 | lib.mkIf (config.description != null) "desc:${config.description}"; 218 | 219 | size = if resolutionIsPoint then 220 | let 221 | transform = transformEnum.variantName config.transform; 222 | horizontal = { 223 | x = config.resolution.x / config.scale; 224 | y = config.resolution.y / config.scale; 225 | }; 226 | vertical = { 227 | x = horizontal.y; 228 | y = horizontal.x; 229 | }; 230 | lut = { 231 | "Normal" = horizontal; 232 | "Degrees90" = vertical; 233 | "Degrees180" = horizontal; 234 | "Degrees270" = vertical; 235 | "Flipped" = horizontal; 236 | "FlippedDegrees90" = vertical; 237 | "FlippedDegrees180" = horizontal; 238 | "FlippedDegrees270" = vertical; 239 | }; 240 | in lut.${transform} 241 | else 242 | null; 243 | 244 | modeString = if resolutionIsPoint then 245 | # The resolution in `WIDTHxHEIGHT@REFRESH`, with `@REFRESH` optionally. 246 | "${toString config.resolution.x}x${toString config.resolution.y}${ 247 | lib.optionalString (config.refreshRate != null) 248 | "@${toString config.refreshRate}" 249 | }" 250 | else 251 | # The resolution verbatim if it is an enum string. 252 | config.resolution; 253 | 254 | positionString = if positionIsPoint then 255 | # The position in `XxY` format if it is a point. 256 | "${toString config.position.x}x${toString config.position.y}" 257 | else 258 | # The position verbatim if it is an enum string. 259 | config.position; 260 | }; 261 | }); 262 | in { 263 | options = { 264 | wayland.windowManager.hyprland.monitors = lib.mkOption { 265 | type = types.attrsOf monitorDefType; 266 | default = { }; 267 | description = '' 268 | Monitors to configure. The attribute name is not used in the 269 | Hyprland configuration, but is a convenience for recursive Nix. 270 | 271 | The "output" the monitor will have (the connector, not make and model) 272 | is specified in the `output` attribute for the monitor. 273 | It is not the attribute name of the monitor in *this* parent set. 274 | ''; 275 | example = lib.literalExpression '' 276 | (with config.wayland.windowManager.hyprland.monitors; { 277 | # The attribute name `internal` is for usage in recursive Nix. 278 | internal = { 279 | output = "eDP-1"; 280 | pos = "auto"; # `auto` is default 281 | size = "preferred"; # `preferred` is default 282 | bitdepth = 10; 283 | }; 284 | }) 285 | ''; 286 | }; 287 | }; 288 | 289 | config = { 290 | wayland.windowManager.hyprland.config.monitorv2 = lib.mapAttrsToList 291 | (_: def: { 292 | inherit (def) output; 293 | disabled = lib.mkIf def.disable def.disable; 294 | mode = def.modeString; 295 | position = def.positionString; 296 | scale = lib.mkIf (def.scale != 1.0) def.scale; 297 | vrr = lib.mkIf (def.vrrMode != null) def.vrrMode; 298 | bitdepth = lib.mkIf (def.bitdepth != 8) def.bitdepth; 299 | transform = let default = transformEnum.mapping."Normal"; 300 | in lib.mkIf (def.transform != default) def.transform; 301 | mirror = lib.mkIf (def.mirror != null) def.mirror; 302 | }) cfg; 303 | }; 304 | } 305 | -------------------------------------------------------------------------------- /hm-module/config.nix: -------------------------------------------------------------------------------- 1 | { self, lib, ... }: 2 | { config, pkgs, ... }: 3 | let 4 | inherit (lib) types; 5 | inherit (lib.hyprnix.types) configFile; 6 | 7 | cfg = config.wayland.windowManager.hyprland; 8 | 9 | # The `hyprland` package in `pkgs` is probably newer if the overlay is used, 10 | # otherwise, the package from this flake is guaranteed to be newer. 11 | defaultPackage = let 12 | inNixpkgs = pkgs.hyprland.version; 13 | inFlake = self.packages.${pkgs.system}.hyprland.version; 14 | in if lib.versionOlder inNixpkgs inFlake then # hyprland in nixpkgs is newer 15 | pkgs.hyprland 16 | else # nixpkgs has same version or older 17 | self.packages.${pkgs.system}.hyprland; 18 | 19 | hyprlang = pkgs.callPackage ./configFormat.nix { inherit lib; }; 20 | configRenames = import ./configRenames.nix { inherit lib; }; 21 | configFormat = 22 | hyprlang (cfg.configFormatOptions // { inherit (cfg) configOrder; }); 23 | 24 | toConfigString = attrs: 25 | lib.pipe attrs [ 26 | (with configRenames; renameAttrs renames.from renames.to) 27 | configFormat.toConfigString 28 | ]; 29 | in { 30 | options = { 31 | wayland.windowManager.hyprland = { 32 | enable = lib.mkEnableOption (lib.mdDoc '' 33 | Whether to install the Hyprland package and generate configuration files. 34 | 35 | ${defaultPackage.meta.description} 36 | 37 | See <${defaultPackage.meta.homepage}> for more information. 38 | ''); 39 | 40 | package = lib.mkOption { 41 | type = types.package; 42 | default = defaultPackage; 43 | example = lib.literalExpression '' 44 | pkgs.hyprland # if you use the overlay 45 | ''; 46 | description = lib.mdDoc '' 47 | Hyprland package to use. The options in {option}`xwayland` and 48 | {option}`nvidiaPatches` will be applied to the package 49 | specified here via an override. 50 | 51 | Defaults to the one provided by the flake. Set it to 52 | {package}`pkgs.hyprland` to use the one provided by nixpkgs or 53 | if you have an overlay. 54 | 55 | Set to null to not add any Hyprland package to your path. This should 56 | be done if you want to use the NixOS module to install Hyprland. 57 | ''; 58 | }; 59 | 60 | finalPackage = lib.mkOption { 61 | type = types.package; 62 | readOnly = true; 63 | description = lib.mdDoc '' 64 | The final Hyprland packge that should be used in other parts of configuration. 65 | This is the result after applying overrides which are enabled/disabled/specified 66 | by other options of this module (for example, `xwayland.enable` or `nvidiaPatches`). 67 | ''; 68 | }; 69 | 70 | plugins = lib.mkOption { 71 | type = types.listOf (types.either types.package types.path); 72 | default = [ ]; 73 | description = lib.mdDoc '' 74 | List of paths or packages to install as Hyprland plugins. 75 | ''; 76 | }; 77 | 78 | xwayland.enable = lib.mkOption { 79 | type = types.bool; 80 | default = true; 81 | description = lib.mdDoc '' 82 | Enable XWayland. 83 | ''; 84 | }; 85 | 86 | nvidiaPatches = lib.mkOption { 87 | type = lib.types.bool; 88 | default = false; 89 | defaultText = lib.literalExpression "false"; 90 | example = lib.literalExpression "true"; 91 | description = lib.mdDoc '' 92 | Patch wlroots for better Nvidia support. 93 | ''; 94 | }; 95 | 96 | reloadConfig = lib.mkOption { 97 | type = lib.types.bool; 98 | default = true; 99 | description = lib.mdDoc '' 100 | If enabled, automatically tell Hyprland to reload configuration 101 | after activating a new Home Manager generation. 102 | 103 | Note, this option is different from {option}`misc.disable_autoreload`, 104 | which disables Hyprland's filesystem watch. 105 | ''; 106 | }; 107 | 108 | ### CONFIG ### 109 | 110 | configFile = lib.mkOption { 111 | type = types.attrsOf (configFile pkgs "${config.xdg.configHome}/hypr"); 112 | default = { }; 113 | description = lib.mdDoc '' 114 | Configuration files and directories to link in the Hyprland config directory. 115 | This is an attribute set of file descriptions similar to 116 | {option}`xdg.configFile`, except relative to {path}`$XDG_CONFIG_HOME/hypr`. 117 | 118 | If necessary, you may set {option}`xdg.configFile."hypr".recursive = true`. 119 | ''; 120 | }; 121 | 122 | configPackage = lib.mkOption { 123 | type = types.package; 124 | readOnly = true; 125 | }; 126 | 127 | config = lib.mkOption { 128 | type = configFormat.type; 129 | default = { }; 130 | description = lib.mdDoc '' 131 | Hyprland config attributes. 132 | These will be serialized to lines of text, 133 | included in {path}`$XDG_CONFIG_HOME/hypr/hyprland.conf`. 134 | ''; 135 | }; 136 | 137 | extraConfig = lib.mkOption { 138 | type = with types; nullOr lines; 139 | default = null; 140 | description = lib.mdDoc '' 141 | Extra configuration lines to append to the bottom of 142 | `~/.config/hypr/hyprland.conf`. 143 | ''; 144 | }; 145 | 146 | configOrder = lib.mkOption { 147 | # TODO move this type to configFormat 148 | type = types.listOf (types.listOf types.singleLineStr); 149 | default = [ 150 | [ "env" ] 151 | 152 | [ "exec-once" ] 153 | [ "exec" ] 154 | 155 | [ "source" ] 156 | 157 | [ "monitor" ] 158 | [ "monitorv2" ] 159 | [ "monitorv2" "output" ] 160 | [ "monitorv2" "mode" ] 161 | [ "monitorv2" "position" ] 162 | [ "monitorv2" "scale" ] 163 | [ "monitorv2" "transform" ] 164 | 165 | [ "workspace" ] 166 | 167 | [ "dwindle" ] 168 | [ "master" ] 169 | [ "general" ] 170 | [ "cursor" ] 171 | [ "input" ] 172 | [ "input" "touchpad" ] 173 | [ "input" "touchdevice" ] 174 | [ "input" "tablet" ] 175 | [ "device:.*" ] 176 | [ "binds" ] 177 | [ "gestures" ] 178 | [ "group" ] 179 | [ "group" "groupbar" ] 180 | [ "decoration" ] 181 | [ "animations" ] 182 | [ "animations" "bezier" ] 183 | [ "animations" "animation" ] 184 | 185 | [ "plugin" ] 186 | 187 | [ "blurls" ] 188 | [ "windowrule" ] 189 | [ "layerrule" ] 190 | [ "windowrulev2" ] 191 | 192 | [ "misc" ] 193 | [ "xwayland" ] 194 | [ "ecosystem" ] 195 | [ "debug" ] 196 | ]; 197 | description = lib.mdDoc '' 198 | An ordered list of attribute paths 199 | to determine sorting order of config section lines. 200 | 201 | This is necessary in some cases, namely where `bezier` must be defined 202 | before it can be used in `animation`. 203 | ''; 204 | }; 205 | 206 | configFormatOptions = { 207 | indentChars = lib.mkOption { 208 | type = types.strMatching "([ \\t]+)"; 209 | default = " "; 210 | description = lib.mdDoc '' 211 | Characters to use for each indent level, 212 | ''; 213 | }; 214 | 215 | spaceAroundEquals = lib.mkOption { 216 | type = types.bool; 217 | default = true; 218 | description = lib.mdDoc '' 219 | Whether to have spaces before and after an equals `=` operator. 220 | ''; 221 | }; 222 | 223 | ## TODO Replace this with boolean flag options. 224 | ## Asking a novice to build a predicate just to adjust some spacing 225 | ## is too much, and this can be more parametric. 226 | # lineBreakPred = lib.mkOption { 227 | # type = types.anything; 228 | # # type = with types; functionTo (functionTo bool); 229 | # default = prev: next: 230 | # let 231 | # inherit (configFormat.lib) nodeType isRepeatNode isSectionNode; 232 | # betweenDifferent = nodeType prev != nodeType next; 233 | # betweenRepeats = isRepeatNode prev && isRepeatNode next; 234 | # betweenSections = isSectionNode prev && isSectionNode next; 235 | # in prev != null 236 | # && (betweenDifferent || betweenRepeats || betweenSections); 237 | # description = lib.mdDoc '' 238 | # The predicate with which to determine where to insert line breaks. 239 | # Return `true` to add a break, `false` to continue. 240 | 241 | # Use functions from {path}`configFormat.nix` to test node types. 242 | # ''; 243 | # defaultText = lib.literalExpression '' 244 | # prev: next: 245 | # let 246 | # configFormat = (import ./configFormat.nix args') cfg.configFormatOptions; 247 | # inherit (configFormat.lib) nodeType isRepeatNode isSectionNode; 248 | # betweenDifferent = nodeType prev != nodeType next; 249 | # betweenRepeats = isRepeatNode prev && isRepeatNode next; 250 | # betweenSections = isSectionNode prev && isSectionNode next; 251 | # in prev != null 252 | # && (betweenDifferent || betweenRepeats || betweenSections) 253 | # ''; 254 | # }; 255 | }; 256 | }; 257 | }; 258 | 259 | config = lib.mkIf cfg.enable (lib.mkMerge [ 260 | { 261 | wayland.windowManager.hyprland.finalPackage = cfg.package.override { 262 | enableXWayland = cfg.xwayland.enable; 263 | inherit (cfg) nvidiaPatches; 264 | }; 265 | home.packages = [ cfg.finalPackage ] 266 | ++ lib.optional cfg.xwayland.enable pkgs.xwayland; 267 | } 268 | { 269 | wayland.windowManager.hyprland.configPackage = pkgs.symlinkJoin { 270 | name = "hyprland-config"; 271 | paths = lib.mapAttrsToList (_: file: file.source) cfg.configFile; 272 | }; 273 | } 274 | (lib.mkIf cfg.enable { xdg.configFile."hypr".source = cfg.configPackage; }) 275 | # Can't set `hyprland.config.plugin` because the key is expected to be unique, 276 | # and that attribute should be used for plugin config, not loading them. 277 | (lib.mkIf (cfg.plugins != [ ]) { 278 | wayland.windowManager.hyprland.configFile."hyprland.conf".text = 279 | lib.mkOrder 350 (toConfigString { 280 | plugin = 281 | map (package: "${package}/lib/lib${package.pname}.so") cfg.plugins; 282 | }); 283 | }) 284 | (lib.mkIf (cfg.config != null) { 285 | wayland.windowManager.hyprland.configFile."hyprland.conf".text = 286 | lib.mkOrder 500 (toConfigString cfg.config); 287 | }) 288 | (lib.mkIf (cfg.extraConfig != null) { 289 | wayland.windowManager.hyprland.configFile."hyprland.conf".text = 290 | lib.mkOrder 900 cfg.extraConfig; 291 | }) 292 | (lib.mkIf cfg.reloadConfig { 293 | wayland.windowManager.hyprland.config.misc.disable_autoreload = 294 | lib.mkDefault true; 295 | 296 | xdg.configFile."hypr".onChange = '' 297 | ( 298 | XDG_RUNTIME_DIR=''${XDG_RUNTIME_DIR:-/run/user/$(id -u)} 299 | if [[ -d "$XDG_RUNTIME_DIR/hypr" ]]; then 300 | for instance in $(${cfg.finalPackage}/bin/hyprctl instances -j | jq ".[].instance" -r); do 301 | response="$(${cfg.finalPackage}/bin/hyprctl -i "$instance" reload config-only 2>&1)" 302 | [[ $response =~ ^ok ]] && \ 303 | echo "Hyprland instance reloaded: $HYPRLAND_INSTANCE_SIGNATURE" 304 | done 305 | fi 306 | ) 307 | ''; 308 | }) 309 | ]); 310 | } 311 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "aquamarine": { 4 | "inputs": { 5 | "hyprutils": [ 6 | "hyprland", 7 | "hyprutils" 8 | ], 9 | "hyprwayland-scanner": [ 10 | "hyprland", 11 | "hyprwayland-scanner" 12 | ], 13 | "nixpkgs": [ 14 | "hyprland", 15 | "nixpkgs" 16 | ], 17 | "systems": [ 18 | "hyprland", 19 | "systems" 20 | ] 21 | }, 22 | "locked": { 23 | "lastModified": 1750372185, 24 | "narHash": "sha256-lVBKxd9dsZOH1fA6kSE5WNnt8e+09fN+NL/Q3BjTWHY=", 25 | "owner": "hyprwm", 26 | "repo": "aquamarine", 27 | "rev": "7cef49d261cbbe537e8cb662485e76d29ac4cbca", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "hyprwm", 32 | "repo": "aquamarine", 33 | "type": "github" 34 | } 35 | }, 36 | "bird-nix-lib": { 37 | "inputs": { 38 | "nixpkgs": [ 39 | "nixpkgs" 40 | ], 41 | "systems": [ 42 | "systems" 43 | ] 44 | }, 45 | "locked": { 46 | "lastModified": 1718015582, 47 | "narHash": "sha256-VzK23WKktlJdTcoOh+rXKhSPYRWkl9ShIZh4tB44UV4=", 48 | "owner": "spikespaz", 49 | "repo": "bird-nix-lib", 50 | "rev": "95948f65ac043078661910cb23a123b4b2b9fb8c", 51 | "type": "github" 52 | }, 53 | "original": { 54 | "owner": "spikespaz", 55 | "repo": "bird-nix-lib", 56 | "type": "github" 57 | } 58 | }, 59 | "flake-compat": { 60 | "flake": false, 61 | "locked": { 62 | "lastModified": 1696426674, 63 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 64 | "owner": "edolstra", 65 | "repo": "flake-compat", 66 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "edolstra", 71 | "repo": "flake-compat", 72 | "type": "github" 73 | } 74 | }, 75 | "gitignore": { 76 | "inputs": { 77 | "nixpkgs": [ 78 | "hyprland", 79 | "pre-commit-hooks", 80 | "nixpkgs" 81 | ] 82 | }, 83 | "locked": { 84 | "lastModified": 1709087332, 85 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 86 | "owner": "hercules-ci", 87 | "repo": "gitignore.nix", 88 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 89 | "type": "github" 90 | }, 91 | "original": { 92 | "owner": "hercules-ci", 93 | "repo": "gitignore.nix", 94 | "type": "github" 95 | } 96 | }, 97 | "hyprcursor": { 98 | "inputs": { 99 | "hyprlang": [ 100 | "hyprland", 101 | "hyprlang" 102 | ], 103 | "nixpkgs": [ 104 | "hyprland", 105 | "nixpkgs" 106 | ], 107 | "systems": [ 108 | "hyprland", 109 | "systems" 110 | ] 111 | }, 112 | "locked": { 113 | "lastModified": 1749155331, 114 | "narHash": "sha256-XR9fsI0zwLiFWfqi/pdS/VD+YNorKb3XIykgTg4l1nA=", 115 | "owner": "hyprwm", 116 | "repo": "hyprcursor", 117 | "rev": "45fcc10b4c282746d93ec406a740c43b48b4ef80", 118 | "type": "github" 119 | }, 120 | "original": { 121 | "owner": "hyprwm", 122 | "repo": "hyprcursor", 123 | "type": "github" 124 | } 125 | }, 126 | "hyprgraphics": { 127 | "inputs": { 128 | "hyprutils": [ 129 | "hyprland", 130 | "hyprutils" 131 | ], 132 | "nixpkgs": [ 133 | "hyprland", 134 | "nixpkgs" 135 | ], 136 | "systems": [ 137 | "hyprland", 138 | "systems" 139 | ] 140 | }, 141 | "locked": { 142 | "lastModified": 1750371717, 143 | "narHash": "sha256-cNP+bVq8m5x2Rl6MTjwfQLCdwbVmKvTH7yqVc1SpiJM=", 144 | "owner": "hyprwm", 145 | "repo": "hyprgraphics", 146 | "rev": "15c6f8f3a567fec9a0f732cd310a7ff456deef88", 147 | "type": "github" 148 | }, 149 | "original": { 150 | "owner": "hyprwm", 151 | "repo": "hyprgraphics", 152 | "type": "github" 153 | } 154 | }, 155 | "hyprland": { 156 | "inputs": { 157 | "aquamarine": "aquamarine", 158 | "hyprcursor": "hyprcursor", 159 | "hyprgraphics": "hyprgraphics", 160 | "hyprland-protocols": "hyprland-protocols", 161 | "hyprland-qtutils": "hyprland-qtutils", 162 | "hyprlang": "hyprlang", 163 | "hyprutils": "hyprutils", 164 | "hyprwayland-scanner": "hyprwayland-scanner", 165 | "nixpkgs": "nixpkgs", 166 | "pre-commit-hooks": "pre-commit-hooks", 167 | "systems": [ 168 | "systems" 169 | ], 170 | "xdph": "xdph" 171 | }, 172 | "locked": { 173 | "lastModified": 1750681989, 174 | "narHash": "sha256-uxIwiV1p2SVNIoP+oD025lZKfq4zNn7CmdaYVoskqnQ=", 175 | "owner": "hyprwm", 176 | "repo": "hyprland", 177 | "rev": "cf7e3aa448f8c9e0d9e8f407e6ed730da55acc69", 178 | "type": "github" 179 | }, 180 | "original": { 181 | "owner": "hyprwm", 182 | "repo": "hyprland", 183 | "type": "github" 184 | } 185 | }, 186 | "hyprland-protocols": { 187 | "inputs": { 188 | "nixpkgs": [ 189 | "hyprland", 190 | "nixpkgs" 191 | ], 192 | "systems": [ 193 | "hyprland", 194 | "systems" 195 | ] 196 | }, 197 | "locked": { 198 | "lastModified": 1749046714, 199 | "narHash": "sha256-kymV5FMnddYGI+UjwIw8ceDjdeg7ToDVjbHCvUlhn14=", 200 | "owner": "hyprwm", 201 | "repo": "hyprland-protocols", 202 | "rev": "613878cb6f459c5e323aaafe1e6f388ac8a36330", 203 | "type": "github" 204 | }, 205 | "original": { 206 | "owner": "hyprwm", 207 | "repo": "hyprland-protocols", 208 | "type": "github" 209 | } 210 | }, 211 | "hyprland-qt-support": { 212 | "inputs": { 213 | "hyprlang": [ 214 | "hyprland", 215 | "hyprland-qtutils", 216 | "hyprlang" 217 | ], 218 | "nixpkgs": [ 219 | "hyprland", 220 | "hyprland-qtutils", 221 | "nixpkgs" 222 | ], 223 | "systems": [ 224 | "hyprland", 225 | "hyprland-qtutils", 226 | "systems" 227 | ] 228 | }, 229 | "locked": { 230 | "lastModified": 1749154592, 231 | "narHash": "sha256-DO7z5CeT/ddSGDEnK9mAXm1qlGL47L3VAHLlLXoCjhE=", 232 | "owner": "hyprwm", 233 | "repo": "hyprland-qt-support", 234 | "rev": "4c8053c3c888138a30c3a6c45c2e45f5484f2074", 235 | "type": "github" 236 | }, 237 | "original": { 238 | "owner": "hyprwm", 239 | "repo": "hyprland-qt-support", 240 | "type": "github" 241 | } 242 | }, 243 | "hyprland-qtutils": { 244 | "inputs": { 245 | "hyprland-qt-support": "hyprland-qt-support", 246 | "hyprlang": [ 247 | "hyprland", 248 | "hyprlang" 249 | ], 250 | "hyprutils": [ 251 | "hyprland", 252 | "hyprland-qtutils", 253 | "hyprlang", 254 | "hyprutils" 255 | ], 256 | "nixpkgs": [ 257 | "hyprland", 258 | "nixpkgs" 259 | ], 260 | "systems": [ 261 | "hyprland", 262 | "systems" 263 | ] 264 | }, 265 | "locked": { 266 | "lastModified": 1750371812, 267 | "narHash": "sha256-D868K1dVEACw17elVxRgXC6hOxY+54wIEjURztDWLk8=", 268 | "owner": "hyprwm", 269 | "repo": "hyprland-qtutils", 270 | "rev": "b13c7481e37856f322177010bdf75fccacd1adc8", 271 | "type": "github" 272 | }, 273 | "original": { 274 | "owner": "hyprwm", 275 | "repo": "hyprland-qtutils", 276 | "type": "github" 277 | } 278 | }, 279 | "hyprlang": { 280 | "inputs": { 281 | "hyprutils": [ 282 | "hyprland", 283 | "hyprutils" 284 | ], 285 | "nixpkgs": [ 286 | "hyprland", 287 | "nixpkgs" 288 | ], 289 | "systems": [ 290 | "hyprland", 291 | "systems" 292 | ] 293 | }, 294 | "locked": { 295 | "lastModified": 1750371198, 296 | "narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=", 297 | "owner": "hyprwm", 298 | "repo": "hyprlang", 299 | "rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b", 300 | "type": "github" 301 | }, 302 | "original": { 303 | "owner": "hyprwm", 304 | "repo": "hyprlang", 305 | "type": "github" 306 | } 307 | }, 308 | "hyprutils": { 309 | "inputs": { 310 | "nixpkgs": [ 311 | "hyprland", 312 | "nixpkgs" 313 | ], 314 | "systems": [ 315 | "hyprland", 316 | "systems" 317 | ] 318 | }, 319 | "locked": { 320 | "lastModified": 1750371096, 321 | "narHash": "sha256-JB1IeJ41y7kWc/dPGV6RMcCUM0Xj2NEK26A2Ap7EM9c=", 322 | "owner": "hyprwm", 323 | "repo": "hyprutils", 324 | "rev": "38f3a211657ce82a1123bf19402199b67a410f08", 325 | "type": "github" 326 | }, 327 | "original": { 328 | "owner": "hyprwm", 329 | "repo": "hyprutils", 330 | "type": "github" 331 | } 332 | }, 333 | "hyprwayland-scanner": { 334 | "inputs": { 335 | "nixpkgs": [ 336 | "hyprland", 337 | "nixpkgs" 338 | ], 339 | "systems": [ 340 | "hyprland", 341 | "systems" 342 | ] 343 | }, 344 | "locked": { 345 | "lastModified": 1750371869, 346 | "narHash": "sha256-lGk4gLjgZQ/rndUkzmPYcgbHr8gKU5u71vyrjnwfpB4=", 347 | "owner": "hyprwm", 348 | "repo": "hyprwayland-scanner", 349 | "rev": "aa38edd6e3e277ae6a97ea83a69261a5c3aab9fd", 350 | "type": "github" 351 | }, 352 | "original": { 353 | "owner": "hyprwm", 354 | "repo": "hyprwayland-scanner", 355 | "type": "github" 356 | } 357 | }, 358 | "nixpkgs": { 359 | "locked": { 360 | "lastModified": 1750365781, 361 | "narHash": "sha256-XE/lFNhz5lsriMm/yjXkvSZz5DfvKJLUjsS6pP8EC50=", 362 | "owner": "NixOS", 363 | "repo": "nixpkgs", 364 | "rev": "08f22084e6085d19bcfb4be30d1ca76ecb96fe54", 365 | "type": "github" 366 | }, 367 | "original": { 368 | "owner": "NixOS", 369 | "ref": "nixos-unstable", 370 | "repo": "nixpkgs", 371 | "type": "github" 372 | } 373 | }, 374 | "pre-commit-hooks": { 375 | "inputs": { 376 | "flake-compat": "flake-compat", 377 | "gitignore": "gitignore", 378 | "nixpkgs": [ 379 | "hyprland", 380 | "nixpkgs" 381 | ] 382 | }, 383 | "locked": { 384 | "lastModified": 1749636823, 385 | "narHash": "sha256-WUaIlOlPLyPgz9be7fqWJA5iG6rHcGRtLERSCfUDne4=", 386 | "owner": "cachix", 387 | "repo": "git-hooks.nix", 388 | "rev": "623c56286de5a3193aa38891a6991b28f9bab056", 389 | "type": "github" 390 | }, 391 | "original": { 392 | "owner": "cachix", 393 | "repo": "git-hooks.nix", 394 | "type": "github" 395 | } 396 | }, 397 | "root": { 398 | "inputs": { 399 | "bird-nix-lib": "bird-nix-lib", 400 | "hyprland": "hyprland", 401 | "nixpkgs": [ 402 | "hyprland", 403 | "nixpkgs" 404 | ], 405 | "systems": "systems" 406 | } 407 | }, 408 | "systems": { 409 | "locked": { 410 | "lastModified": 1689347949, 411 | "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", 412 | "owner": "nix-systems", 413 | "repo": "default-linux", 414 | "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", 415 | "type": "github" 416 | }, 417 | "original": { 418 | "owner": "nix-systems", 419 | "repo": "default-linux", 420 | "type": "github" 421 | } 422 | }, 423 | "xdph": { 424 | "inputs": { 425 | "hyprland-protocols": [ 426 | "hyprland", 427 | "hyprland-protocols" 428 | ], 429 | "hyprlang": [ 430 | "hyprland", 431 | "hyprlang" 432 | ], 433 | "hyprutils": [ 434 | "hyprland", 435 | "hyprutils" 436 | ], 437 | "hyprwayland-scanner": [ 438 | "hyprland", 439 | "hyprwayland-scanner" 440 | ], 441 | "nixpkgs": [ 442 | "hyprland", 443 | "nixpkgs" 444 | ], 445 | "systems": [ 446 | "hyprland", 447 | "systems" 448 | ] 449 | }, 450 | "locked": { 451 | "lastModified": 1750372504, 452 | "narHash": "sha256-VBeZb1oqZM1cqCAZnFz/WyYhO8aF/ImagI7WWg/Z3Og=", 453 | "owner": "hyprwm", 454 | "repo": "xdg-desktop-portal-hyprland", 455 | "rev": "400308fc4f9d12e0a93e483c2e7a649e12af1a92", 456 | "type": "github" 457 | }, 458 | "original": { 459 | "owner": "hyprwm", 460 | "repo": "xdg-desktop-portal-hyprland", 461 | "type": "github" 462 | } 463 | } 464 | }, 465 | "root": "root", 466 | "version": 7 467 | } 468 | --------------------------------------------------------------------------------