From 70d4cdf5fa78af8f9427fd955dff45982f89ae9b Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Mon, 9 Nov 2026 19:35:39 +0300 Subject: [PATCH] fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Install lucid --- host/Eclipse/modules/lucid/default.nix | 4 + host/Impreza/modules/lucid/default.nix | 9 + host/Rias/modules/lucid/default.nix | 21 ++ modules/user/lucid/default.nix | 366 ++++++---------------- user/chest/home.nix | 9 +- user/chest/modules/lucid/default.nix | 185 +++++++++++ user/sweetbread/home.nix | 10 +- user/sweetbread/modules/lucid/default.nix | 28 ++ user/sweetbread/modules/lucid/rias.nix | 13 + 9 files changed, 372 insertions(+), 273 deletions(-) create mode 100644 host/Eclipse/modules/lucid/default.nix create mode 100644 host/Impreza/modules/lucid/default.nix create mode 100644 host/Rias/modules/lucid/default.nix create mode 100644 user/chest/modules/lucid/default.nix create mode 100644 user/sweetbread/modules/lucid/default.nix create mode 100644 user/sweetbread/modules/lucid/rias.nix diff --git a/host/Eclipse/modules/lucid/default.nix b/host/Eclipse/modules/lucid/default.nix new file mode 100644 index 0000000..a38f56f --- /dev/null +++ b/host/Eclipse/modules/lucid/default.nix @@ -0,0 +1,4 @@ +{ ... }: +{ + lucid.liveDecoder = "cuda"; +} diff --git a/host/Impreza/modules/lucid/default.nix b/host/Impreza/modules/lucid/default.nix new file mode 100644 index 0000000..3044d9f --- /dev/null +++ b/host/Impreza/modules/lucid/default.nix @@ -0,0 +1,9 @@ +{ config, ... }: +{ + lucid.prefsSeed = ./lucid-prefs-impreza.json; + lucid.widgetSeed = ./lucid-widgets-impreza.json; + lucid.initialFiles = { + "${config.home.homeDirectory}/.config/hypr/lucid-settings.lua" = ./lucid-settings-impreza.lua; + "${config.home.homeDirectory}/.config/hypr/lucid-glass.lua" = ./lucid-glass-impreza.lua; + }; +} diff --git a/host/Rias/modules/lucid/default.nix b/host/Rias/modules/lucid/default.nix new file mode 100644 index 0000000..365454a --- /dev/null +++ b/host/Rias/modules/lucid/default.nix @@ -0,0 +1,21 @@ +{ config, lib, ... }: +{ + lucid.widgetSeed = ./lucid-widgets-rias.json; + lucid.liveDecoder = "cuda"; + lucid.bindsModule = lib.mkDefault "${config.lucid.upstreamSource}/support/hypr/modules/binds.lua"; + lucid.luaModules = { + "modules/decorations.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/decorations.lua"; + "modules/gestures.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/gestures.lua"; + "modules/input.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/input.lua"; + "modules/layout.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/layout.lua"; + "modules/layerrules.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/layerrules.lua"; + "modules/lumen.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/lumen.lua"; + "modules/misc.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/misc.lua"; + "modules/plugins.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/plugins.lua"; + "modules/windowrules.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/windowrules.lua"; + "modules/monitors.lua" = { content = "${config.lucid.upstreamSource}/support/hypr/modules/monitors.lua"; autoLoad = false; }; + "modules/glass.lua" = { content = "${config.lucid.upstreamSource}/support/hypr/modules/glass.lua"; autoLoad = false; }; + "modules/settings.lua" = { content = "${config.lucid.upstreamSource}/support/hypr/modules/settings.lua"; autoLoad = false; }; + "modules/startup.lua" = { content = "${config.lucid.upstreamSource}/support/hypr/modules/startup.lua"; autoLoad = false; }; + }; +} diff --git a/modules/user/lucid/default.nix b/modules/user/lucid/default.nix index 605b1b5..0ba1878 100644 --- a/modules/user/lucid/default.nix +++ b/modules/user/lucid/default.nix @@ -1,67 +1,5 @@ -{ config, lib, osConfig, pkgs, ... }: +{ config, lib, pkgs, ... }: let - host = osConfig.networking.hostName; - isChest = config.home.username == "chest"; - isSweetbread = config.home.username == "sweetbread"; - isRias = host == "Rias"; - sweetbreadLucid = ../../../user/sweetbread/modules/lucid; - riasLucid = ../../../host/Rias/modules/lucid; - imprezaLucid = ../../../host/Impreza/modules/lucid; - specialBindIds = [ "scratchpad" "scratchpad-stash" "special-music" "special-comms" "special-todo" "special-sysmon" "daemons-workspace" ]; - specialSeed = pkgs.writeText "lucid-special-bindings.json" (builtins.toJSON { - version = 1; - binds = builtins.filter (entry: builtins.elem entry.id specialBindIds) - (builtins.fromJSON (builtins.readFile ./lucid-keybind-catalog.json)).binds; - }); - chestBindingsScript = pkgs.writeText "lucid-chest-bindings.py" '' -import hashlib -import json -import re -import sys -from collections import Counter - -source, reference, special, output = sys.argv[1:] -binds = json.loads(source) -refs = json.load(open(reference))["binds"] -specials = json.load(open(special))["binds"] -counts = Counter() -result = [] -for entry in binds: - args = entry["_args"] - if not 2 <= len(args) <= 3 or args[1].get("_type") != "lua-inline": - raise ValueError(f"Unsupported Hyprland binding: {entry}") - keys, expr = args[0], args[1]["expr"] - opts = args[2] if len(args) == 3 else {} - signature = json.dumps([keys.upper().replace(" ", ""), opts], sort_keys=True) - counts[signature] += 1 - identifier = hashlib.sha256(signature.encode()).hexdigest()[:12] - b = {"id": f"chest-{identifier}-{counts[signature]}", "keys": keys} - match = next((r for r in refs if r.get("keys", "").upper().replace(" ", "") == keys.upper().replace(" ", "") and r.get("opts", {}) == opts), None) - b["desc"] = match.get("desc", keys) if match else keys - b["category"] = match.get("category", "Other") if match else "Other" - workspace = re.search(r"workspace = (\d+)", expr) - if workspace and expr.startswith("hl.dsp.focus("): - b.update(desc=f"Focus workspace {workspace.group(1)}", category="Workspaces") - elif workspace and expr.startswith("hl.dsp.window.move("): - b.update(desc=f"Move window to workspace {workspace.group(1)}", category="Workspaces") - if keys.upper().replace(" ", "") == "SUPER+SHIFT+S": - b.update(desc="Region screenshot and edit", category="Screenshots") - m = re.fullmatch(r'hl\.dsp\.exec_cmd\(("(?:\\.|[^"\\])*")\)\s*', expr, re.S) - if m: - cmd = json.loads(m.group(1)) - cmd = re.sub(r'/nix/store/[0-9a-z]{32}-[^/\s"\\]+/bin/([A-Za-z0-9_.+-]+)', r'\1', cmd) - b.update(type="exec", cmd=cmd) - else: - b.update(type="lua", lua=expr) - if opts: - b["opts"] = opts - result.append(b) -json.dump({"version": 1, "binds": result + specials}, open(output, "w"), indent=2) - ''; - chestSeed = pkgs.runCommand "lucid-chest-bindings.json" { nativeBuildInputs = [ pkgs.python3 ]; } '' - python3 ${chestBindingsScript} ${lib.escapeShellArg (builtins.toJSON config.lucidHyprlandBinds)} \ - ${./lucid-keybind-catalog.json} ${specialSeed} "$out" - ''; staticBindKeys = map (entry: builtins.elemAt entry._args 0) config.wayland.windowManager.hyprland.settings.bind; nonRiasBinds = pkgs.runCommand "lucid-nix-binds.lua" { nativeBuildInputs = [ pkgs.python3 ]; } '' python3 - ${source}/support/hypr/modules/binds.lua "$out" ${lib.escapeShellArg (builtins.toJSON staticBindKeys)} <<'PY' @@ -110,49 +48,6 @@ source = source.replace(fallback_text, 'Nix bindings remain active.', 1) pathlib.Path(sys.argv[2]).write_text(source) PY ''; - chestBinds = pkgs.runCommand "lucid-chest-loader.lua" { nativeBuildInputs = [ pkgs.python3 ]; } '' - python3 - ${source}/support/hypr/modules/binds.lua "$out" <<'PYCODE' -import pathlib -import sys -source = pathlib.Path(sys.argv[1]).read_text() -old = '{ id = "terminal", keys = "F9", type = "exec", cmd = "kitty" }' -new = '{ id = "terminal", keys = "SUPER + RETURN", type = "exec", cmd = "ghostty" }' -if source.count(old) != 1: - raise SystemExit("Lucid emergency bindings changed") -pathlib.Path(sys.argv[2]).write_text(source.replace(old, new, 1)) -PYCODE - ''; - chestMonitors = pkgs.runCommand "lucid-chest-monitors.lua" { nativeBuildInputs = [ pkgs.python3 ]; } '' - python3 - ${source}/support/hypr/modules/monitors.lua "$out" <<'PY' -import pathlib -import sys - -source = pathlib.Path(sys.argv[1]).read_text() -default_rule = """hl.monitor({ - output = "", - mode = M.defaults.mode, - position = M.defaults.position, - scale = M.defaults.scale, -}) -""" -if source.count(default_rule) != 1: - raise SystemExit("Lucid monitor defaults changed; refusing to replace host monitor rules") -pathlib.Path(sys.argv[2]).write_text(source.replace(default_rule, "", 1)) -PY - ''; - chestBorders = pkgs.runCommand "lucid-chest-decorations.lua" { nativeBuildInputs = [ pkgs.python3 ]; } '' - python3 - ${source}/support/hypr/modules/decorations.lua "$out" <<'PY' -import pathlib -import sys - -source = pathlib.Path(sys.argv[1]).read_text() -marker = "local active_border, inactive_border = borders()" -if source.count(marker) != 1: - raise SystemExit("Lucid border module changed; refusing to replace host appearance settings") -pathlib.Path(sys.argv[2]).write_text(source.split(marker, 1)[0]) -PY - ''; - liveDecoder = if builtins.elem osConfig.networking.hostName [ "Rias" "Eclipse" ] then "cuda" else "vaapi"; vaultwardenLauncherPatch = pkgs.writeText "lucid-vaultwarden-launcher.py" '' import pathlib import sys @@ -360,31 +255,6 @@ dock_path.write_text(dock) ''; wallpaperDir = "${config.home.homeDirectory}/Wallpapers"; wallpaperScript = "${config.home.homeDirectory}/.config/hypr/scripts/wallpaper/set-wallpaper.sh"; - widgetLayout = if osConfig.networking.hostName == "Rias" - then riasLucid + "/lucid-widgets-rias.json" - else if isChest && host == "Impreza" then imprezaLucid + "/lucid-widgets-impreza.json" - else null; - bindSeed = if isSweetbread then sweetbreadLucid + "/lucid-keybinds.json" - else chestSeed; - specialCustomValue = builtins.toJSON [ { - key = "daemons"; - label = "Daemons"; - glyph = "build"; - on = true; - apps = ""; - } ]; - genericPrefsSeed = pkgs.writeText "lucid-default-prefs.json" (builtins.toJSON ( - (builtins.fromJSON (builtins.readFile (sweetbreadLucid + "/lucid-prefs.json"))) // { - specialCustom = if isRias then "[]" else specialCustomValue; - } - )); - chestPrefsSeed = pkgs.runCommand "lucid-chest-prefs.json" { nativeBuildInputs = [ pkgs.jq ]; } '' - ${pkgs.jq}/bin/jq --arg specialCustom ${lib.escapeShellArg specialCustomValue} \ - '.specialCustom = $specialCustom' ${source}/defaults/prefs.json > "$out" - ''; - prefsSeed = if isSweetbread then genericPrefsSeed - else if isChest && host == "Impreza" then imprezaLucid + "/lucid-prefs-impreza.json" - else chestPrefsSeed; defaultWallpaper = "${wallpaperDir}/bluehour.jpg"; hyprLua = import ../hyprland/lua.nix { inherit lib; }; colors = config.lib.stylix.colors.withHashtag; @@ -450,29 +320,6 @@ PY user = true; }); themeName = pkgs.writeText "lucid-current-theme" "stylix\n"; - chestOsdPatch = pkgs.writeText "lucid-chest-osd.patch" '' - --- a/lucidosd/Osd.qml - +++ b/lucidosd/Osd.qml - @@ -26 +26 @@ - - readonly property int brightnessPercent: osdWindow.maxBrightness > 0 ? Math.round((parseInt(brightnessFile.text()) / osdWindow.maxBrightness) * 100) : 0 - + property int brightnessPercent: 0 - @@ -378,2 +378,15 @@ - onFileChanged: reload() - + onLoaded: { - + const value = parseInt(brightnessFile.text()); - + if (osdWindow.maxBrightness > 0 && !isNaN(value)) - + osdWindow.brightnessPercent = Math.round(value / osdWindow.maxBrightness * 100); - + } - } - + - + Timer { - + id: chestBrightnessPoll - + interval: 500 - + repeat: true - + running: osdWindow.backlightDevice !== "" - + onTriggered: brightnessFile.reload() - + } - ''; themeAutoPatch = pkgs.writeText "lucid-theme-auto.patch" '' --- a/Prefs.qml +++ b/Prefs.qml @@ -578,19 +425,6 @@ PY ''; qmlPackages = with pkgs.qt6; [ qt5compat qtmultimedia ]; tesseract = pkgs.tesseract4.override { enableLanguages = [ "eng" "rus" ]; }; - shellFile = if isChest then pkgs.writeText "lucid-chest-shell.qml" (builtins.replaceStrings [ - ''import "./lucidlock"'' - " Lock {\n }\n" - " void Clip.probed;" - ] [ - "" - "" - " void Notifs.count;\n void KdeConnect.installed;\n void HyprConfig.moduleProbed;\n void Monitors.moduleProbed;\n void Glass.moduleProbed;\n void Startup.moduleProbed;\n void Clip.probed;" - ] (builtins.readFile ./lucid-shell.qml)) else ./lucid-shell.qml; - prefsFilter = if isChest then - ''.barEnabled = true | .widgetOnTop = false | .widgetHideFullscreen = true | .fontFamily = $font | .liveWallpapers = false | .idleLock = false | .idleLockBeforeSleep = false | .launcherPowerButtons = "suspend,reboot,shutdown" | .powerModuleActions = "suspend,hibernate,logout,reboot,shutdown"'' - else - ''.barEnabled = true | .widgetOnTop = false | .widgetHideFullscreen = true | .fontFamily = $font | .wallpaperFolder = $wallpaperDir | .liveDecoder = $decoder''; lucid = pkgs.writeShellScript "lucid" '' export NIXPKGS_QT6_QML_IMPORT_PATH="${lib.makeSearchPath "lib/qt-6/qml" qmlPackages}:''${NIXPKGS_QT6_QML_IMPORT_PATH:-}" export QT_PLUGIN_PATH="${lib.makeSearchPath "lib/qt-6/plugins" qmlPackages}:''${QT_PLUGIN_PATH:-}" @@ -619,10 +453,82 @@ PY exec "${wallpaperScript}" "$selected" ''; in { + options.lucid = { + upstreamSource = lib.mkOption { + type = lib.types.path; + default = source; + description = "Patched Lucid source tree used by profile modules."; + }; + keybindSeed = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "Optional initial Lucid keybinding file."; + }; + prefsSeed = lib.mkOption { + type = lib.types.path; + default = "${source}/defaults/prefs.json"; + description = "Initial Lucid preferences file."; + }; + widgetSeed = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "Optional initial Lucid widget layout."; + }; + initialFiles = lib.mkOption { + type = lib.types.attrsOf lib.types.path; + default = {}; + description = "Files to seed once at their absolute destinations."; + }; + managedFiles = lib.mkOption { + type = lib.types.attrsOf lib.types.path; + default = {}; + description = "Lucid files installed at their absolute destinations."; + }; + prefsFilter = lib.mkOption { + type = lib.types.lines; + default = ''.barEnabled = true | .widgetOnTop = false | .widgetHideFullscreen = true | .fontFamily = $font | .wallpaperFolder = $wallpaperDir | .liveDecoder = $decoder''; + description = "Declarative jq updates applied to Lucid preferences."; + }; + liveDecoder = lib.mkOption { + type = lib.types.str; + default = "vaapi"; + description = "Hardware decoder used by Lucid wallpaper playback."; + }; + bindsModule = lib.mkOption { + type = lib.types.path; + default = nonRiasBinds; + description = "Lua module that loads this profile's Hyprland bindings."; + }; + luaModules = lib.mkOption { + type = lib.types.attrs; + default = {}; + description = "Additional Lucid Hyprland Lua modules for this profile."; + }; + extraHyprlandConfig = lib.mkOption { + type = lib.types.lines; + default = ""; + description = "Profile-specific Hyprland Lua configuration loaded after the shared Lucid setup."; + }; + startWallpaper = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Apply the configured wallpaper before Lucid starts."; + }; + installWallpaperSupport = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Install Lucid wallpaper integration scripts."; + }; + shellFile = lib.mkOption { + type = lib.types.path; + default = ./lucid-shell.qml; + description = "Lucid Quickshell entry point for this user profile."; + }; + }; + programs.waybar.enable = lib.mkForce false; services.mako.enable = lib.mkForce false; - home.packages = with pkgs; [ cliphist ffmpeg grim imagemagick libnotify pywal16 wf-recorder tesseract ] - ++ lib.optionals isChest [ brightnessctl playerctl slurp swappy ]; + home.packages = with pkgs; [ cliphist ffmpeg grim imagemagick libnotify pywal16 wf-recorder tesseract ]; home.activation.installLucid = lib.hm.dag.entryAfter [ "writeBoundary" ] '' target="${config.home.homeDirectory}/.config/quickshell" @@ -642,26 +548,6 @@ in { --exclude='/lucidkeys/state.json' \ --exclude='/lucidwidgets/widgets.json' \ "${source}/" "$target/" - ${lib.optionalString isChest '' - # Notifs refers to Lockscreen even without a lock surface. Keep its - # singleton from taking over logind lock signals or its LockedHint. - lock_qml="$target/Lockscreen.qml" - if ! grep -Fq 'return; // Lucid lock disabled for chest' "$lock_qml"; then - grep -Fq 'function lock(settle) {' "$lock_qml" && \ - grep -Fq 'function pushLockedHint() {' "$lock_qml" && \ - grep -Fq 'id: logindWatch' "$lock_qml" || { - echo "Lucid lock integration changed; refusing to leave it active for chest" >&2 - exit 1 - } - $DRY_RUN_CMD sed -i \ - -e '/function lock(settle) {/a\ return; // Lucid lock disabled for chest' \ - -e '/function pushLockedHint() {/a\ return; // Lucid lock disabled for chest' \ - -e '/id: logindWatch/{n;n;s/running: true/running: false/;}' "$lock_qml" - fi - if ! grep -Fq 'id: chestBrightnessPoll' "$target/lucidosd/Osd.qml"; then - $DRY_RUN_CMD ${pkgs.patch}/bin/patch --batch --forward -p1 -d "$target" < ${chestOsdPatch} - fi - ''} $DRY_RUN_CMD ${pkgs.patch}/bin/patch --batch -p1 -d "$target" < ${./idle-work.patch} if ! grep -Fq 'mode === "auto"' "$target/Prefs.qml"; then $DRY_RUN_CMD ${pkgs.patch}/bin/patch --batch --forward -p1 -d "$target" < ${themeAutoPatch} @@ -701,18 +587,12 @@ in { // Nix focus workaround for Hyprland 0.57: detach pointer and keyboard focus while menus are open.\ Quickshell.execDetached(["hyprctl", "eval", "hl.config({ input = { follow_mouse = " + (dockWindow.menuOpen ? 2 : 1) + " } })"]);' "$dock_qml" fi - $DRY_RUN_CMD install -m644 ${shellFile} "$target/shell.qml" + $DRY_RUN_CMD install -m644 ${config.lucid.shellFile} "$target/shell.qml" $DRY_RUN_CMD install -m644 ${./LucidBar.qml} "$target/LucidBar.qml" $DRY_RUN_CMD install -m644 ${./Vaultwarden.qml} "$target/Vaultwarden.qml" - ${lib.optionalString isSweetbread '' - $DRY_RUN_CMD install -m644 ${sweetbreadLucid}/Agenda.qml "$target/Agenda.qml" - $DRY_RUN_CMD install -m644 ${sweetbreadLucid}/CalendarSource.qml "$target/CalendarSource.qml" - $DRY_RUN_CMD install -m644 ${sweetbreadLucid}/ClockCalendar.qml "$target/lucidbar/ClockCalendar.qml" - $DRY_RUN_CMD install -m644 ${sweetbreadLucid}/thunderbird-calendar.py "$target/thunderbird-calendar.py" - ''} $DRY_RUN_CMD install -m644 ${./WidgetLayer.qml} "$target/lucidwidgets/WidgetLayer.qml" $DRY_RUN_CMD install -m644 ${./WidgetsPage.qml} "$target/lucidprefs/WidgetsPage.qml" - ${lib.optionalString (!isChest) '' + ${lib.optionalString config.lucid.installWallpaperSupport '' $DRY_RUN_CMD mkdir -p "${wallpaperDir}" \ "${config.home.homeDirectory}/.config/hypr/scripts/wallpaper" $DRY_RUN_CMD install -m755 "${source}/support/wallpaper/set-wallpaper.sh" \ @@ -725,7 +605,7 @@ in { lucid_dir="${config.home.homeDirectory}/.config/lucid" $DRY_RUN_CMD mkdir -p "$lucid_dir/themes" if [ ! -s "$lucid_dir/keybinds.json" ]; then - $DRY_RUN_CMD install -m644 ${bindSeed} "$lucid_dir/keybinds.json" + ${lib.optionalString (config.lucid.keybindSeed != null) ''$DRY_RUN_CMD install -m644 ${config.lucid.keybindSeed} "$lucid_dir/keybinds.json"''} fi $DRY_RUN_CMD ${pkgs.rsync}/bin/rsync -rltp --chmod=Du+rwx,Fu+rw --ignore-existing \ "${source}/support/lucid/themes/" "$lucid_dir/themes/" @@ -741,14 +621,14 @@ in { "$lucid_dir/apply-theme.sh" if [ ! -e "$target/lucidprefs/prefs.json" ]; then - $DRY_RUN_CMD install -m644 ${prefsSeed} "$target/lucidprefs/prefs.json" + $DRY_RUN_CMD install -m644 ${config.lucid.prefsSeed} "$target/lucidprefs/prefs.json" fi if [ -z "''${DRY_RUN_CMD:-}" ] && [ -f "$target/lucidprefs/prefs.json" ]; then prefs_tmp=$(${pkgs.coreutils}/bin/mktemp "$target/lucidprefs/prefs.json.XXXXXX") if ${pkgs.jq}/bin/jq --arg font ${lib.escapeShellArg config.stylix.fonts.sansSerif.name} \ --arg wallpaperDir ${lib.escapeShellArg wallpaperDir} \ - --arg decoder ${lib.escapeShellArg liveDecoder} \ - ${lib.escapeShellArg prefsFilter} \ + --arg decoder ${lib.escapeShellArg config.lucid.liveDecoder} \ + ${lib.escapeShellArg config.lucid.prefsFilter} \ "$target/lucidprefs/prefs.json" > "$prefs_tmp"; then ${pkgs.coreutils}/bin/chmod 644 "$prefs_tmp" ${pkgs.coreutils}/bin/mv "$prefs_tmp" "$target/lucidprefs/prefs.json" @@ -757,21 +637,21 @@ in { exit 1 fi fi - ${lib.optionalString (widgetLayout != null) '' + ${lib.optionalString (config.lucid.widgetSeed != null) '' if [ ! -e "$target/lucidwidgets/widgets.json" ]; then - $DRY_RUN_CMD install -m644 ${widgetLayout} "$target/lucidwidgets/widgets.json" + $DRY_RUN_CMD install -m644 ${config.lucid.widgetSeed} "$target/lucidwidgets/widgets.json" fi ''} - ${lib.optionalString (isChest && host == "Impreza") '' - if [ ! -e "${config.home.homeDirectory}/.config/hypr/lucid-settings.lua" ]; then - $DRY_RUN_CMD install -m644 ${imprezaLucid}/lucid-settings-impreza.lua \ - "${config.home.homeDirectory}/.config/hypr/lucid-settings.lua" + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (destination: sourceFile: '' + if [ ! -e ${lib.escapeShellArg destination} ]; then + $DRY_RUN_CMD mkdir -p ${lib.escapeShellArg (builtins.dirOf destination)} + $DRY_RUN_CMD install -m644 ${sourceFile} ${lib.escapeShellArg destination} fi - if [ ! -e "${config.home.homeDirectory}/.config/hypr/lucid-glass.lua" ]; then - $DRY_RUN_CMD install -m644 ${imprezaLucid}/lucid-glass-impreza.lua \ - "${config.home.homeDirectory}/.config/hypr/lucid-glass.lua" - fi - ''} + '') config.lucid.initialFiles)} + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (destination: sourceFile: '' + $DRY_RUN_CMD mkdir -p ${lib.escapeShellArg (builtins.dirOf destination)} + $DRY_RUN_CMD install -m644 ${sourceFile} ${lib.escapeShellArg destination} + '') config.lucid.managedFiles)} for pair in \ 'blur.json lucidbar/blur.json' \ @@ -815,73 +695,25 @@ in { ExecStart = "${lucid}"; Restart = "on-failure"; RestartSec = 2; - } // lib.optionalAttrs (!isChest) { ExecStartPre = "${startWallpaper}"; }; + } // lib.optionalAttrs config.lucid.startWallpaper { ExecStartPre = "${startWallpaper}"; }; Install.WantedBy = [ "hyprland-session.target" ]; }; wayland.windowManager.hyprland = { settings = { on = [ (hyprLua.onStart [ "systemctl --user start lucid.service" ]) ]; - bind = lib.mkIf (isRias || isChest) (lib.mkForce []); - window_rule = lib.mkIf isChest [ - { - name = "float-lucid-settings"; - match = { class = "org.quickshell"; title = "Lucid Settings"; }; - float = true; - size = [ 1180 800 ]; - center = true; - } - { - name = "float-lucid-choosers"; - match = { - class = "^(org\\.gnome\\.Nautilus|xdg-desktop-portal-gtk|xdg-desktop-portal-gnome|zenity|org\\.kde\\.kdialog|kdialog)$"; - title = "^(Choose an account picture|Choose a template|Choose a colour scheme|Export the palette|Save received files to|Send files|Send to .+)$"; - }; - float = true; - center = true; - } - ]; }; # Home Manager autoloads these before its generated settings. Lucid's # stateful UI modules are installed too, then required after the Nix base # config below so saved UI choices take effect without losing that base. extraLuaFiles = { - "modules/binds.lua" = builtins.toPath (if isRias then "${source}/support/hypr/modules/binds.lua" else if isChest then "${chestBinds}" else "${nonRiasBinds}"); + "modules/binds.lua" = config.lucid.bindsModule; "modules/json.lua" = builtins.toPath "${source}/support/hypr/modules/json.lua"; "modules/specials.lua" = { content = builtins.toPath "${source}/support/hypr/modules/specials.lua"; autoLoad = false; }; - } // lib.optionalAttrs isRias { - "modules/decorations.lua" = builtins.toPath "${source}/support/hypr/modules/decorations.lua"; - "modules/gestures.lua" = builtins.toPath "${source}/support/hypr/modules/gestures.lua"; - "modules/input.lua" = builtins.toPath "${source}/support/hypr/modules/input.lua"; - "modules/layout.lua" = builtins.toPath "${source}/support/hypr/modules/layout.lua"; - "modules/layerrules.lua" = builtins.toPath "${source}/support/hypr/modules/layerrules.lua"; - "modules/lumen.lua" = builtins.toPath "${source}/support/hypr/modules/lumen.lua"; - "modules/misc.lua" = builtins.toPath "${source}/support/hypr/modules/misc.lua"; - "modules/plugins.lua" = builtins.toPath "${source}/support/hypr/modules/plugins.lua"; - "modules/windowrules.lua" = builtins.toPath "${source}/support/hypr/modules/windowrules.lua"; - } // lib.optionalAttrs isChest { - "modules/decorations.lua" = builtins.toPath "${chestBorders}"; - } // lib.optionalAttrs (isRias || isChest) { - "modules/glass.lua" = { - content = builtins.toPath "${source}/support/hypr/modules/glass.lua"; - autoLoad = false; - }; - "modules/monitors.lua" = { - content = builtins.toPath (if isChest then "${chestMonitors}" else "${source}/support/hypr/modules/monitors.lua"); - autoLoad = false; - }; - "modules/settings.lua" = { - content = builtins.toPath "${source}/support/hypr/modules/settings.lua"; - autoLoad = false; - }; - "modules/startup.lua" = { - content = builtins.toPath "${source}/support/hypr/modules/startup.lua"; - autoLoad = false; - }; - }; + } // config.lucid.luaModules; # Nix owns session startup and environment exports; don't load Lucid's # generic autostart/env defaults on top of the existing host setup. extraConfig = '' @@ -889,12 +721,6 @@ in { package.path = config_home .. "/hypr/?.lua;" .. config_home .. "/hypr/?/init.lua;" .. package.path require("modules.specials") LucidSpecials.apply() - '' + lib.optionalString isChest '' - require("modules.monitors") - '' + lib.optionalString (isRias || isChest) '' - require("modules.settings") - require("modules.glass") - require("modules.startup") - ''; + '' + config.lucid.extraHyprlandConfig; }; } diff --git a/user/chest/home.nix b/user/chest/home.nix index 494cc00..2f7c6c6 100644 --- a/user/chest/home.nix +++ b/user/chest/home.nix @@ -1,7 +1,14 @@ -{ pkgs, pkgs-pinned, host, ... }: { +{ pkgs, pkgs-pinned, lib, osConfig, ... }: { imports = [ ../../modules/user/packages/cs_utils.nix ../../modules/user/lucid + ./modules/lucid + ] ++ lib.optionals (osConfig.networking.hostName == "Impreza") [ + ../../host/Impreza/modules/lucid + ] ++ lib.optionals (osConfig.networking.hostName == "Rias") [ + ../../host/Rias/modules/lucid + ] ++ lib.optionals (osConfig.networking.hostName == "Eclipse") [ + ../../host/Eclipse/modules/lucid ]; disabledModules = [ ../../modules/user/packages/art.nix diff --git a/user/chest/modules/lucid/default.nix b/user/chest/modules/lucid/default.nix new file mode 100644 index 0000000..157652f --- /dev/null +++ b/user/chest/modules/lucid/default.nix @@ -0,0 +1,185 @@ +{ config, lib, pkgs, ... }: +let + source = config.lucid.upstreamSource; + specialBindIds = [ "scratchpad" "scratchpad-stash" "special-music" "special-comms" "special-todo" "special-sysmon" "daemons-workspace" ]; + specialSeed = pkgs.writeText "lucid-special-bindings.json" (builtins.toJSON { + version = 1; + binds = builtins.filter (entry: builtins.elem entry.id specialBindIds) + (builtins.fromJSON (builtins.readFile ../../../../modules/user/lucid/lucid-keybind-catalog.json)).binds; + }); + chestBindingsScript = pkgs.writeText "lucid-chest-bindings.py" '' +import hashlib, json, re, sys +from collections import Counter +source, reference, special, output = sys.argv[1:] +binds = json.loads(source) +refs = json.load(open(reference))["binds"] +specials = json.load(open(special))["binds"] +counts, result = Counter(), [] +for entry in binds: + args = entry["_args"] + if not 2 <= len(args) <= 3 or args[1].get("_type") != "lua-inline": + raise ValueError(f"Unsupported Hyprland binding: {entry}") + keys, expr = args[0], args[1]["expr"] + opts = args[2] if len(args) == 3 else {} + signature = json.dumps([keys.upper().replace(" ", ""), opts], sort_keys=True) + counts[signature] += 1 + identifier = hashlib.sha256(signature.encode()).hexdigest()[:12] + b = {"id": f"chest-{identifier}-{counts[signature]}", "keys": keys} + match = next((r for r in refs if r.get("keys", "").upper().replace(" ", "") == keys.upper().replace(" ", "") and r.get("opts", {}) == opts), None) + b["desc"] = match.get("desc", keys) if match else keys + b["category"] = match.get("category", "Other") if match else "Other" + workspace = re.search(r"workspace = (\d+)", expr) + if workspace and expr.startswith("hl.dsp.focus("): + b.update(desc=f"Focus workspace {workspace.group(1)}", category="Workspaces") + elif workspace and expr.startswith("hl.dsp.window.move("): + b.update(desc=f"Move window to workspace {workspace.group(1)}", category="Workspaces") + if keys.upper().replace(" ", "") == "SUPER+SHIFT+S": b.update(desc="Region screenshot and edit", category="Screenshots") + m = re.fullmatch(r'hl\.dsp\.exec_cmd\(("(?:\\.|[^"\\])*")\)\s*', expr, re.S) + if m: + cmd = json.loads(m.group(1)) + cmd = re.sub(r'/nix/store/[0-9a-z]{32}-[^/\s"\\]+/bin/([A-Za-z0-9_.+-]+)', r'\1', cmd) + b.update(type="exec", cmd=cmd) + else: b.update(type="lua", lua=expr) + if opts: b["opts"] = opts + result.append(b) +json.dump({"version": 1, "binds": result + specials}, open(output, "w"), indent=2) + ''; + chestSeed = pkgs.runCommand "lucid-chest-bindings.json" { nativeBuildInputs = [ pkgs.python3 ]; } '' + python3 ${chestBindingsScript} ${lib.escapeShellArg (builtins.toJSON config.lucidHyprlandBinds)} \ + ${../../../../modules/user/lucid/lucid-keybind-catalog.json} ${specialSeed} "$out" + ''; + chestBinds = pkgs.runCommand "lucid-chest-loader.lua" { nativeBuildInputs = [ pkgs.python3 ]; } '' + python3 - ${source}/support/hypr/modules/binds.lua "$out" <<'PY' +import pathlib, sys +source = pathlib.Path(sys.argv[1]).read_text() +old = '{ id = "terminal", keys = "F9", type = "exec", cmd = "kitty" }' +new = '{ id = "terminal", keys = "SUPER + RETURN", type = "exec", cmd = "ghostty" }' +if source.count(old) != 1: raise SystemExit("Lucid emergency bindings changed") +pathlib.Path(sys.argv[2]).write_text(source.replace(old, new, 1)) +PY + ''; + chestMonitors = pkgs.runCommand "lucid-chest-monitors.lua" { nativeBuildInputs = [ pkgs.python3 ]; } '' + python3 - ${source}/support/hypr/modules/monitors.lua "$out" <<'PY' +import pathlib, sys +source = pathlib.Path(sys.argv[1]).read_text() +rule = """hl.monitor({ + output = "", + mode = M.defaults.mode, + position = M.defaults.position, + scale = M.defaults.scale, +}) +""" +if source.count(rule) != 1: raise SystemExit("Lucid monitor defaults changed") +pathlib.Path(sys.argv[2]).write_text(source.replace(rule, "", 1)) +PY + ''; + chestBorders = pkgs.runCommand "lucid-chest-decorations.lua" { nativeBuildInputs = [ pkgs.python3 ]; } '' + python3 - ${source}/support/hypr/modules/decorations.lua "$out" <<'PY' +import pathlib, sys +source = pathlib.Path(sys.argv[1]).read_text() +marker = "local active_border, inactive_border = borders()" +if source.count(marker) != 1: raise SystemExit("Lucid border module changed") +pathlib.Path(sys.argv[2]).write_text(source.split(marker, 1)[0]) +PY + ''; + chestPrefs = pkgs.writeText "lucid-chest-prefs.json" (builtins.toJSON ( + (builtins.fromJSON (builtins.readFile "${source}/defaults/prefs.json")) // { + specialCustom = builtins.toJSON [ { key = "daemons"; label = "Daemons"; glyph = "build"; on = true; apps = ""; } ]; + } + )); +in +{ + lucid.keybindSeed = chestSeed; + lucid.prefsSeed = chestPrefs; + lucid.bindsModule = chestBinds; + lucid.luaModules = { + "modules/decorations.lua" = lib.mkForce chestBorders; + "modules/monitors.lua" = lib.mkForce { content = chestMonitors; autoLoad = false; }; + "modules/glass.lua" = { content = "${source}/support/hypr/modules/glass.lua"; autoLoad = false; }; + "modules/settings.lua" = { content = "${source}/support/hypr/modules/settings.lua"; autoLoad = false; }; + "modules/startup.lua" = { content = "${source}/support/hypr/modules/startup.lua"; autoLoad = false; }; + }; + lucid.extraHyprlandConfig = '' + require("modules.monitors") + require("modules.settings") + require("modules.glass") + require("modules.startup") + ''; + lucid.shellFile = pkgs.writeText "lucid-chest-shell.qml" (builtins.replaceStrings [ + ''import "./lucidlock"'' + " Lock {\n }\n" + " void Clip.probed;" + ] [ + "" + "" + " void Notifs.count;\n void KdeConnect.installed;\n void HyprConfig.moduleProbed;\n void Monitors.moduleProbed;\n void Glass.moduleProbed;\n void Startup.moduleProbed;\n void Clip.probed;" + ] (builtins.readFile ../../../../modules/user/lucid/lucid-shell.qml)); + + lucid.prefsFilter = ''.barEnabled = true | .widgetOnTop = false | .widgetHideFullscreen = true | .fontFamily = $font | .liveWallpapers = false | .idleLock = false | .idleLockBeforeSleep = false | .launcherPowerButtons = "suspend,reboot,shutdown" | .powerModuleActions = "suspend,hibernate,logout,reboot,shutdown"''; + + home.packages = with pkgs; [ brightnessctl playerctl slurp swappy ]; + + wayland.windowManager.hyprland.settings = { + bind = lib.mkForce []; + window_rule = [ + { + name = "float-lucid-settings"; + match = { class = "org.quickshell"; title = "Lucid Settings"; }; + float = true; + size = [ 1180 800 ]; + center = true; + } + { + name = "float-lucid-choosers"; + match = { + class = "^(org\\.gnome\\.Nautilus|xdg-desktop-portal-gtk|xdg-desktop-portal-gnome|zenity|org\\.kde\\.kdialog|kdialog)$"; + title = "^(Choose an account picture|Choose a template|Choose a colour scheme|Export the palette|Save received files to|Send files|Send to .+)$"; + }; + float = true; + center = true; + } + ]; + }; + + home.activation.installChestLucidFixes = lib.hm.dag.entryAfter [ "installLucid" ] '' + target="${config.home.homeDirectory}/.config/quickshell" + lock_qml="$target/Lockscreen.qml" + if ! grep -Fq 'return; // Lucid lock disabled for chest' "$lock_qml"; then + grep -Fq 'function lock(settle) {' "$lock_qml" && \ + grep -Fq 'function pushLockedHint() {' "$lock_qml" && \ + grep -Fq 'id: logindWatch' "$lock_qml" || { + echo "Lucid lock integration changed; refusing to leave it active for chest" >&2 + exit 1 + } + $DRY_RUN_CMD sed -i \ + -e '/function lock(settle) {/a\ return; // Lucid lock disabled for chest' \ + -e '/function pushLockedHint() {/a\ return; // Lucid lock disabled for chest' \ + -e '/id: logindWatch/{n;n;s/running: true/running: false/;}' "$lock_qml" + fi + if ! grep -Fq 'id: chestBrightnessPoll' "$target/lucidosd/Osd.qml"; then + $DRY_RUN_CMD ${pkgs.patch}/bin/patch --batch --forward -p1 -d "$target" < ${pkgs.writeText "lucid-chest-osd.patch" '' + --- a/lucidosd/Osd.qml + +++ b/lucidosd/Osd.qml + @@ -26 +26 @@ + - readonly property int brightnessPercent: osdWindow.maxBrightness > 0 ? Math.round((parseInt(brightnessFile.text()) / osdWindow.maxBrightness) * 100) : 0 + + property int brightnessPercent: 0 + @@ -378,2 +378,15 @@ + onFileChanged: reload() + + onLoaded: { + + const value = parseInt(brightnessFile.text()); + + if (osdWindow.maxBrightness > 0 && !isNaN(value)) + + osdWindow.brightnessPercent = Math.round(value / osdWindow.maxBrightness * 100); + + } + } + + + + Timer { + + id: chestBrightnessPoll + + interval: 500 + + repeat: true + + running: osdWindow.backlightDevice !== "" + + onTriggered: brightnessFile.reload() + + } + ''} + fi + ''; +} diff --git a/user/sweetbread/home.nix b/user/sweetbread/home.nix index 0ac74f9..da67418 100644 --- a/user/sweetbread/home.nix +++ b/user/sweetbread/home.nix @@ -1,4 +1,4 @@ -{ pkgs, ... }: let +{ pkgs, lib, osConfig, ... }: let # Match the Electron FFmpeg build recommended by Vivaldi 8.2. vivaldiCodecs = pkgs.runCommand "vivaldi-ffmpeg-electron-44.4.2" { nativeBuildInputs = [ pkgs.unzip ]; @@ -11,7 +11,13 @@ echo "39f63af3ef6f8db9eea20803c6f6dff8b2d5ad596d506ea1effb3f856715ac1c $out/lib/libffmpeg.so" | sha256sum -c ''; in { - imports = [ ../../modules/user/lucid ]; + imports = [ + ../../modules/user/lucid + ./modules/lucid + ] ++ lib.optionals (osConfig.networking.hostName == "Rias") [ + ../../host/Rias/modules/lucid + ./modules/lucid/rias.nix + ]; programs.hyprlock.enable = true; home.packages = with pkgs; [ diff --git a/user/sweetbread/modules/lucid/default.nix b/user/sweetbread/modules/lucid/default.nix new file mode 100644 index 0000000..268646c --- /dev/null +++ b/user/sweetbread/modules/lucid/default.nix @@ -0,0 +1,28 @@ +{ config, lib, pkgs, ... }: +let + specialCustom = builtins.toJSON [ { + key = "daemons"; + label = "Daemons"; + glyph = "build"; + on = true; + apps = ""; + } ]; +in { + lucid.keybindSeed = ./lucid-keybinds.json; + lucid.prefsSeed = pkgs.writeText "sweetbread-lucid-prefs.json" (builtins.toJSON ( + (builtins.fromJSON (builtins.readFile ./lucid-prefs.json)) // { + specialCustom = specialCustom; + } + )); + lucid.startWallpaper = true; + lucid.installWallpaperSupport = true; + + lucid.managedFiles = { + "${config.home.homeDirectory}/.config/quickshell/Agenda.qml" = ./Agenda.qml; + "${config.home.homeDirectory}/.config/quickshell/CalendarSource.qml" = ./CalendarSource.qml; + "${config.home.homeDirectory}/.config/quickshell/lucidbar/ClockCalendar.qml" = ./ClockCalendar.qml; + "${config.home.homeDirectory}/.config/quickshell/thunderbird-calendar.py" = ./thunderbird-calendar.py; + }; + + wayland.windowManager.hyprland.settings.bind = lib.mkForce []; +} diff --git a/user/sweetbread/modules/lucid/rias.nix b/user/sweetbread/modules/lucid/rias.nix new file mode 100644 index 0000000..19d2707 --- /dev/null +++ b/user/sweetbread/modules/lucid/rias.nix @@ -0,0 +1,13 @@ +{ config, lib, ... }: +{ + lucid.prefsSeed = lib.mkForce (builtins.toFile "sweetbread-rias-lucid-prefs.json" (builtins.toJSON ( + (builtins.fromJSON (builtins.readFile ./lucid-prefs.json)) // { + specialCustom = "[]"; + } + ))); + lucid.extraHyprlandConfig = '' + require("modules.settings") + require("modules.glass") + require("modules.startup") + ''; +}