diff --git a/README.md b/README.md
index fcfa799..fe500d4 100644
--- a/README.md
+++ b/README.md
@@ -159,6 +159,8 @@ If you install the fonts after installing HyperPanel, you will need to restart H
### NixOS & Home-Manager
+
+Overlay Method
Alternatively, if you're using NixOS and/or Home-Manager, you can setup AGS using the provided Nix Flake. First, add the repository to your Flake's inputs, and enable the overlay.
```nix
@@ -220,6 +222,111 @@ wayland.windowManager.hyprland.settings.exec-once = [
];
```
+
+
+
+Home Manager Module
+If you want to configure HyprPanel with the Home Manager module, read this section instead.
+
+First, as with the overlay method, add HyprPanel to your flake.
+```nix
+# flake.nix
+{
+ inputs = {
+ hyprpanel.url = "github:jas-singhfsu/hyprpanel";
+ # Good practice to ensure packages in HyprPanel
+ # are the same version as your system packages.
+ hyprpanel.inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ # ...
+}
+```
+
+Then, import the Home Manager module and configure it as you wish.
+Below is an example of some of the options that are available.
+```nix
+# *.nix
+{ inputs, ... }:
+{
+ imports = [ inputs.hyprpanel.homeManagerModules.hyprpanel ];
+
+ programs.hyprpanel = {
+
+ # Enable the module.
+ # Default: false
+ enable = true;
+
+ # Automatically restart HyprPanel with systemd.
+ # Useful when updating your config so that you
+ # don't need to manually restart it.
+ # Default: false
+ systemd.enable = true;
+
+ # Adds '/nix/store/.../hyprpanel' to the
+ # 'exec-once' in your Hyprland config.
+ # Default: false
+ hyprland.enable = true;
+
+ # Fixes the overwrite issue with HyprPanel.
+ # See below for more information.
+ # Default: false
+ overwrite.enable = true;
+
+ # Import a specific theme from './themes/*.json'.
+ # Default: ""
+ theme = "gruvbox_split";
+
+ # See 'https://hyprpanel.com/configuration/panel.html'.
+ # Default: null
+ layout = {
+ "bar.layouts" = {
+ "0" = {
+ left = [ "dashboard" "workspaces" ];
+ middle = [ "media" ];
+ right = [ "volume" "systray" "notifications" ];
+ };
+ };
+ };
+
+ # See './nix/module.nix:103'.
+ # Many of the options from the GUI are included.
+ # Default:
+ settings = {
+ bar.launcher.autoDetectIcon = true;
+ bar.workspaces.show_icons = true;
+
+ menus.clock = {
+ time = {
+ military = true;
+ hideSeconds = true;
+ };
+ weather.unit = "metric";
+ };
+
+ menus.dashboard.directories.enabled = false;
+ menus.dashboard.stats.enable_gpu = true;
+
+ theme.bar.transparent = true;
+
+ theme.font = {
+ name = "CaskaydiaCove NF";
+ size = "16px";
+ };
+ };
+ };
+}
+```
+:warning: **Caveat**: Currently, updating the configuration through the GUI will
+overwrite the `config.json` file by deleting it and creating a new one in its
+place. This causes an error with Home Manager as the config must be a symlink to
+the current generation for Home Manager to properly update it. A shorthand fix
+is to delete `config.json` if it is NOT a symlink which can be handled for you
+with the module by setting the `overwrite.enable` option. An obvious caveat to
+this is that you can no longer tweak the configuration through the GUI. The
+recommended workflow is to keep track of the differences and apply it later
+in the module. This will hopefully be improved in a future update - benvonh.
+
### Launch the panel
diff --git a/flake.nix b/flake.nix
index 681ac53..a079623 100644
--- a/flake.nix
+++ b/flake.nix
@@ -75,5 +75,7 @@
overlay = final: prev: {
hyprpanel = self.packages.${prev.stdenv.system}.default;
};
+
+ homeManagerModules.hyprpanel = import ./nix/module.nix self;
};
}
diff --git a/nix/module.nix b/nix/module.nix
new file mode 100644
index 0000000..263c027
--- /dev/null
+++ b/nix/module.nix
@@ -0,0 +1,265 @@
+self: { lib, pkgs, config, ... }:
+let
+ inherit (lib) types mkIf mkOption mkEnableOption;
+
+ cfg = config.programs.hyprpanel;
+
+ # No package option
+ package = self.packages.${pkgs.system}.default;
+
+ # Shorthand lambda for self-documenting options under settings
+ mkStrOption = default: mkOption { type = types.str; default = default; };
+ mkIntOption = default: mkOption { type = types.int; default = default; };
+ mkBoolOption = default: mkOption { type = types.bool; default = default; };
+
+ # TODO: Please merge https://github.com/Jas-SinghFSU/HyprPanel/pull/497
+ # Do not ask what this does...
+ flattenAttrs = attrSet: prefix:
+ let
+ process = key: value:
+ if builtins.isAttrs value then
+ flattenAttrs value "${prefix}${key}."
+ else
+ { "${prefix}${key}" = value; };
+ in
+ builtins.foldl' (acc: key:
+ acc // process key attrSet.${key}
+ ) {} (builtins.attrNames attrSet);
+
+ toNestedValue =
+ let
+ escapeString = s: builtins.replaceStrings [ "\"" ] [ "\\\"" ] s;
+ in
+ value:
+ if builtins.isBool value then
+ if value then "true" else "false"
+ else if (builtins.isInt value || builtins.isFloat value) then
+ builtins.toString value
+ else if builtins.isString value then
+ "\"" + escapeString value + "\""
+ else if builtins.isList value then
+ let
+ items = builtins.map toNestedValue value;
+ in
+ "[" + (builtins.concatStringsSep ", " items) + "]"
+ else if builtins.isAttrs value then
+ let
+ keys = builtins.attrNames value;
+ toKeyValue = k: "\"${k}\": ${toNestedValue value.${k}}";
+ inner = builtins.concatStringsSep ", " (builtins.map toKeyValue keys);
+ in
+ "{ " + inner + " }"
+ else
+ abort "Unexpected error! Please post a new issue...";
+
+ toNestedObject = attrSet:
+ let
+ keys = builtins.attrNames attrSet;
+ kvPairs = builtins.map (k: "\"${k}\": ${toNestedValue attrSet.${k}}") keys;
+ in
+ "{ " + builtins.concatStringsSep ", " kvPairs + " }";
+in
+{
+ options.programs.hyprpanel = {
+ enable = mkEnableOption "HyprPanel";
+ systemd.enable = mkEnableOption "systemd integration";
+ hyprland.enable = mkEnableOption "Hyprland integration";
+ overwrite.enable = mkEnableOption "overwrite config fix";
+
+ theme = mkOption {
+ type = types.str;
+ default = null;
+ example = "catppuccin_mocha";
+ description = "Theme to import (see ./themes/*.json)";
+ };
+
+ layout = mkOption {
+ type = lib.formats.json;
+ default = null;
+ example = ''
+ {
+ "bar.layouts" = {
+ "0" = {
+ left = [ "dashboard" "workspaces" "windowtitle" ];
+ middle = [ "media" ];
+ right = [ "volume" "network" "bluetooth" "battery" "systray" "clock" "notifications" ];
+ };
+ "1" = {
+ left = [ "dashboard" "workspaces" "windowtitle" ];
+ middle = [ "media" ];
+ right = [ "volume" "clock" "notifications" ];
+ };
+ "2" = {
+ left = [ "dashboard" "workspaces" "windowtitle" ];
+ middle = [ "media" ];
+ right = [ "volume" "clock" "notifications" ];
+ };
+ };
+ };
+ '';
+ description = "https://hyprpanel.com/configuration/panel.html";
+ };
+
+ settings = {
+ bar.autoHide = mkStrOption "never";
+ bar.customModules.updates.pollingInterval = mkIntOption 1440000;
+ bar.customModules.updates.updateCommand = mkOption {
+ type = types.str; default = ""; description = "not usable in nix";
+ };
+ bar.launcher.autoDetectIcon = mkBoolOption false;
+ bar.launcher.icon = mkStrOption "";
+ bar.launcher.middleClick = mkStrOption "";
+ bar.launcher.rightClick = mkStrOption "";
+ bar.launcher.scrollDown = mkStrOption "";
+ bar.launcher.scrollUp = mkStrOption "";
+ bar.workspaces.applicationIconOncePerWorkspace = mkBoolOption true;
+ bar.workspaces.hideUnoccupied = mkBoolOption true;
+ bar.workspaces.icons.active = mkStrOption "";
+ bar.workspaces.icons.available = mkStrOption "";
+ bar.workspaces.icons.occupied = mkStrOption "";
+ bar.workspaces.monitorSpecific = mkBoolOption true;
+ bar.workspaces.numbered_active_indicator = mkStrOption "underline";
+ bar.workspaces.showAllActive = mkBoolOption true;
+ bar.workspaces.showApplicationIcons = mkBoolOption false;
+ bar.workspaces.showWsIcons = mkBoolOption false;
+ bar.workspaces.show_icons = mkBoolOption false;
+ bar.workspaces.show_numbered = mkBoolOption false;
+ bar.workspaces.workspaces = mkIntOption 5;
+ hyprpanel.restartAgs = mkBoolOption true;
+ # FIXME: Flag does not exist anymore
+ # hyprpanel.restartCommand = mkStrOption "hyprpanel -q; hyprpanel";
+ hyprpanel.restartCommand = mkStrOption "${pkgs.procps}/bin/pkill -u $USER -USR1 hyprpanel; ${package}/bin/hyprpanel";
+ menus.clock.time.hideSeconds = mkBoolOption false;
+ menus.clock.time.military = mkBoolOption false;
+ menus.clock.weather.enabled = mkBoolOption true;
+ menus.clock.weather.interval = mkIntOption 60000;
+ menus.clock.weather.key = mkStrOption "";
+ menus.clock.weather.location = mkStrOption "Los Angeles";
+ menus.clock.weather.unit = mkStrOption "imperial";
+ menus.dashboard.controls.enabled = mkBoolOption true;
+ menus.dashboard.directories.enabled = mkBoolOption true;
+ menus.dashboard.directories.left.directory1.command = mkStrOption "bash -c \"xdg-open $HOME/Downloads/\"";
+ menus.dashboard.directories.left.directory1.label = mkStrOption " Downloads";
+ menus.dashboard.directories.left.directory2.command = mkStrOption "bash -c \"xdg-open $HOME/Videos/\"";
+ menus.dashboard.directories.left.directory2.label = mkStrOption " Videos";
+ menus.dashboard.directories.left.directory3.command = mkStrOption "bash -c \"xdg-open $HOME/Projects/\"";
+ menus.dashboard.directories.left.directory3.label = mkStrOption " Projects";
+ menus.dashboard.directories.right.directory1.command = mkStrOption "bash -c \"xdg-open $HOME/Documents/\"";
+ menus.dashboard.directories.right.directory1.label = mkStrOption " Documents";
+ menus.dashboard.directories.right.directory2.command = mkStrOption "bash -c \"xdg-open $HOME/Pictures/\"";
+ menus.dashboard.directories.right.directory2.label = mkStrOption " Pictures";
+ menus.dashboard.directories.right.directory3.command = mkStrOption "bash -c \"xdg-open $HOME/\"";
+ menus.dashboard.directories.right.directory3.label = mkStrOption " Home";
+ menus.dashboard.powermenu.avatar.image = mkStrOption "$HOME/.face.icon";
+ menus.dashboard.powermenu.avatar.name = mkStrOption "system";
+ menus.dashboard.powermenu.confirmation = mkBoolOption true;
+ menus.dashboard.powermenu.logout = mkStrOption "hyprctl dispatch exit";
+ menus.dashboard.powermenu.reboot = mkStrOption "systemctl reboot";
+ menus.dashboard.powermenu.shutdown = mkStrOption "systemctl poweroff";
+ menus.dashboard.powermenu.sleep = mkStrOption "systemctl suspend";
+ menus.dashboard.shortcuts.enabled = mkBoolOption true;
+ menus.dashboard.shortcuts.left.shortcut1.command = mkStrOption "microsoft-edge-stable";
+ menus.dashboard.shortcuts.left.shortcut1.icon = mkStrOption "";
+ menus.dashboard.shortcuts.left.shortcut1.tooltip = mkStrOption "Microsoft Edge";
+ menus.dashboard.shortcuts.left.shortcut2.command = mkStrOption "spotify-launcher";
+ menus.dashboard.shortcuts.left.shortcut2.icon = mkStrOption "";
+ menus.dashboard.shortcuts.left.shortcut2.tooltip = mkStrOption "Spotify";
+ menus.dashboard.shortcuts.left.shortcut3.command = mkStrOption "discord";
+ menus.dashboard.shortcuts.left.shortcut3.icon = mkStrOption "";
+ menus.dashboard.shortcuts.left.shortcut3.tooltip = mkStrOption "Discord";
+ menus.dashboard.shortcuts.left.shortcut4.command = mkStrOption "rofi -show drun";
+ menus.dashboard.shortcuts.left.shortcut4.icon = mkStrOption "";
+ menus.dashboard.shortcuts.left.shortcut4.tooltip = mkStrOption "Search Apps";
+ menus.dashboard.shortcuts.right.shortcut1.command = mkStrOption "sleep 0.5 && hyprpicker -a";
+ menus.dashboard.shortcuts.right.shortcut1.icon = mkStrOption "";
+ menus.dashboard.shortcuts.right.shortcut1.tooltip = mkStrOption "Color Picker";
+ menus.dashboard.shortcuts.right.shortcut3.command = mkStrOption "bash -c \"/nix/store/4w82a1c7ypp8zds0yyg4rps44b2dh83i-hyprpanel/share/scripts/snapshot.sh\"";
+ menus.dashboard.shortcuts.right.shortcut3.icon = mkStrOption "";
+ menus.dashboard.shortcuts.right.shortcut3.tooltip = mkStrOption "Screenshot";
+ menus.dashboard.stats.enable_gpu = mkBoolOption false;
+ menus.dashboard.stats.enabled = mkBoolOption true;
+ menus.dashboard.stats.interval = mkIntOption 2000;
+ menus.media.displayTime = mkBoolOption false;
+ menus.media.displayTimeTooltip = mkBoolOption false;
+ menus.media.hideAlbum = mkBoolOption false;
+ menus.media.hideAuthor = mkBoolOption false;
+ menus.media.noMediaText = mkStrOption "No Media Currently Playing";
+ menus.transition = mkStrOption "crossfade";
+ menus.transitionTime = mkIntOption 200;
+ tear = mkBoolOption false;
+ terminal = mkStrOption "$TERM";
+ theme.bar.border.location = mkStrOption "none";
+ theme.bar.buttons.borderSize = mkStrOption "0.1em";
+ theme.bar.buttons.dashboard.enableBorder = mkBoolOption false;
+ theme.bar.buttons.enableBorders = mkBoolOption false;
+ theme.bar.buttons.style = mkStrOption "default";
+ theme.bar.buttons.workspaces.enableBorder = mkBoolOption false;
+ theme.bar.buttons.workspaces.smartHighlight = mkBoolOption true;
+ theme.bar.floating = mkBoolOption false;
+ theme.bar.layer = mkStrOption "top";
+ theme.bar.location = mkStrOption "top";
+ theme.bar.menus.monochrome = mkBoolOption false;
+ theme.bar.transparent = mkBoolOption false;
+ theme.font.name = mkStrOption "Ubuntu Nerd Font";
+ theme.font.size = mkStrOption "1.2rem";
+ theme.font.weight = mkIntOption 600;
+ theme.matugen = mkBoolOption false;
+ theme.matugen_settings.contrast = mkIntOption 0;
+ theme.matugen_settings.mode = mkStrOption "dark";
+ theme.matugen_settings.scheme_type = mkStrOption "tonal-spot";
+ theme.matugen_settings.variation = mkStrOption "standard_1";
+ wallpaper.enable = mkBoolOption true;
+ wallpaper.image = mkStrOption "";
+ wallpaper.pywal = mkBoolOption false;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # TODO:(benvonh) Nerd font packaging changes in NixOS 25.05
+ home.packages = [ package (pkgs.nerdfonts.override { fonts = [ "JetBrainsMono" ]; }) ];
+
+ # NOTE:(benvonh)
+ # When changing the configuration through the GUI, HyprPanel will delete the `config.json` file and create a new
+ # one in its place which destroys the original symlink to the current Home Manager generation. To work around this,
+ # we can automatically delete the `config.json` file before generating a new config by enabling the
+ # `overwrite.enable` option. Though, at some point, a proper fix should be implemented.
+ home.activation =
+ let
+ path = "${config.xdg.configFile.hyprpanel.target}";
+ in mkIf cfg.overwrite.enable {
+ hyprpanel = lib.hm.dag.entryBefore [ "writeBoundary" ] ''
+ [[ -L "${path}" ]] || rm "${path}"
+ '';
+ };
+
+ xdg.configFile.hyprpanel = let
+ theme = if cfg.theme != null then builtins.fromJSON (builtins.readFile ../themes/${cfg.theme}.json) else {};
+ flatSet = flattenAttrs (lib.attrsets.recursiveUpdate cfg.settings theme) "";
+ mergeSet = if cfg.layout == null then flatSet else flatSet // cfg.layout;
+ in {
+ target = "hyprpanel/config.json";
+ text = toNestedObject mergeSet;
+ onChange = "${pkgs.procps}/bin/pkill -u $USER -USR1 hyprpanel || true";
+ };
+
+ systemd.user.services = mkIf cfg.systemd.enable {
+ hyprpanel = {
+ Unit = {
+ Description = "A Bar/Panel for Hyprland with extensive customizability.";
+ Documentation = "https://hyprpanel.com";
+ PartOf = [ "graphical-session.target" ];
+ After = [ "graphical-session-pre.target" ];
+ };
+ Service = {
+ ExecStart = "${package}/bin/hyprpanel";
+ ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR1 $MAINPID";
+ Restart = "on-failure";
+ KillMode = "mixed";
+ };
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+
+ wayland.windowManager.hyprland.settings.exec-once = mkIf cfg.hyprland.enable [ "${package}/bin/hyprpanel" ];
+ };
+}