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