This repository has no description
6.2 kB
166 lines
1{ ... }:
2{
3 flake.wrapperModules.noctalia-shell =
4 {
5 config,
6 lib,
7 ...
8 }:
9 let
10 pkgs = config.pkgs;
11 jsonFormat = pkgs.formats.json { };
12
13 sway-focus-workspace = pkgs.writeShellScript "sway-focus-workspace" ''
14 # Bring a workspace to the currently focused output
15 ws="$1"
16 current_output=$(${pkgs.sway}/bin/swaymsg -t get_workspaces \
17 | ${pkgs.jq}/bin/jq -r '.[] | select(.focused) | .output')
18 ${pkgs.sway}/bin/swaymsg "workspace $ws, move workspace to output $current_output"
19 '';
20
21 # Patch noctalia-shell for sway:
22 # - Use workspace name instead of number (activate() sends
23 # "workspace number <num>" which fails for named workspaces)
24 # - Show all workspaces on all screens (globalWorkspaces)
25 # - Bring workspace to current screen on click instead of jumping
26 # - Periodically resync the workspace model: the upstream QuickShell
27 # i3 plugin never removes renamed workspaces (a "rename" event is
28 # looked up by the NEW name, misses the old model object, and only
29 # logs "Workspace <name> doesn't exist"). A renamed workspace thus
30 # lingers as a stale pill until a full refresh; clicking that ghost
31 # pill then re-creates the old name as a real workspace.
32 swayPatched = pkgs.noctalia-shell.overrideAttrs (old: {
33 postInstall = (old.postInstall or "") + ''
34 substituteInPlace $out/share/noctalia-shell/Services/Compositor/SwayService.qml \
35 --replace-fail 'workspace.handle.activate();' \
36 'Quickshell.execDetached(["${sway-focus-workspace}", workspace.name]);' \
37 --replace-fail 'property bool initialized: false' \
38 'property bool globalWorkspaces: true
39 property bool initialized: false
40
41 // Drop stale workspace model entries (see patch note above).
42 Timer {
43 interval: 30000
44 repeat: true
45 running: initialized
46 onTriggered: I3.refreshWorkspaces()
47 }'
48 '';
49 });
50
51 declSettingsFile = jsonFormat.generate "noctalia-declarative-settings.json" config.settings;
52 in
53 {
54 _class = "wrapper";
55
56 options = {
57 settings = lib.mkOption {
58 type = jsonFormat.type;
59 default = { };
60 example = lib.literalExpression ''
61 {
62 hooks.enabled = true;
63 hooks.darkModeChange = "switch-theme $1";
64 }
65 '';
66 description = ''
67 Declarative noctalia settings. Deep-merged into the on-disk
68 `settings.json` on every start (declarative wins). Keys not
69 mentioned here remain user-mutable and survive across
70 invocations — only the fields you set are pinned.
71 '';
72 };
73
74 settingsPath = lib.mkOption {
75 type = lib.types.str;
76 default = ".config/noctalia/settings.json";
77 description = ''
78 Path to the noctalia settings file, relative to `$HOME`.
79 '';
80 };
81
82 settingsPatches = lib.mkOption {
83 type = lib.types.listOf lib.types.str;
84 default = [ ];
85 example = lib.literalExpression ''
86 [
87 # Force the Workspace bar widget to show workspace names.
88 ".bar.widgets.center |= map(if .id == \"Workspace\" then .labelMode = \"name\" else . end)"
89 ]
90 '';
91 description = ''
92 jq update expressions applied to `settings.json` on every start,
93 after the deep-merge of `settings`. Use these to pin individual
94 fields inside arrays (where deep-merge would replace the whole
95 array) without touching sibling entries.
96
97 Each string is passed as the filter to a separate `jq` invocation.
98 Patches run in order; if any patch fails the file is left
99 untouched.
100 '';
101 };
102
103 swayWorkspaceFix = lib.mkOption {
104 type = lib.types.bool;
105 default = true;
106 description = ''
107 Patch noctalia-shell so its workspace bar works on sway with
108 named/global workspaces and click-to-current-output. Applied
109 to the default package only; ignored if you override `package`.
110 '';
111 };
112 };
113
114 config.package = lib.mkDefault (
115 if config.swayWorkspaceFix then swayPatched else pkgs.noctalia-shell
116 );
117
118 config.extraPackages = [
119 pkgs.jq
120 pkgs.coreutils
121 ];
122
123 # On every start: merge declarative settings into the user's
124 # writable settings.json. User-edited keys outside the declarative
125 # set are preserved; declarative keys are overwritten each run.
126 config.preHook = ''
127 NOCTALIA_SETTINGS="$HOME/${config.settingsPath}"
128 mkdir -p "$(dirname "$NOCTALIA_SETTINGS")"
129 if [ ! -f "$NOCTALIA_SETTINGS" ]; then
130 echo '{}' > "$NOCTALIA_SETTINGS"
131 fi
132 NOCTALIA_TMP=$(mktemp "$NOCTALIA_SETTINGS.XXXXXX")
133 noctalia_merge_ok=1
134 if ! ${pkgs.jq}/bin/jq --slurpfile decl ${declSettingsFile} \
135 '. * $decl[0]' "$NOCTALIA_SETTINGS" > "$NOCTALIA_TMP"; then
136 noctalia_merge_ok=0
137 fi
138 ${lib.concatMapStringsSep "\n" (patch: ''
139 if [ "$noctalia_merge_ok" = 1 ]; then
140 NOCTALIA_TMP2=$(mktemp "$NOCTALIA_SETTINGS.XXXXXX")
141 if ${pkgs.jq}/bin/jq ${lib.escapeShellArg patch} "$NOCTALIA_TMP" > "$NOCTALIA_TMP2"; then
142 mv "$NOCTALIA_TMP2" "$NOCTALIA_TMP"
143 else
144 rm -f "$NOCTALIA_TMP2"
145 noctalia_merge_ok=0
146 fi
147 fi
148 '') config.settingsPatches}
149 if [ "$noctalia_merge_ok" = 1 ]; then
150 mv "$NOCTALIA_TMP" "$NOCTALIA_SETTINGS"
151 else
152 rm -f "$NOCTALIA_TMP"
153 echo "noctalia declarative settings merge failed; leaving settings.json untouched" >&2
154 fi
155 '';
156
157 config.meta.maintainers = [
158 {
159 name = "lassulus";
160 github = "lassulus";
161 githubId = 621375;
162 }
163 ];
164 config.meta.platforms = lib.platforms.linux;
165 };
166}