hyprland: workspace/rules module init
still some work too be done, the regex types are cursed.
This commit is contained in:
		
							parent
							
								
									0b8a730519
								
							
						
					
					
						commit
						0873f56c28
					
				
					 5 changed files with 447 additions and 22 deletions
				
			
		|  | @ -8,5 +8,7 @@ | ||||||
|     ./monitor.nix |     ./monitor.nix | ||||||
|     ./gestures.nix |     ./gestures.nix | ||||||
|     ./appearance.nix |     ./appearance.nix | ||||||
|  |     ./workspaces.nix | ||||||
|  |     ./options | ||||||
|   ]; |   ]; | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										140
									
								
								modules/home/workstation/hyprland/settings/options/default.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								modules/home/workstation/hyprland/settings/options/default.nix
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,140 @@ | ||||||
|  | { | ||||||
|  |   lib, | ||||||
|  |   config, | ||||||
|  |   ... | ||||||
|  | }: let | ||||||
|  |   inherit | ||||||
|  |     (lib) | ||||||
|  |     flatten | ||||||
|  |     attrValues | ||||||
|  |     concatStringsSep | ||||||
|  |     filterAttrs | ||||||
|  |     mapAttrsToList | ||||||
|  |     boolToString | ||||||
|  |     mkOption | ||||||
|  |     isBool | ||||||
|  |     ; | ||||||
|  |   inherit | ||||||
|  |     (lib.types) | ||||||
|  |     listOf | ||||||
|  |     attrsOf | ||||||
|  |     submodule | ||||||
|  |     nullOr | ||||||
|  |     str | ||||||
|  |     int | ||||||
|  |     bool | ||||||
|  |     oneOf | ||||||
|  |     ; | ||||||
|  | 
 | ||||||
|  |   # our cursed regex types | ||||||
|  |   hyprland = import ./rules.nix {inherit lib;}; | ||||||
|  | 
 | ||||||
|  |   _toString = type: val: | ||||||
|  |     if isBool val | ||||||
|  |     then | ||||||
|  |       if type == "windowrule" | ||||||
|  |       then | ||||||
|  |         if val | ||||||
|  |         then "1" | ||||||
|  |         else "0" | ||||||
|  |       else boolToString val # for workspace rules | ||||||
|  |     else toString val; | ||||||
|  | 
 | ||||||
