aboutsummaryrefslogtreecommitdiff
path: root/modules/programs/waybar.nix
diff options
context:
space:
mode:
Diffstat (limited to 'modules/programs/waybar.nix')
-rw-r--r--modules/programs/waybar.nix363
1 files changed, 363 insertions, 0 deletions
diff --git a/modules/programs/waybar.nix b/modules/programs/waybar.nix
new file mode 100644
index 00000000000..369f6e32aba
--- /dev/null
+++ b/modules/programs/waybar.nix
@@ -0,0 +1,363 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.programs.waybar;
+
+ # Used when generating warnings
+ modulesPath = "programs.waybar.settings.[].modules";
+
+ # Taken from <https://github.com/Alexays/Waybar/blob/adaf84304865e143e4e83984aaea6f6a7c9d4d96/src/factory.cpp>
+ defaultModuleNames = [
+ "sway/mode"
+ "sway/workspaces"
+ "sway/window"
+ "wlr/taskbar"
+ "idle_inhibitor"
+ "memory"
+ "cpu"
+ "clock"
+ "disk"
+ "tray"
+ "network"
+ "backlight"
+ "pulseaudio"
+ "mpd"
+ "temperature"
+ "bluetooth"
+ "battery"
+ ];
+
+ isValidCustomModuleName = x:
+ elem x defaultModuleNames || (hasPrefix "custom/" x && stringLength x > 7);
+
+ margins = let
+ mkMargin = name: {
+ "margin-${name}" = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 10;
+ description = "Margins value without unit.";
+ };
+ };
+ margins = map mkMargin [ "top" "left" "bottom" "right" ];
+ in foldl' mergeAttrs { } margins;
+
+ waybarBarConfig = with lib.types;
+ submodule {
+ options = {
+ layer = mkOption {
+ type = nullOr (enum [ "top" "bottom" ]);
+ default = null;
+ description = ''
+ Decide if the bar is displayed in front (<code>"top"</code>)
+ of the windows or behind (<code>"bottom"</code>).
+ '';
+ example = "top";
+ };
+
+ output = mkOption {
+ type = nullOr (either str (listOf str));
+ default = null;
+ example = literalExample ''
+ [ "DP-1" "!DP-2" "!DP-3" ]
+ '';
+ description = ''
+ Specifies on which screen this bar will be displayed.
+ Exclamation mark(!) can be used to exclude specific output.
+ '';
+ };
+
+ position = mkOption {
+ type = nullOr (enum [ "top" "bottom" "left" "right" ]);
+ default = null;
+ example = "right";
+ description = "Bar position relative to the output.";
+ };
+
+ height = mkOption {
+ type = nullOr ints.unsigned;
+ default = null;
+ example = 5;
+ description =
+ "Height to be used by the bar if possible. Leave blank for a dynamic value.";
+ };
+
+ width = mkOption {
+ type = nullOr ints.unsigned;
+ default = null;
+ example = 5;
+ description =
+ "Width to be used by the bar if possible. Leave blank for a dynamic value.";
+ };
+
+ modules-left = mkOption {
+ type = nullOr (listOf str);
+ default = null;
+ description = "Modules that will be displayed on the left.";
+ example = literalExample ''
+ [ "sway/workspaces" "sway/mode" "wlr/taskbar" ]
+ '';
+ };
+
+ modules-center = mkOption {
+ type = nullOr (listOf str);
+ default = null;
+ description = "Modules that will be displayed in the center.";
+ example = literalExample ''
+ [ "sway/window" ]
+ '';
+ };
+
+ modules-right = mkOption {
+ type = nullOr (listOf str);
+ default = null;
+ description = "Modules that will be displayed on the right.";
+ example = literalExample ''
+ [ "mpd" "custom/mymodule#with-css-id" "temperature" ]
+ '';
+ };
+
+ modules = mkOption {
+ type = attrsOf unspecified;
+ default = { };
+ description = "Modules configuration.";
+ example = literalExample ''
+ {
+ "sway/window" = {
+ max-length = 50;
+ };
+ "clock" = {
+ format-alt = "{:%a, %d. %b %H:%M}";
+ };
+ }
+ '';
+ };
+
+ margin = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Margins value using the CSS format without units.";
+ example = "20 5";
+ };
+
+ inherit (margins) margin-top margin-left margin-bottom margin-right;
+
+ name = mkOption {
+ type = nullOr str;
+ default = null;
+ description =
+ "Optional name added as a CSS class, for styling multiple waybars.";
+ example = "waybar-1";
+ };
+
+ gtk-layer-shell = mkOption {
+ type = nullOr bool;
+ default = null;
+ example = false;
+ description =
+ "Option to disable the use of gtk-layer-shell for popups.";
+ };
+ };
+ };
+in {
+ meta.maintainers = [ hm.maintainers.berbiche ];
+
+ options.programs.waybar = with lib.types; {
+ enable = mkEnableOption "Waybar";
+
+ package = mkOption {
+ type = package;
+ default = pkgs.waybar;
+ defaultText = literalExample "${pkgs.waybar}";
+ description = ''
+ Waybar package to use. Set to <code>null</code> to use the default module.
+ '';
+ };
+
+ settings = mkOption {
+ type = listOf waybarBarConfig;
+ default = [ ];
+ description = ''
+ Configuration for Waybar, see <link
+ xlink:href="https://github.com/Alexays/Waybar/wiki/Configuration"/>
+ for supported values.
+ '';
+ example = literalExample ''
+ [
+ {
+ layer = "top";
+ position = "top";
+ height = 30;
+ output = [
+ "eDP-1"
+ "HDMI-A-1"
+ ];
+ modules-left = [ "sway/workspaces" "sway/mode" "wlr/taskbar" ];
+ modules-center = [ "sway/window" "custom/hello-from-waybar" ];
+ modules-right = [ "mpd" "custom/mymodule#with-css-id" "temperature" ];
+ modules = {
+ "sway/workspaces" = {
+ disable-scroll = true;
+ all-outputs = true;
+ };
+ "custom/hello-from-waybar" = {
+ format = "hello {}";
+ max-length = 40;
+ interval = "once";
+ exec = pkgs.writeShellScript "hello-from-waybar" '''
+ echo "from within waybar"
+ ''';
+ };
+ };
+ }
+ ]
+ '';
+ };
+
+ systemd.enable = mkEnableOption "Waybar systemd integration";
+
+ style = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ CSS style of the bar.
+ See <link xlink:href="https://github.com/Alexays/Waybar/wiki/Configuration"/>
+ for the documentation.
+ '';
+ example = ''
+ * {
+ border: none;
+ border-radius: 0;
+ font-family: Source Code Pro;
+ }
+ window#waybar {
+ background: #16191C;
+ color: #AAB2BF;
+ }
+ #workspaces button {
+ padding: 0 5px;
+ }
+ '';
+ };
+ };
+
+ config = let
+ # Inspired by https://github.com/NixOS/nixpkgs/pull/89781
+ writePrettyJSON = name: x:
+ pkgs.runCommandLocal name { } ''
+ ${pkgs.jq}/bin/jq . > $out <<<${escapeShellArg (builtins.toJSON x)}
+ '';
+
+ configSource = let
+ # Removes nulls because Waybar ignores them for most values
+ removeNulls = filterAttrs (_: v: v != null);
+
+ # Makes the actual valid configuration Waybar accepts
+ # (strips our custom settings before converting to JSON)
+ makeConfiguration = configuration:
+ let
+ # The "modules" option is not valid in the JSON
+ # as its descendants have to live at the top-level
+ settingsWithoutModules =
+ filterAttrs (n: _: n != "modules") configuration;
+ settingsModules =
+ optionalAttrs (configuration.modules != { }) configuration.modules;
+ in removeNulls (settingsWithoutModules // settingsModules);
+ # The clean list of configurations
+ finalConfiguration = map makeConfiguration cfg.settings;
+ in writePrettyJSON "waybar-config.json" finalConfiguration;
+
+ warnings = let
+ mkUnreferencedModuleWarning = name:
+ "The module '${name}' defined in '${modulesPath}' is not referenced "
+ + "in either `modules-left`, `modules-center` or `modules-right` of Waybar's options";
+ mkUndefinedModuleWarning = settings: name:
+ let
+ # Locations where the module is undefined (a combination modules-{left,center,right})
+ locations = flip filter [ "left" "center" "right" ]
+ (x: elem name settings."modules-${x}");
+ mkPath = loc: "'${modulesPath}-${loc}'";
+ # The modules-{left,center,right} configuration that includes
+ # an undefined module
+ path = concatMapStringsSep " and " mkPath locations;
+ in "The module '${name}' defined in ${path} is neither "
+ + "a default module or a custom module declared in '${modulesPath}'";
+ mkInvalidModuleNameWarning = name:
+ "The custom module '${name}' defined in '${modulesPath}' is not a valid "
+ + "module name. A custom module's name must start with 'custom/' "
+ + "like 'custom/mymodule' for instance";
+
+ # Find all modules in `modules-{left,center,right}` and `modules` not declared/referenced.
+ # `cfg.settings` is a list of Waybar configurations
+ # and we need to preserve the index for appropriate warnings
+ allFaultyModules = flip map cfg.settings (settings:
+ let
+ allModules = unique
+ (concatMap (x: attrByPath [ "modules-${x}" ] [ ] settings) [
+ "left"
+ "center"
+ "right"
+ ]);
+ declaredModules = attrNames settings.modules;
+ # Modules declared in `modules` but not referenced in `modules-{left,center,right}`
+ unreferencedModules = subtractLists allModules declaredModules;
+ # Modules listed in modules-{left,center,right} that are not default modules
+ nonDefaultModules = subtractLists defaultModuleNames allModules;
+ # Modules referenced in `modules-{left,center,right}` but not declared in `modules`
+ undefinedModules = subtractLists declaredModules nonDefaultModules;
+ # Check for invalid module names
+ invalidModuleNames =
+ filter (m: !isValidCustomModuleName m) (attrNames settings.modules);
+ in {
+ # The Waybar bar configuration (since config.settings is a list)
+ settings = settings;
+ undef = undefinedModules;
+ unref = unreferencedModules;
+ invalidName = invalidModuleNames;
+ });
+
+ allWarnings = flip concatMap allFaultyModules
+ ({ settings, undef, unref, invalidName }:
+ let
+ unreferenced = map mkUnreferencedModuleWarning unref;
+ undefined = map (mkUndefinedModuleWarning settings) undef;
+ invalid = map mkInvalidModuleNameWarning invalidName;
+ in undefined ++ unreferenced ++ invalid);
+ in allWarnings;
+
+ in mkIf cfg.enable (mkMerge [
+ { home.packages = [ cfg.package ]; }
+ (mkIf (cfg.settings != [ ]) {
+ # Generate warnings about defined but unreferenced modules
+ inherit warnings;
+
+ xdg.configFile."waybar/config".source = configSource;
+ })
+ (mkIf (cfg.style != null) {
+ xdg.configFile."waybar/style.css".text = cfg.style;
+ })
+ (mkIf cfg.systemd.enable {
+ systemd.user.services.waybar = {
+ Unit = {
+ Description =
+ "Highly customizable Wayland bar for Sway and Wlroots based compositors.";
+ Documentation = "https://github.com/Alexays/Waybar/wiki";
+ PartOf = [ "graphical-session.target" ];
+ Requisite = [ "dbus.service" ];
+ After = [ "dbus.service" ];
+ };
+
+ Service = {
+ Type = "dbus";
+ BusName = "fr.arouillard.waybar";
+ ExecStart = "${cfg.package}/bin/waybar";
+ Restart = "always";
+ RestartSec = "1sec";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ })
+ ]);
+}