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
|
||||
./gestures.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 = {
|
||||
windowrulev2 = [
|
||||
"float,move 191 15,size 924 396,class:^(1Password)$"
|
||||
|
||||
"float, title:^(Picture-in-Picture)$"
|
||||
"pin, title:^(Picture-in-Picture)$"
|
||||
|
||||
"float,move 237 175, size 1200 720,title:^(File Upload)$"
|
||||
|
||||
"workspace 4, title:^(Vesktop)$"
|
||||
|
||||
# Floating BTOP
|
||||
"float,title:^(BTOP)$"
|
||||
"size 85%,title:^(BTOP)$"
|
||||
"pin,title:^(BTOP)$"
|
||||
"center,title:^(BTOP)$"
|
||||
"stayfocused,title:^(BTOP)$"
|
||||
|
||||
# Tearing
|
||||
"immediate, title:^(TEKKEN™8)$"
|
||||
];
|
||||
};
|
||||
wayland.windowManager.hyprland.windowRules = [
|
||||
# TODO tag games for immediate
|
||||
{
|
||||
matches = {title = "TEKKEN™8";};
|
||||
rules = ["immediate"];
|
||||
}
|
||||
{
|
||||
matches = {class = "firefox";};
|
||||
rules = ["idleinhibit fullscreen"];
|
||||
}
|
||||
{
|
||||
matches = {class = "1Password";};
|
||||
rules = ["center 1" "float" "size 50%"];
|
||||
}
|
||||
{
|
||||
matches = {title = "BTOP";};
|
||||
rules = ["float" "size 85%" "pin" "center 1" "stayfocused" "dimaround"];
|
||||
}
|
||||
{
|
||||
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