|  |   # format rules for hyprland | ||||||
|  |   formatRules = type: rule: | ||||||
|  |     concatStringsSep "," ( | ||||||
|  |       mapAttrsToList (name: value: "${name}:${_toString type value}") | ||||||
|  |       (filterAttrs (_: v: v != null) rule) | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |   # workspace handling | ||||||
|  |   mkWorkspaces = mapAttrsToList ( | ||||||
|  |     selector: rules: "${selector},${formatRules "workspacerule" rules}" | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   # rule options | ||||||
|  |   mkRuleOption = type: description: | ||||||
|  |     mkOption { | ||||||
|  |       type = nullOr type; | ||||||
|  |       default = null; | ||||||
|  |       inherit description; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |   # window rule matchers | ||||||
|  |   windowRuleMatchers = submodule { | ||||||
|  |     options = { | ||||||
|  |       class = mkRuleOption str "Window class matcher"; | ||||||
|  |       title = mkRuleOption str "Window title matcher"; | ||||||
|  |       initialClass = mkRuleOption str "Initial window class matcher"; | ||||||
|  |       initialTitle = mkRuleOption str "Initial window title matcher"; | ||||||
|  |       xwayland = mkRuleOption bool "Match XWayland windows"; | ||||||
|  |       floating = mkRuleOption bool "Match floating windows"; | ||||||
|  |       fullscreen = mkRuleOption bool "Match fullscreen windows"; | ||||||
|  |       workspace = mkRuleOption str "Match windows on specific workspace"; | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   # Workspace rules submodule | ||||||
|  |   workspaceRules = submodule { | ||||||
|  |     options = { | ||||||
|  |       name = mkRuleOption str "Default name of workspace"; | ||||||
|  |       monitor = mkRuleOption str "Binds workspace to monitor"; | ||||||
|  |       default = mkRuleOption bool "Set as default workspace for monitor"; | ||||||
|  |       gapsin = mkRuleOption int "Gaps between windows"; | ||||||
|  |       gapsout = mkRuleOption int "Gaps between windows and monitor edges"; | ||||||
|  |       bordersize = mkRuleOption int "Border size around windows"; | ||||||
|  |       border = mkRuleOption bool "Draw borders"; | ||||||
|  |       shadow = mkRuleOption bool "Draw shadows"; | ||||||
|  |       rounding = mkRuleOption bool "Draw rounded corners"; | ||||||
|  |       decorate = mkRuleOption bool "Draw window decorations"; | ||||||
|  |       persistent = mkRuleOption bool "Keep workspace alive when empty"; | ||||||
|  |       on-created-empty = mkRuleOption str "Command to run when workspace created empty"; | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   # Window rules type using our validated types from rules.nix | ||||||
|  |   windowRuleType = listOf (oneOf (attrValues hyprland.types)); | ||||||
|  | 
 | ||||||
|  |   cfg = config.wayland.windowManager.hyprland; | ||||||
|  | in { | ||||||
|  |   options.wayland.windowManager.hyprland = { | ||||||
|  |     # Workspace configuration | ||||||
|  |     workspaces = mkOption { | ||||||
|  |       type = attrsOf workspaceRules; | ||||||
|  |       default = {}; | ||||||
|  |       description = "Workspace-specific configurations"; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     # Window rules configuration | ||||||
|  |     windowRules = mkOption { | ||||||
|  |       type = listOf (submodule { | ||||||
|  |         options = { | ||||||
|  |           matches = mkOption { | ||||||
|  |             type = windowRuleMatchers; | ||||||
|  |             description = "Window matching criteria"; | ||||||
|  |           }; | ||||||
|  |           rules = mkOption { | ||||||
|  |             type = windowRuleType; | ||||||
|  |             description = "Rules to apply to matching windows"; | ||||||
|  |           }; | ||||||
|  |         }; | ||||||
|  |       }); | ||||||
|  |       default = []; | ||||||
|  |       description = "Window-specific rules"; | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   config.wayland.windowManager.hyprland.settings = { | ||||||
|  |     workspace = mkWorkspaces cfg.workspaces; | ||||||
|  |     windowrulev2 = let | ||||||
|  |       # Convert rules to Hyprland format | ||||||
|  |       formatWindowRule = rule: let | ||||||
|  |         matches = formatRules "windowrule" rule.matches; | ||||||
|  |         rules = map (r: "${r},${matches}") rule.rules; | ||||||
|  |       in | ||||||
|  |         rules; | ||||||
|  |     in | ||||||
|  |       flatten (map formatWindowRule cfg.windowRules); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										237
									
								
								modules/home/workstation/hyprland/settings/options/rules.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								modules/home/workstation/hyprland/settings/options/rules.nix
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,237 @@ | ||||||
|  | {lib, ...}: let | ||||||
|  |   inherit (lib) concatStringsSep attrValues; | ||||||
|  |   inherit (lib.types) enum strMatching; | ||||||
|  | 
 | ||||||
|  |   # helper function for constructing regex patterns for hyprland rules | ||||||
|  |   mkRegexTarget = { | ||||||
|  |     name, | ||||||
|  |     regex ? [], | ||||||
|  |     extraRegex ? "", | ||||||
|  |     optional ? false, | ||||||
|  |   }: let | ||||||
|  |     regexPart = concatStringsSep "|" regex; | ||||||
|  |     parameterPart = | ||||||
|  |       if optional | ||||||
|  |       then "( (${regexPart}))?" | ||||||
|  |       else " (${regexPart})"; | ||||||
|  |   in | ||||||
|  |     strMatching "${name}${parameterPart}${extraRegex}"; | ||||||
|  | 
 | ||||||
|  |   # basic windowrules that can be validated with simple enum | ||||||
|  | 
 | ||||||
|  |   basicWindowRules = enum [ | ||||||
|  |     "float" | ||||||
|  |     "tile" | ||||||
|  |     "fullscreen" | ||||||
|  |     "maximize" | ||||||
|  |     "pseudo" | ||||||
|  |     "noinitialfocus" | ||||||
|  |     "pin" | ||||||
|  |     "unset" | ||||||
|  |     "nomaxsize" | ||||||
|  |     "stayfocused" | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   # toggleable options | ||||||
|  |   toggleableRules = [ | ||||||
|  |     # window decoration | ||||||
|  |     "decorate" # window decorations | ||||||
|  |     "noborder" # borders | ||||||
|  |     "noshadow" # shadows | ||||||
|  |     "norounding" # corner rounding | ||||||
|  | 
 | ||||||
|  |     # Visual effects | ||||||
|  |     "noblur" # blur | ||||||
|  |     "noanim" # animations | ||||||
|  |     "nodim" # dimming | ||||||
|  |     "opaque" # opacity enforcement | ||||||
|  |     "forcergbx" # RGB handling | ||||||
|  |     "xray" # blur xray mode | ||||||
|  |     "immediate" # tearing mode | ||||||
|  | 
 | ||||||
|  |     # Behavior | ||||||
|  |     "dimaround" # dim around window | ||||||
|  |     "focusonactivate" # focus on activation request | ||||||
|  |     "nofocus" # disable focus | ||||||
|  |     "stayfocused" # keep focus | ||||||
|  |     "keepaspectratio" # maintain aspect ratio | ||||||
|  |     "nearestneighbor" # nearest neighbor filtering | ||||||
|  |     "nomaxsize" # disable max size | ||||||
|  |     "noshortcutsinhibit" # shortcut inhibiting | ||||||
|  |     "allowsinput" # XWayland input forcing | ||||||
|  |     "renderunfocused" # unfocused rendering | ||||||
|  |     "syncfullscreen" # fullscreen sync | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   # reusable regex pattens to be used in constructing our types | ||||||
|  |   patterns = { | ||||||
|  |     # toggles | ||||||
|  |     onOpt = "1|true|on|yes|0|salse|off|no|toggle|unset"; | ||||||
|  | 
 | ||||||
|  |     # numbers | ||||||
|  |     float = "[+-]?([0-9]*[.])?[0-9]+"; | ||||||
|  |     int = "[0-9]+"; | ||||||
|  |     alpha = ''(0|0?\.[[:digit:]]+|1|1\.0)''; | ||||||
|  |     percentage = "[0-9]+(%)?"; | ||||||
|  | 
 | ||||||
|  |     # position | ||||||
|  |     anchor = "100%-w?-[0-9]+"; | ||||||
|  |     coordinate = "${patterns.percentage}|${patterns.anchor}"; | ||||||
|  |     deg = "(0-360)"; | ||||||
|  | 
 | ||||||
|  |     # identification | ||||||
|  |     numericId = "[1-9][0-9]*"; | ||||||
|  |     sign = "[+-]"; | ||||||
|  |     relative = "${patterns.sign}${patterns.numericId}"; | ||||||
|  |     monitorPrefix = "(m|r)"; | ||||||
|  |     monitorRelative = "${patterns.monitorPrefix}(${patterns.sign}${patterns.numericId}|~${patterns.numericId})"; | ||||||
|  |     emptyModifiers = "[mn]*"; | ||||||
|  |     empty = "empty${patterns.emptyModifiers}"; | ||||||
|  |     mode = "(always|focus|fullscreen|none)"; | ||||||
|  |     ident = "[A-Za-z0-9_][A-Za-z0-9_ -]*"; | ||||||
|  |     name = "name:${patterns.ident}"; | ||||||
|  |     special = "special(:[a-zA-Z0-9_-]+)?"; | ||||||
|  |     previous = "previous(_per_monitor)?"; | ||||||
|  |     openWorkspace = "e(${patterns.sign}${patterns.numericId}|~${patterns.numericId})"; | ||||||
|  | 
 | ||||||
|  |     # colors | ||||||
|  |     rgbValue = ''([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])''; | ||||||
|  |     rgbaHex = ''rgba\([[:xdigit:]]{8}\)''; | ||||||
|  |     rgbaDecimal = ''rgba\(${patterns.rgbValue}, *${patterns.rgbValue}, *${patterns.rgbValue}, *${patterns.alpha}\)''; | ||||||
|  |     rgbHex = ''rgb\([[:xdigit:]]{3}([[:xdigit:]]{3})?\)''; | ||||||
|  |     rgbDecimal = ''rgb\(${patterns.rgbValue}, *${patterns.rgbValue}, *${patterns.rgbValue}\)''; | ||||||
|  |     legacy = ''0x[[:xdigit:]]{8}''; | ||||||
|  |     color = concatStringsSep "|" [ | ||||||
|  |       patterns.rgbaHex | ||||||
|  |       patterns.rgbaDecimal | ||||||
|  |       patterns.rgbHex | ||||||
|  |       patterns.rgbDecimal | ||||||
|  |       patterns.legacy | ||||||
|  |     ]; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   mkToggleableRules = name: | ||||||
|  |     mkRegexTarget { | ||||||
|  |       inherit name; | ||||||
|  |       regex = [patterns.onOpt]; | ||||||
|  |       optional = true; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |   toggleTargets = builtins.listToAttrs (map (name: { | ||||||
|  |       inherit name; | ||||||
|  |       value = mkToggleableRules name; | ||||||
|  |     }) | ||||||
|  |     toggleableRules); | ||||||
|  | 
 | ||||||
|  |   # regex patterns to pass to the workspace rule | ||||||
|  | 
 | ||||||
|  |   types = | ||||||
|  |     { | ||||||
|  |       inherit basicWindowRules; | ||||||
|  |       workspace = mkRegexTarget { | ||||||
|  |         name = "workspace"; | ||||||
|  |         regex = attrValues { | ||||||
|  |           inherit (patterns) numericId relative monitorRelative empty name special previous openWorkspace; | ||||||
|  |         }; | ||||||
|  |         extraRegex = "( +silent)?"; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       monitor = mkRegexTarget { | ||||||
|  |         name = "monitor"; | ||||||
|  |         regex = attrValues { | ||||||
|  |           inherit (patterns) numericId ident; | ||||||
|  |         }; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       group = mkRegexTarget { | ||||||
|  |         name = "group"; | ||||||
|  |         regex = ["set|new|lock|barred|deny|invade|override|unset"]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       size = mkRegexTarget { | ||||||
|  |         name = "size"; | ||||||
|  |         regex = [ | ||||||
|  |           "([0-9]+|[<>][0-9]+)(%|px)?( ([0-9]+|[<>][0-9]+)(%|px)?)?" | ||||||
|  |         ]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       move = mkRegexTarget { | ||||||
|  |         name = "move"; | ||||||
|  |         regex = [ | ||||||
|  |           "(${patterns.coordinate}) (${patterns.coordinate})" | ||||||
|  |           "cursor (${patterns.percentage}) (${patterns.percentage})" | ||||||
|  |           "onscreen cursor (${patterns.percentage}) (${patterns.percentage})" | ||||||
|  |         ]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       bordercolor = mkRegexTarget { | ||||||
|  |         name = "bordercolor"; | ||||||
|  |         regex = [patterns.color]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       idleinhibit = mkRegexTarget { | ||||||
|  |         name = "idleinhibit"; | ||||||
|  |         regex = [patterns.mode]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       opacity = mkRegexTarget { | ||||||
|  |         name = "opacity"; | ||||||
|  |         regex = let | ||||||
|  |           opacityValue = "${patterns.alpha}( override)?"; | ||||||
|  |         in [ | ||||||
|  |           opacityValue | ||||||
|  |           "${opacityValue} ${opacityValue}" | ||||||
|  |           "${opacityValue} ${opacityValue} ${opacityValue}" | ||||||
|  |         ]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       center = mkRegexTarget { | ||||||
|  |         name = "center"; | ||||||
|  |         regex = ["[0-1]"]; | ||||||
|  |         optional = true; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       roundingpower = mkRegexTarget { | ||||||
|  |         name = "roundingpower"; | ||||||
|  |         regex = [patterns.float]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       bordersize = mkRegexTarget { | ||||||
|  |         name = "bordersize"; | ||||||
|  |         regex = [patterns.int]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       rounding = mkRegexTarget { | ||||||
|  |         name = "rounding"; | ||||||
|  |         regex = [patterns.int]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       scrollmouse = mkRegexTarget { | ||||||
|  |         name = "scrollmouse"; | ||||||
|  |         regex = [patterns.float]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       scrolltouchpad = mkRegexTarget { | ||||||
|  |         name = "scrolltouchpad"; | ||||||
|  |         regex = [patterns.float]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       tag = mkRegexTarget { | ||||||
|  |         name = "tag"; | ||||||
|  |         regex = [''[+-]?[[:alnum:]_]+\*?'']; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       maxsize = mkRegexTarget { | ||||||
|  |         name = "maxsize"; | ||||||
|  |         regex = ["${patterns.int} ${patterns.int}"]; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       minsize = mkRegexTarget { | ||||||
|  |         name = "minsize"; | ||||||
|  |         regex = ["${patterns.int} ${patterns.int}"]; | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     // toggleTargets; | ||||||
|  | in { | ||||||
|  |   inherit types; | ||||||
|  | } | ||||||
|  | @ -1,24 +1,33 @@ | ||||||
| { | { | ||||||
|   wayland.windowManager.hyprland.settings = { |   wayland.windowManager.hyprland.windowRules = [ | ||||||
|     windowrulev2 = [ |     # TODO tag games for immediate | ||||||
|       "float,move 191 15,size 924 396,class:^(1Password)$" |     { | ||||||
| 
 |       matches = {title = "TEKKEN™8";}; | ||||||
|       "float, title:^(Picture-in-Picture)$" |       rules = ["immediate"]; | ||||||
|       "pin, title:^(Picture-in-Picture)$" |     } | ||||||
| 
 |     { | ||||||
|       "float,move 237 175, size 1200 720,title:^(File Upload)$" |       matches = {class = "firefox";}; | ||||||
| 
 |       rules = ["idleinhibit fullscreen"]; | ||||||
|       "workspace 4, title:^(Vesktop)$" |     } | ||||||
| 
 |     { | ||||||
|       # Floating BTOP |       matches = {class = "1Password";}; | ||||||
|       "float,title:^(BTOP)$" |       rules = ["center 1" "float" "size 50%"]; | ||||||
|       "size 85%,title:^(BTOP)$" |     } | ||||||
|       "pin,title:^(BTOP)$" |     { | ||||||
|       "center,title:^(BTOP)$" |       matches = {title = "BTOP";}; | ||||||
|       "stayfocused,title:^(BTOP)$" |       rules = ["float" "size 85%" "pin" "center 1" "stayfocused" "dimaround"]; | ||||||
| 
 |     } | ||||||
|       # Tearing |     { | ||||||
|       "immediate, title:^(TEKKEN™8)$" |       matches = {class = "vesktop";}; | ||||||
|  |       rules = ["workspace 4 silent"]; | ||||||
|  |     } | ||||||
|  |     { | ||||||
|  |       matches = {title = "^(Picture-in-Picture)$";}; | ||||||
|  |       rules = ["float" "pin"]; | ||||||
|  |     } | ||||||
|  |     { | ||||||
|  |       matches = {title = "^(Open Files)$";}; | ||||||
|  |       rules = ["center 1" "float" "size 50%"]; | ||||||
|  |     } | ||||||
|   ]; |   ]; | ||||||
|   }; |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										37
									
								
								modules/home/workstation/hyprland/settings/workspaces.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								modules/home/workstation/hyprland/settings/workspaces.nix
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | ||||||
|  | {osConfig, ...}: let | ||||||
|  |   inherit (osConfig.ooknet) hardware; | ||||||
|  |   multiMonitor = builtins.length hardware.monitors > 1; | ||||||
|  |   primary = hardware.primaryMonitor; | ||||||
|  |   secondary = | ||||||
|  |     if multiMonitor | ||||||
|  |     then (builtins.elemAt hardware.monitors 1).name | ||||||
|  |     else primary; | ||||||
|  | in { | ||||||
|  |   wayland.windowManager.hyprland.workspaces = { | ||||||
|  |     "1" = { | ||||||
|  |       name = "terminal"; | ||||||
|  |       monitor = primary; | ||||||
|  |       default = true; | ||||||
|  |     }; | ||||||
|  |     "2" = { | ||||||
|  |       name = "browser"; | ||||||
|  |       monitor = primary; | ||||||
|  |     }; | ||||||
|  |     "3" = { | ||||||
|  |       name = "media"; | ||||||
|  |       monitor = secondary; | ||||||
|  |       default = true; | ||||||
|  |     }; | ||||||
|  |     "4" = { | ||||||
|  |       name = "discord"; | ||||||
|  |       monitor = secondary; | ||||||
|  |     }; | ||||||
|  |     "5" = { | ||||||
|  |       name = "gaming"; | ||||||
|  |       monitor = primary; | ||||||
|  |     }; | ||||||
|  |     "r[6-9]" = { | ||||||
|  |       monitor = primary; | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue