oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
15,476 bytes raw
1
{self}: {
2
  config,
3
  lib,
4
  pkgs,
5
  ...
6
}: let
7
  inherit (lib) mkEnableOption mkOption mkIf types concatMapStringsSep concatStringsSep concatMapStrings boolToString optional;
8
  inherit (lib.strings) escapeNixString;
9
  cfg = config.programs.oxwm.settings;
10
  pkg = config.programs.oxwm.package;
11
12
  # Converts a nix submodule into a single oxwm bar block
13
  blockToLua = block: let
14
    common = ''
15
      interval = ${toString block.interval},
16
      color = "#${block.color}",
17
      underline = ${boolToString block.underline},
18
    '';
19
  in
20
    "oxwm.bar.block.${block.kind}({\n"
21
    + (
22
      if block.kind == "static"
23
      then ''
24
        text = "${block.text}",
25
        ${common}
26
      ''
27
      else if block.kind == "shell"
28
      then ''
29
        format = "${block.format}",
30
        command = "${block.command}",
31
        ${common}
32
      ''
33
      else if block.kind == "datetime"
34
      then ''
35
        format = "${block.format}",
36
        date_format = "${block.date_format}",
37
        ${common}
38
      ''
39
      else if block.kind == "battery"
40
      then ''
41
        format = "${block.format}",
42
        charging = "${block.charging}",
43
        discharging = "${block.discharging}",
44
        full = "${block.full}",
45
        ${common}
46
      ''
47
      else ''
48
        format = "${block.format}",
49
        ${common}
50
      ''
51
    )
52
    + "})";
53
54
  # Converts a nix submodule into a single oxwm window rule
55
  ruleToLua = rule: let
56
    fields = concatStringsSep ", " (
57
      optional (rule.match.class != null) ''class = "${rule.match.class}"''
58
      ++ optional (rule.match.instance != null) ''instance = "${rule.match.instance}"''
59
      ++ optional (rule.match.title != null) ''title = "${rule.match.title}"''
60
      ++ optional (rule.match.role != null) ''role = "${rule.match.role}"''
61
      ++ optional (rule.floating != null) ''floating = ${boolToString rule.floating}''
62
      ++ optional (rule.tag != null) ''tag = ${toString rule.tag}''
63
      ++ optional (rule.fullscreen != null) ''fullscreen = ${boolToString rule.fullscreen}''
64
      ++ optional (rule.focus != null) ''focus = ${boolToString rule.focus}''
65
    );
66
  in "oxwm.rule.add({ ${fields} })";
67
68
  configText = ''
69
    -- @meta
70
    -- @module 'oxwm'
71
72
    oxwm.set_terminal("${cfg.terminal}")
73
    oxwm.set_modkey("${cfg.modkey}")
74
    oxwm.set_tags({${concatMapStringsSep ", " escapeNixString cfg.tags}})
75
76
    local blocks = {
77
      ${concatMapStringsSep ",\n" blockToLua cfg.bar.blocks}
78
    };
79
    oxwm.bar.set_blocks(blocks)
80
    oxwm.bar.set_font("${cfg.bar.font}")
81
    oxwm.bar.set_scheme_normal(${concatMapStringsSep ", " (c: ''"#${c}"'') cfg.bar.unoccupiedScheme})
82
    oxwm.bar.set_scheme_occupied(${concatMapStringsSep ", " (c: ''"#${c}"'') cfg.bar.occupiedScheme})
83
    oxwm.bar.set_scheme_selected(${concatMapStringsSep ", " (c: ''"#${c}"'') cfg.bar.selectedScheme})
84
    oxwm.bar.set_scheme_urgent(${concatMapStringsSep ", " (c: ''"#${c}"'') cfg.bar.urgentScheme})
85
    oxwm.bar.set_hide_vacant_tags(${boolToString cfg.bar.hideVacantTags})
86
87
    oxwm.border.set_width(${toString cfg.border.width})
88
    oxwm.border.set_focused_color("#${cfg.border.focusedColor}")
89
    oxwm.border.set_unfocused_color("#${cfg.border.unfocusedColor}")
90
91
    oxwm.gaps.set_smart(${cfg.gaps.smart})
92
    oxwm.gaps.set_inner(${concatMapStringsSep ", " toString cfg.gaps.inner})
93
    oxwm.gaps.set_outer(${concatMapStringsSep ", " toString cfg.gaps.outer})
94
95
    oxwm.set_layout_symbol("tiling", "${cfg.layoutSymbol.tiling}")
96
    oxwm.set_layout_symbol("normie", "${cfg.layoutSymbol.normie}")
97
    oxwm.set_layout_symbol("tabbed", "${cfg.layoutSymbol.tabbed}")
98
99
    ${
100
      concatMapStrings (cmd: ''
101
        oxwm.autostart("${cmd}")
102
      '')
103
      cfg.autostart
104
    }
105
    ${
106
      concatMapStrings (bind: ''
107
        oxwm.key.bind({ ${concatMapStringsSep ", " escapeNixString bind.mods} }, "${bind.key}", ${bind.action})
108
      '')
109
      cfg.binds
110
    }
111
    ${
112
      concatMapStrings (chord: ''
113
        oxwm.key.chord({
114
          ${concatMapStringsSep ",\n  " (note: ''{ { ${concatMapStringsSep ", " escapeNixString note.mods} }, "${note.key}" }'') chord.notes}
115
        }, ${chord.action})
116
      '')
117
      cfg.chords
118
    }
119
    ${
120
      concatMapStrings (rule: ''
121
        ${ruleToLua rule}
122
      '')
123
      cfg.rules
124
    }
125
126
    ${cfg.extraConfig}
127
  '';
128
129
    validatedConfig = pkgs.runCommand "config.lua" {
130
      config = configText;
131
      passAsFile = [ "config" ];
132
      buildInputs = [ pkg ];
133
    } ''
134
      mkdir -p $TMPDIR/oxwm
135
      cp $configPath $TMPDIR/oxwm/config.lua
136
      XDG_CONFIG_HOME=$TMPDIR ${lib.getExe pkg} --validate
137
      cp $configPath $out
138
    '';
