A display profile can now pin workspaces to outputs:
docked = {
monitors = [ … ];
workspaces = { "1" = "DP-3"; "9" = "eDP-1"; };
};
The bare list-of-monitors shape still works (either-type; hyprland.nix
normalizes — coercedTo refuses list-of-submodule sources). Pins bake as
Hyprland `workspace` rules from the active profile and apply instantly
on `nomarchy-display-profile apply`: keyword sets the session rule,
moveworkspacetomonitor moves open workspaces over. Pins from a
previously applied profile linger in-session until reload/rebuild
(hyprctl can only add) — inert if the output is absent, noted in the
applier.
V1: flake check green (checks.display-profiles now asserts
workspaceRule) + scratch-downstream eval: baked settings carry both
rule sets, the generated applier has the keyword+dispatch pairs
(bash -n), match/list correct with mixed shapes. V3 queued in
HARDWARE-QUEUE (rides the dock test). Item 15 complete — deleted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
73 lines
3.4 KiB
Nix
73 lines
3.4 KiB
Nix
# Pure monitor-rule composition, split out of hyprland.nix so
|
|
# checks.display-profiles can exercise the overlay semantics directly —
|
|
# no Home Manager evaluation, no state-file fixture.
|
|
{ lib }:
|
|
|
|
rec {
|
|
# Skeleton for an entry built outside the option type (mirrors the
|
|
# monitorType defaults in options.nix).
|
|
defaults = {
|
|
resolution = "preferred"; position = "auto"; scale = 1;
|
|
transform = null; mirror = null; bitdepth = null; vrr = null; extra = "";
|
|
};
|
|
|
|
# An entry -> a Hyprland `monitor` rule. Unset optional fields are
|
|
# omitted; `resolution = "disable"` collapses to the short form.
|
|
rule = m:
|
|
if m.resolution == "disable" then "${m.name}, disable"
|
|
else lib.concatStringsSep ", " (
|
|
[ m.name m.resolution (toString m.position) (toString m.scale) ]
|
|
++ lib.optionals (m.transform != null) [ "transform" (toString m.transform) ]
|
|
++ lib.optionals (m.mirror != null) [ "mirror" m.mirror ]
|
|
++ lib.optionals (m.bitdepth != null) [ "bitdepth" (toString m.bitdepth) ]
|
|
++ lib.optionals (m.vrr != null) [ "vrr" (toString m.vrr) ]
|
|
++ lib.optional (m.extra != "") m.extra
|
|
);
|
|
|
|
# A profile's workspace pinning ({ "1" = "DP-3"; }) -> a Hyprland
|
|
# `workspace` rule. Only the active profile's pins are rendered
|
|
# (hyprland.nix); a workspace rule naming an absent output is inert.
|
|
workspaceRule = ws: out: "${ws}, monitor:${out}";
|
|
|
|
# The full overlay, three layers:
|
|
#
|
|
# 1. base (nomarchy.monitors) with the ACTIVE display profile's entries
|
|
# (settings.displayProfile naming a nomarchy.displayProfiles key)
|
|
# replacing base entries WHOLE, by name — a profile entry is a
|
|
# complete statement about that output. Outputs a profile doesn't
|
|
# name keep their base rules; a cleared/unknown/non-string value
|
|
# ("" after `nomarchy-display-profile base`, or hand-edited junk —
|
|
# the validator only warns) means base config only.
|
|
#
|
|
# 2. Menu-remembered per-output resolutions (settings.monitors:
|
|
# output-name -> "WxH@R", written instantly by the Display menu)
|
|
# overlaid field-level by name: a declared output keeps its
|
|
# position/scale/etc and only its resolution changes; an output
|
|
# covered solely by the `,preferred,auto,1` wildcard (e.g. the
|
|
# laptop's built-in panel) becomes a new rule with default
|
|
# position/scale. The menu pick wins over a hand-set resolution
|
|
# (it's the explicit live action) — EXCEPT onto disabled entries: a
|
|
# stale pick, made while the output was enabled, must not resurrect
|
|
# a panel the active profile (or the base config) disables.
|
|
#
|
|
# 3. Anything still uncovered falls to the wildcard (hyprland.nix
|
|
# prepends it).
|
|
resolve = { base, profiles, active, resOverrides }:
|
|
let
|
|
activeName = if builtins.isString active then active else "";
|
|
profileEntries = profiles.${activeName} or [ ];
|
|
profileNames = map (m: m.name) profileEntries;
|
|
baseMonitors =
|
|
lib.filter (m: ! lib.elem m.name profileNames) base
|
|
++ profileEntries;
|
|
declaredNames = map (m: m.name) baseMonitors;
|
|
in
|
|
map (m: if resOverrides ? ${m.name} && m.resolution != "disable"
|
|
then m // { resolution = resOverrides.${m.name}; }
|
|
else m)
|
|
baseMonitors
|
|
++ lib.mapAttrsToList
|
|
(name: res: defaults // { inherit name; resolution = res; })
|
|
(lib.filterAttrs (name: _: ! lib.elem name declaredNames) resOverrides);
|
|
}
|