Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Home Manager module #605

Merged
merged 7 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ If you install the fonts after installing HyperPanel, you will need to restart H

### NixOS & Home-Manager

<details>
<summary>Overlay Method</summary>
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
Expand Down Expand Up @@ -220,6 +222,111 @@ wayland.windowManager.hyprland.settings.exec-once = [
];

```
</details>

<details>
<summary>Home Manager Module</summary>
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: <same as gui>
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.
</details>

### Launch the panel

Expand Down
2 changes: 2 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,7 @@
fi
'';
};

homeManagerModules.hyprpanel = import ./nix/module.nix self;
};
}
267 changes: 267 additions & 0 deletions nix/module.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
self: { lib, pkgs, config, ... }:
let
inherit (lib) types mkIf mkOption mkEnableOption;

cfg = config.programs.hyprpanel;

jsonFormat = pkgs.formats.json { };

# 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 these do...
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 = jsonFormat.type;
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 applicable with 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 \"${../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" ];
};
}
Loading