139
in {
140
  options.programs.oxwm = {
141
    enable = mkEnableOption "oxwm window manager";
142
    package = mkOption {
143
      type = types.package;
144
      default = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
145
      description = "The oxwm package to use";
146
    };
147
    extraSessionCommands = mkOption {
148
      type = types.lines;
149
      default = "";
150
      description = "Shell commands executed just before oxwm is started";
151
    };
152
    settings = {
153
      terminal = mkOption {
154
        type = types.str;
155
        default = "alacritty";
156
        description = "Terminal used";
157
      };
158
      modkey = mkOption {
159
        type = types.str;
160
        default = "Mod4";
161
        description = "Modifier key. Used for mouse dragging";
162
      };
163
      tags = mkOption {
164
        type = types.listOf types.str;
165
        default = ["1" "2" "3" "4" "5" "6" "7" "8" "9"];
166
        description = "Workspace tags";
167
        example = ["" "󰊯" "" "" "󰙯" "󱇤" "" "󱘶" "󰧮"];
168
      };
169
      layoutSymbol = {
170
        tiling = mkOption {
171
          type = types.str;
172
          default = "[T]";
173
          description = "Symbol in tiling mode";
174
        };
175
        normie = mkOption {
176
          type = types.str;
177
          default = "[F]";
178
          description = "Symbol in normie mode";
179
        };
180
        tabbed = mkOption {
181
          type = types.str;
182
          default = "[=]";
183
          description = "Symbol in tabbed mode";
184
        };
185
      };
186
      autostart = mkOption {
187
        type = types.listOf types.str;
188
        default = [];
189
        description = "A list of commands to run when oxwm starts";
190
      };
191
      binds = mkOption {
192
        type = types.listOf (types.submodule {
193
          options = {
194
            mods = mkOption {
195
              type = types.listOf types.str;
196
              default = ["${cfg.modkey}"];
197
              description = "The modifier keys to invoke the command";
198
            };
199
            key = mkOption {
200
              type = types.str;
201
              description = "The keystroke to invoke the command";
202
            };
203
            action = mkOption {
204
              type = types.str;
205
              description = "The command to invoke";
206
            };
207
          };
208
        });
209
        default = [];
210
        description = "The list of keybinds";
211
        example = ''
212
          [
213
            {
214
              mods = [ "Mod4" "Shift" ];
215
              key = "Slash";
216
              action = "oxwm.show_keybinds()";
217
            }
218
            {
219
              mods = [ "Mod4" ];
220
              key = "D";
221
              action = "oxwm.spawn({ "sh", "-c", "dmenu_run -l 10" })";
222
            }
223
          ];
224
        '';
225
      };
226
      chords = mkOption {
227
        type = types.listOf (types.submodule {
228
          options = {
229
            notes = mkOption {
230
              type = types.listOf (types.submodule {
231
                options = {
232
                  mods = mkOption {
233
                    type = types.listOf types.str;
234
                    default = [];
235
                  };
236
                  key = mkOption {type = types.str;};
237
                };
238
              });
239
            };
240
            action = mkOption {type = types.str;};
241
          };
242
        });
243
        default = [];
244
        description = "A list of key chords for OXWM to use";
245
        example = ''
246
          [
247
            {
248
              notes = [
249
                {
250
                  mods = [ "Mod4" ];
251
                  key = "Space";
252
                }
253
                {
254
                  mods = [];
255
                  key = "T";
256
                }
257
              ];
258
              action = "oxwm.spawn_terminal()";
259
            }
260
          ];
261
        '';
262
      };
263
      border = {
264
        width = mkOption {
265
          type = types.int;
266
          default = 2;
267
          description = "Width of the window borders";
268
        };
269
        focusedColor = mkOption {
270
          type = types.str;
271
          default = "6dade3";
272
          description = "Color of the focused window";
273
        };
274
        unfocusedColor = mkOption {
275
          type = types.str;
276
          default = "bbbbbb";
277
          description = "Color of the unfocused window";
278
        };
279
      };
280
      gaps = {
281
        smart = mkOption {
282
          type = types.enum ["enabled" "disabled"];
283
          default = "enabled";
284
          description = "If enabled, removes border if single window in tag";
285
        };
286
        inner = mkOption {
287
          type = types.listOf types.int;
288
          default = [5 5];
289
          description = "Inner gaps [ horizontal vertical ] in pixels";
290
        };
291
        outer = mkOption {
292
          type = types.listOf types.int;
293
          default = [5 5];
294
          description = "Outer gaps [ horizontal vertical ] in pixels";
295
        };
296
      };
297
      bar = {
298
        font = mkOption {
299
          type = types.str;
300
          default = "monospace 10";
301
          description = "The font displayed on the bar";
302
        };
303
        hideVacantTags = mkOption {
304
          type = types.bool;
305
          default = false;
306
          description = "Whether to hide tags with no windows from the bar";
307
        };
308
        unoccupiedScheme = mkOption {
309
          type = types.listOf types.str;
310
          default = ["bbbbbb" "1a1b26" "444444"];
311
          description = "The colorscheme to use for unoccupied tags as hex colors. Do not put a `#` before each color.";
312
        };
313
        occupiedScheme = mkOption {
314
          type = types.listOf types.str;
315
          default = ["0db9d7" "1a1b26" "0db9d7"];
316
          description = "The colorscheme to use for occupied tags as hex colors. Do not put a `#` before each color.";
317
        };
318
        selectedScheme = mkOption {
319
          type = types.listOf types.str;
320
          default = ["0db9d7" "1a1b26" "ad8ee6"];
321
          description = "The colorscheme to use for selected tags as hex colors. Do not put a `#` before each color.";
322
        };
323
        urgentScheme = mkOption {
324
          type = types.listOf types.str;
325
          default = ["f7768e" "1a1b26" "f7768e"];
326
          description = "The colorscheme to use for tags with a window requesting attention as hex colors. Do not put a `#` before each color.";
327
        };
328
        blocks = mkOption {
329
          type = types.listOf (types.submodule {
330
            options = {
331
              kind = mkOption {
332
                type = types.enum ["ram" "static" "shell" "datetime" "battery"];
333
                default = "static";
334
                description = "The kind of block to be used";
335
              };
336
              interval = mkOption {
337
                type = types.int;
338
                default = 5;
339
              };
340
              color = mkOption {
341
                type = types.str;
342
                default = "";
343
              };
344
              underline = mkOption {
345
                type = types.bool;
346
                default = true;
347
              };
348
              text = mkOption {
349
                type = types.str;
350
                default = "|";
351
              };
352
              format = mkOption {
353
                type = types.str;
354
                default = "{}";
355
              };
356
              command = mkOption {
357
                type = types.str;
358
                default = "uname -r";
359
              };
360
              date_format = mkOption {
361
                type = types.str;
362
                default = "%a, %b %d - %-I:%M %P";
363
              };
364
              charging = mkOption {
365
                type = types.str;
366
                default = "⚡ Bat: {}%";
367
              };
368
              discharging = mkOption {
369
                type = types.str;
370
                default = "- Bat: {}%";
371
              };
372
              full = mkOption {
373
                type = types.str;
374
                default = "✓ Bat: {}%";
375
              };
376
            };
377
          });
378
          description = "The modules to put on the bar";
379
          example = ''
380
            [
381
              {
382
                kind = "ram";
383
                interval = 5;
384
                format = "Ram: {used}/{total} GB";
385
                color = "9ece6a";
386
              }
387
              {
388
                kind = "static";
389
                text = "|";
390
                interval = 99999999;
391
                color = "6dade3";
392
              }
393
              {
394
                kind = "shell";
395
                format = "{}";
396
                command = "uname -r";
397
                interval = 9999999;
398
                color = "f7768e";
399
                underline = true;
400
              }
401
            ];
402
          '';
403
        };
404
      };
405
      rules = mkOption {
406
        type = types.listOf (types.submodule {
407
          options = {
408
            match = {
409
              class = mkOption {
410
                type = types.nullOr types.str;
411
                default = null;
412
                description = "The class to match windows with";
413
              };
414
              instance = mkOption {
415
                type = types.nullOr types.str;
416
                default = null;
417
                description = "The instance to match windows with";
418
              };
419
              title = mkOption {
420
                type = types.nullOr types.str;
421
                default = null;
422
                description = "The title to match windows with";
423
              };
424
              role = mkOption {
425
                type = types.nullOr types.str;
426
                default = null;
427
                description = "The role to match windows with";
428
              };
429
            };
430
            floating = mkOption {
431
              type = types.nullOr types.bool;
432
              default = null;
433
              description = "Whether to apply floating to the matched window";
434
            };
435
            tag = mkOption {
436
              type = types.nullOr types.int;
437
              default = null;
438
              description = "What tag the matched window should be opened on";
439
            };
440
            fullscreen = mkOption {
441
              type = types.nullOr types.bool;
442
              default = null;
443
              description = "Whether to apply fullscreen to the matched window";
444
            };
445
            focus = mkOption {
446
              type = types.nullOr types.bool;
447
              default = null;
448
              description = "Whether to apply focus to the matched window";
449
            };
450
          };
451
        });
452
        description = "A list of window rules for the window manager to follow";
453
        example = ''
454
          [
455
            {
456
              match.class = "gimp";
457
              floating = true;
458
            }
459
            {
460
              match.class = "firefox";
461
              match.title = "Library";
462
              tag = 9;
463
              focus = true;
464
            }
465
          ];
466
        '';
467
      };
468
      extraConfig = mkOption {
469
        type = types.lines;
470
        default = "";
471
        description = "Extra lua confguration that gets inserted at the bottom of the file";
472
      };
473
    };
474
  };
475
476
  config = mkIf config.programs.oxwm.enable {
477
      xdg.configFile."oxwm/config.lua".source = validatedConfig;
478
  };
479
}