# Generic user-level (home-manager) config. Reusable across users — nothing # user-specific here. Identity (home.username/homeDirectory/stateVersion, git # user.name/email), host topology (SSH hosts, Syncthing peers) and personal app # choices (grayjay, Claude Code, …) live in the inventory, not in this module. { config, pkgs, lib, ... }: let terminal = "foot"; browser = "librewolf"; editor = "nvim"; # SSH_ASKPASS helper that drives the GPG pinentry-qt: ssh passphrase/PIN # prompts reuse the same Qt dialog as commit signing, adding no extra GUI # toolkit (a packaged askpass would drag in KDE/GTK — hundreds of MB). # ssh signals the prompt kind via $SSH_ASKPASS_PROMPT (OpenSSH ≥ 8.4): # empty real passphrase/PIN entry — collect input, echo to stdout # confirm/none informational, e.g. "Confirm user presence" (touch request) # A FIDO touch can't be faked by software, so those messages need no dialog: # succeed silently instead of popping a spurious PIN box (the token blinks). sshAskpass = pkgs.writeShellScript "ssh-askpass-pinentry" '' [ -n "''${SSH_ASKPASS_PROMPT:-}" ] && exit 0 desc=$(printf '%s' "''${1:-Passphrase:}" | sed 's/%/%25/g') printf 'SETTITLE ssh\nSETPROMPT PIN:\nSETDESC %s\nGETPIN\nBYE\n' "$desc" \ | ${pkgs.pinentry-qt}/bin/pinentry-qt \ | sed -n 's/^D //p' ''; # Screenshot helper for the Print-key binds below. Two axes, chosen by args: # scope ($1 = full | region, the latter an interactive slurp selection) and # destination ($2 = file | clip). Files are timestamped PNGs under # ~/Pictures/Screenshots; clip copies to the Wayland clipboard. A mako toast # confirms each grab. slurp exits non-zero on Escape, which aborts here. screenshot = pkgs.writeShellScript "screenshot" '' set -eu if [ "$1" = region ]; then geom=$(${pkgs.slurp}/bin/slurp) || exit 1 grab() { ${pkgs.grim}/bin/grim -g "$geom" "$@"; } else grab() { ${pkgs.grim}/bin/grim "$@"; } fi if [ "$2" = clip ]; then grab - | ${pkgs.wl-clipboard}/bin/wl-copy ${pkgs.libnotify}/bin/notify-send "Screenshot" "Copied to clipboard" else dir="${config.xdg.userDirs.pictures}/Screenshots" mkdir -p "$dir" file="$dir/$(date +%Y-%m-%d-%H%M%S).png" grab "$file" ${pkgs.libnotify}/bin/notify-send "Screenshot" "Saved $file" fi ''; # Session/power menu: a fuzzel --dmenu picker (not app-list entries), bound to # $mod+Backspace. --index returns the chosen row number, so dispatch matches on # position, not label text. Icons use the Papirus names via fuzzel's dmenu # icon protocol (NUL + 0x1f after each label). Escape/no pick → fuzzel exits # non-zero, command substitution is empty, case falls through → no-op. # # Icon names deliberately picked from Papirus's apps/ (colored) set that have # NO actions/ variant: system-{reboot,shutdown,log-out} exist in both, and at # dmenu line-height fuzzel resolves the monochrome actions/ copy. gnome-logout # / system-restart / gnome-session-halt are apps-only, so they stay colored. powerMenu = pkgs.writeShellScript "power-menu" '' case $(printf 'Log Out\0icon\x1fgnome-logout\nReboot\0icon\x1fsystem-restart\nShut Down\0icon\x1fgnome-session-halt\n' \ | ${pkgs.fuzzel}/bin/fuzzel --dmenu --index --prompt 'power ') in 0) ${config.wayland.windowManager.hyprland.finalPackage}/bin/hyprctl dispatch exit ;; 1) ${pkgs.systemd}/bin/systemctl reboot ;; 2) ${pkgs.systemd}/bin/systemctl poweroff ;; esac ''; in { imports = [ ./gpg.nix ./media.nix ]; # Put ~/.local/bin (the XDG user-bin dir) on PATH so hand-dropped scripts run # by name without wiring each through nix. home.sessionPath = [ "${config.home.homeDirectory}/.local/bin" ]; home.sessionVariables = { TERMINAL = terminal; BROWSER = browser; EDITOR = editor; # Keep $HOME clean: route node/npm out of ~ into XDG dirs. NODE_REPL_HISTORY = "${config.xdg.stateHome}/node_repl_history"; NPM_CONFIG_CACHE = "${config.xdg.cacheHome}/npm"; NPM_CONFIG_USERCONFIG = "${config.xdg.configHome}/npm/npmrc"; # Python REPL: UTF-8 I/O, custom startup file, XDG-routed history. PYTHONIOENCODING = "UTF-8"; PYTHONSTARTUP = "${config.xdg.configHome}/python/startup"; PYTHON_HISTFILE = "${config.xdg.cacheHome}/python/history"; # psql / wget / readline configs, XDG-routed (the rc files are declared # via xdg.configFile below). PSQLRC = "${config.xdg.configHome}/psql/rc"; WGETRC = "${config.xdg.configHome}/wget/wgetrc"; INPUTRC = "${config.xdg.configHome}/readline/inputrc"; # Rust/cargo: keep registry cache, git checkouts and `cargo install` bins # out of ~/.cargo (the toolchain itself comes from the flake's rust devShell). CARGO_HOME = "${config.xdg.dataHome}/cargo"; # Mute the harmless "Wayland does not support QWindow::requestActivate()" # spam Qt apps (keepassxc) print; scoped to that one log category only. QT_LOGGING_RULES = "qt.qpa.wayland=false"; # Run Chromium/Electron apps (brave, signal, grayjay's webview) natively on # Wayland instead of XWayland: nixpkgs' chromium/electron wrappers read this # and inject the Ozone Wayland flags. This is a Wayland-only (Hyprland) # session, so XWayland is pure downside (blurry fractional scaling, extra # hop). Override per-app with --ozone-platform=x11 if one misbehaves. NIXOS_OZONE_WL = "1"; # GUI helper for ssh passphrase/PIN prompts (see sshAskpass above). Not # forced: with a tty ssh still prompts inline; the helper is used only when a # caller sets SSH_ASKPASS_REQUIRE (e.g. a headless/agent context). SSH_ASKPASS = "${sshAskpass}"; }; # Sets XDG_{CONFIG,DATA,CACHE}_HOME and enables xdg.configFile management. xdg.enable = true; home.packages = with pkgs; [ librewolf keepassxc # config in ~/.config/keepassxc papirus-icon-theme # fuzzel icon-theme; ships non-symbolic power icons # (Adwaita only has them as symbolic, which fuzzel skips) # claude-code is installed by programs.claude-code below (its wrapped # finalPackage) — do NOT also list it here, or buildEnv conflicts on the # wrapper binary. nodejs highlight # `ccat` alias: syntax-highlighted cat libreoffice grim # screenshot capture (also used by the Print-key binds) slurp # interactive region selector for grim ]; # Canonical XDG user dirs. Only Pictures is actually used (screenshot target, # file-picker default). createDirectories stays off so the rest aren't # littered into $HOME — they're merely declared in ~/.config/user-dirs.dirs, # created lazily by whatever app wants them. xdg.userDirs = { enable = true; createDirectories = false; pictures = "${config.home.homeDirectory}/Pictures"; }; # ---- Per-tool config files, routed into XDG dirs --------------------- xdg.configFile = { "python/startup".source = ./assets/python-startup.py; # psql: unlimited, per-database history file (kept out of $HOME). "psql/rc".text = '' \set HISTFILE ${config.xdg.cacheHome}/psql/history-:DBNAME \set HISTSIZE -1 ''; # wget: keep its HSTS db in the cache dir, not ~/. "wget/wgetrc".text = '' hsts-file=${config.xdg.cacheHome}/wget/hsts ''; # readline: affects any libreadline program (python, psql, bash...). # zsh ignores it — it uses ZLE (see programs.zsh below). Binds Up/Down to # prefix history search with the cursor jumped to end of line. "readline/inputrc".text = '' $include /etc/inputrc "\C-x\C-p": history-search-backward "\C-x\C-n": history-search-forward "\e[A": "\C-x\C-p\C-e" "\e[B": "\C-x\C-n\C-e" "\eOA": "\C-x\C-p\C-e" "\eOB": "\C-x\C-n\C-e" ''; }; programs.home-manager.enable = true; programs.git = { enable = true; settings = { pull.rebase = true; init.defaultBranch = "master"; # Detect whitespace errors; speed up untracked-file scans. apply.whitespace = "fix"; core.whitespace = "space-before-tab,-indent-with-non-tab,trailing-space"; core.untrackedCache = true; branch.sort = "-committerdate"; color.ui = "auto"; diff.renames = "copies"; help.autocorrect = 1; merge.log = true; push = { default = "simple"; followTags = true; }; submodule.recurse = true; alias = { l = "log --pretty=oneline -n 20 --graph --abbrev-commit"; logs = "log --show-signature"; logo = "log --pretty=format:\"%C(auto)%h%x09%ad%x09%<(16,trunc)%an%x09%s%x09%d\""; s = "status -s"; d = "!\"git diff-index --quiet HEAD -- || clear; git --no-pager diff --patch-with-stat\""; p = "pull --recurse-submodules"; c = "clone --recursive"; ca = "!git add -A && git commit -av"; go = "!f() { git checkout -b \"$1\" 2> /dev/null || git checkout \"$1\"; }; f"; amend = "commit --amend --reuse-message=HEAD"; reb = "!r() { git rebase -i HEAD~$1; }; r"; aliases = "config --get-regexp alias"; contributors = "shortlog --summary --numbered"; whoami = "config user.email"; # Delete local branches already merged into the current branch. dm = "!git branch --merged | grep -v '\\*' | xargs -n 1 git branch -d"; }; }; }; programs.foot = { enable = true; settings = { main = { font = "JetBrainsMono Nerd Font:size=11"; pad = "8x8"; }; colors-dark = { alpha = 0.9; }; }; }; # App launcher ($mod+d in Hyprland). Wayland-native, launches apps detached # so their stderr goes to the journal, not whatever terminal you're in. programs.fuzzel = { enable = true; settings = { main.font = "JetBrainsMono Nerd Font:size=11"; # Papirus for colored power icons in the $mod+Backspace dmenu (Adwaita # ships those only as symbolic, and fuzzel skips symbolic dirs). It # inherits hicolor, so app icons still resolve (recolored to Papirus). main.icon-theme = "Papirus"; # Fuzzel ignores the portal color-scheme, and its built-in palette is # light — dark has to be set here. Matches waybar (#222/#bbb + One Dark # accents); flat, thin border, no rounding per the minimal look. border = { width = 1; radius = 0; }; colors = { background = "222222f2"; text = "bbbbbbff"; match = "e5c07bff"; selection = "333333ff"; selection-text = "ffffffff"; selection-match = "e5c07bff"; border = "444444ff"; }; }; }; # Curate what fuzzel's app-list mode shows. Fuzzel just enumerates XDG # `.desktop` files, so we shape that set here. Shadow a package's entry by # redefining its id with noDisplay = true: our ~/.local/share/applications # copy wins over the profile's. noDisplay only hides from launchers/menus — # the app stays wired up for xdg-open and "open with", so opening a file # *with* mpv/zathura is unaffected. The attrset merges with anything a private # host adds. noDisplay is mkDefault so a host can un-hide one (e.g. re-expose # mpv) with a plain override, no conflict. (Power/session actions are not # entries here — see the $mod+Backspace dmenu power menu above.) xdg.desktopEntries = let hide = exec: { inherit exec; name = "hidden"; noDisplay = lib.mkDefault true; }; in { # foot's three entries: launched via $mod+Return, never from a menu. foot = hide "foot"; foot-server = hide "foot --server"; footclient = hide "footclient"; # mpv/zathura are file handlers, not things to launch bare from a menu. mpv = hide "mpv %U"; umpv = hide "umpv %U"; "org.pwmt.zathura" = hide "zathura %U"; "org.pwmt.zathura-djvu" = hide "zathura %U"; "org.pwmt.zathura-cb" = hide "zathura %U"; # nvim is $EDITOR / opened from a terminal; no place in a GUI launcher. nvim = hide "nvim %F"; }; programs.fzf.enable = true; # provides the ^f "cd into fzf dir" binding # direnv + nix-direnv: a project's `.envrc` with `use flake` auto-loads any # devShell on cd-in (and caches the eval) — general per-directory environments, # not tied to one language. First consumer is the shared Rust dev shell (see # the `devShells` comment in flake.nix). Zsh integration is on by default. programs.direnv = { enable = true; nix-direnv.enable = true; }; programs.zsh = { enable = true; autosuggestion.enable = true; syntaxHighlighting.enable = true; enableCompletion = true; # Keep the completion dump in the cache dir, not ~/.zcompdump. completionInit = '' _zcd="${config.xdg.cacheHome}/zsh"; [ -d "$_zcd" ] || mkdir -p "$_zcd" autoload -U compinit && compinit -d "$_zcd/zcompdump" ''; defaultKeymap = "viins"; # vi keybindings history = { path = "${config.xdg.cacheHome}/zsh/history"; size = 1000000; save = 1000000; ignoreAllDups = true; # drop older duplicate of a re-run command ignoreSpace = true; # don't record space-prefixed commands share = true; }; shellAliases = { vim = "nvim"; vimdiff = "nvim -d"; cp = "cp -iv"; mv = "mv -iv"; rm = "rm -vI"; mkd = "mkdir -pv"; ls = "ls -hN --color=auto --group-directories-first"; grep = "grep --color=auto"; diff = "diff --color=auto"; ccat = "highlight --out-format=ansi"; ka = "killall"; g = "git"; e = editor; v = editor; }; # Interactive bits with no first-class HM option: prompt, autocd, # completion-menu styling, vi cursor shaping, and a few keybinds. initContent = '' # Colored prompt: [user@host ~]$ autoload -U colors && colors PS1="%B%{$fg[red]%}[%{$fg[yellow]%}%n%{$fg[green]%}@%{$fg[blue]%}%M %{$fg[magenta]%}%~%{$fg[red]%}]%{$reset_color%}$%b " setopt autocd interactive_comments stty stop undef # free up ctrl-s export KEYTIMEOUT=1 # Completion menu: arrow-select, include dotfiles, vi keys in the menu. zstyle ':completion:*' menu select zmodload zsh/complist _comp_options+=(globdots) bindkey -M menuselect 'h' vi-backward-char bindkey -M menuselect 'k' vi-up-line-or-history bindkey -M menuselect 'l' vi-forward-char bindkey -M menuselect 'j' vi-down-line-or-history bindkey -v '^?' backward-delete-char bindkey '^[[P' delete-char # Beam cursor in insert mode, block in normal mode. function zle-keymap-select () { case $KEYMAP in vicmd) echo -ne '\e[1 q';; viins|main) echo -ne '\e[5 q';; esac } zle -N zle-keymap-select zle-line-init() { zle -K viins; echo -ne "\e[5 q"; } zle -N zle-line-init echo -ne '\e[5 q' preexec() { echo -ne '\e[5 q'; } # Edit current line in $EDITOR with ctrl-e; quick bc and fzf-cd binds. autoload edit-command-line; zle -N edit-command-line bindkey '^e' edit-command-line bindkey -s '^a' 'bc -lq\n' bindkey -s '^f' 'cd "$(dirname "$(fzf)")"\n' # Up/Down: search history by the text already typed before the cursor, # then place the cursor at the end of the matched line. autoload -U history-search-end zle -N history-beginning-search-backward-end history-search-end zle -N history-beginning-search-forward-end history-search-end bindkey "^[[A" history-beginning-search-backward-end bindkey "^[[B" history-beginning-search-forward-end ''; }; home.pointerCursor = { name = "Adwaita"; package = pkgs.adwaita-icon-theme; size = 24; gtk.enable = true; }; gtk = { enable = true; theme = { name = "Adwaita-dark"; package = pkgs.gnome-themes-extra; }; # Keep the GTK2 rc out of $HOME (default ~/.gtkrc-2.0). Setting this also # makes home-manager export GTK2_RC_FILES so GTK2 apps still find it. gtk2.configLocation = "${config.xdg.configHome}/gtk-2.0/gtkrc"; # The theme *name* alone isn't enough: the qgtk3 Qt bridge and apps' dark # detection (e.g. keepassxc's "auto" theme) read this flag, not the name. # Set it so both GTK apps and the Qt-via-gtk3 apps resolve to dark. gtk3.extraConfig.gtk-application-prefer-dark-theme = true; gtk4.extraConfig.gtk-application-prefer-dark-theme = true; }; # The canonical dark/light signal on Wayland: apps query the xdg-desktop-portal # Settings interface for org.freedesktop.appearance/color-scheme over D-Bus, and # the gtk portal serves that from this dconf key. keepassxc's "auto" theme reads # it (NixUtils::isDarkMode) and ignores the GTK theme name / Qt style entirely, # so without this the portal reports 0 ("no preference") and keepass stays light. # prefer-dark makes the portal report dark to every color-scheme-aware app. dconf.settings."org/gnome/desktop/interface".color-scheme = "prefer-dark"; # Match the Qt apps (keepassxc, pinentry-qt, ssh askpass) to the GTK # Adwaita-dark above, else they render the default light Fusion style. Use the # gtk3 platform theme, not an adwaita *style* override: nixpkgs wraps Qt apps # with a baked QT_PLUGIN_PATH pointing only at their build deps, so an external # style plugin (adwaita-qt) is unreachable and QT_STYLE_OVERRIDE is silently # ignored. The gtk3 plugin (libqgtk3) ships inside qtbase itself, so it's # always on that path, and it reads the GTK theme (Adwaita-dark) directly. qt = { enable = true; platformTheme.name = "gtk3"; }; # The home-manager Hyprland module below enables xdg.portal and pins the # portal search dir (NIX_XDG_DESKTOP_PORTAL_DIR) to this user profile, adding # only the Hyprland backend — which implements Screenshot/ScreenCast/ # GlobalShortcuts but not FileChooser. Add the GTK portal here (system-level # extraPortals aren't in the pinned dir, so they're never seen) and route # FileChooser to it, else file dialogs in Chromium/Electron apps (signal, # brave) fail with a DBus "No such interface # org.freedesktop.portal.FileChooser" error. xdg.portal = { extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; config.common = { default = [ "hyprland" "gtk" ]; "org.freedesktop.impl.portal.FileChooser" = [ "gtk" ]; }; }; wayland.windowManager.hyprland = { enable = true; configType = "hyprlang"; settings = { monitor = ",preferred,auto,1"; # The qt module sets QT_QPA_PLATFORMTHEME=gtk3 (→ Qt apps read the GTK # Adwaita-dark theme), but only into hm-session-vars.sh + the systemd user # env. gpg-agent (a user service) inherits it, so commit-signing pinentry # is dark — but Hyprland doesn't source that file, so its children (foot → # shell → ssh askpass → pinentry) launch without it and came up light. # Verified the single var flips pinentry-qt dark; export it here so every # session child gets it too. env = [ "QT_QPA_PLATFORMTHEME,gtk3" ]; general = { layout = "master"; gaps_in = 5; gaps_out = 10; border_size = 2; }; decoration = { rounding = 0; shadow.enabled = false; blur.enabled = false; }; animations = { enabled = false; }; # Suppress Hyprland's built-in default wallpaper. The wallpaper here is # driven by a userspace daemon (awww, see modules/media.nix) that only # starts — and pushes the chosen image — once the compositor is up. Until # then Hyprland paints its bundled splash, so on login it briefly shows # before the real wallpaper replaces it. Disabling it leaves the plain # background colour in that gap instead of a flash of unrelated art. misc = { force_default_wallpaper = 0; disable_hyprland_logo = true; }; input = { kb_layout = "de"; follow_mouse = 1; }; "$mod" = "SUPER"; # Drag to move ($mod+LMB) / resize ($mod+RMB) any window. Needed to reach # oversized floating dialogs (e.g. GTK save/upload pickers) whose confirm # button lands off-screen on short displays. bindm = [ "$mod, mouse:272, movewindow" "$mod, mouse:273, resizewindow" ]; bind = [ "$mod, Return, exec, footclient" "$mod, q, killactive" "$mod SHIFT, q, exit" "$mod, f, fullscreen, 1" "$mod SHIFT, f, fullscreen, 0" "$mod, w, exec, ${browser}" "$mod, d, exec, fuzzel" "$mod, Backspace, exec, ${powerMenu}" "$mod, j, layoutmsg, cyclenext" "$mod SHIFT, j, layoutmsg, swapnext" "$mod, k, layoutmsg, cycleprev" "$mod SHIFT, k, layoutmsg, swapprev" "$mod, h, layoutmsg, splitratio -0.05" "$mod, l, layoutmsg, splitratio +0.05" ] ++ ( # Dynamic definitions for viewing workspaces 1 to 9 and moving windows between them. # References physical key codes for robustness with the SHIFT key, e.g. `code:10` instead of `1`. builtins.concatLists ( builtins.genList (i: let target = i + 1; in [ "$mod, code:1${toString i}, workspace, ${toString target}" "$mod SHIFT, code:1${toString i}, movetoworkspace, ${toString target}" ] ) 9 ) ) ++ [ ", XF86MonBrightnessUp, exec, brightnessctl set 15%+" ", XF86MonBrightnessDown, exec, brightnessctl set 15%-" ", XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle" ", XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 3%+" ", XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 3%-" # Screenshots. $mod = pick a region (slurp), SHIFT = to clipboard; # bare Print grabs the whole screen to a file (the common case). ", Print, exec, ${screenshot} full file" "$mod, Print, exec, ${screenshot} region file" "SHIFT, Print, exec, ${screenshot} full clip" "$mod SHIFT, Print, exec, ${screenshot} region clip" ]; exec-once = [ "waybar" # foot in server/daemon mode: $mod+Return runs `footclient`, which # attaches to this one process → faster new-window startup, shared RAM. # Uses foot's default socket, which footclient also defaults to. # Tradeoff: if the server dies, all foot windows die with it. "foot --server" # wallpaper (see modules/media.nix): start the awww daemon, then restore # the persisted choice. awww keeps no state across restarts, so re-push # the ~/.local/share/bg symlink if it exists. "awww-daemon" "sh -c '[ -e \"$HOME/.local/share/bg\" ] && awww img \"$HOME/.local/share/bg\"'" ]; }; }; # Notification daemon. notify-send clients (e.g. imv's info/delete toasts in # modules/media.nix, gpg-agent errors) need a running server on the bus. # Minimal to match the desktop: Nerd Font, no rounding, square border. # Secret Service provider (freedesktop org.freedesktop.secrets). Bare Wayland # session has none otherwise; apps using libsecret/qtkeychain need it. services.gnome-keyring.enable = true; # ssh-agent user service; sets SSH_AUTH_SOCK so `ssh-add` works in any shell. # gnome-keyring above no longer ships an ssh component (dropped upstream), and # gpg's ssh support is off, so this is the only agent. Keys are added on # demand — nothing here presumes a particular ssh config. services.ssh-agent.enable = true; # The agent, not the ssh client, collects the FIDO PIN when signing with a # verify-required sk-ed25519 key it holds — so the *agent's* environment needs # the askpass helper. The systemd user service starts with a minimal env that # lacks it, so such signatures fail with the misleading "incorrect passphrase # supplied to decrypt private key". REQUIRE=force drives the prompt with no # DISPLAY (a daemon has none); sshAskpass exits 0 for the touch step, so only # the PIN pops a dialog. systemd.user.services.ssh-agent.Service.Environment = [ "SSH_ASKPASS=${sshAskpass}" "SSH_ASKPASS_REQUIRE=force" ]; services.mako = { enable = true; settings = { font = "JetBrainsMono Nerd Font 10"; border-radius = 0; default-timeout = 5000; }; }; programs.waybar = { enable = true; settings = { mainBar = { layer = "top"; position = "top"; height = 28; modules-left = [ "hyprland/workspaces" ]; modules-center = [ "clock" ]; modules-right = [ "pulseaudio" "network" "battery" ]; "hyprland/workspaces" = { format = "{id}"; }; clock = { format = "{:%H:%M %a %d %b}"; }; pulseaudio = { format = "{volume}% {icon}"; format-muted = "󰝟"; format-icons = { default = [ "󰕿" "󰖀" "󰕾" ]; headphone = "󰋋"; }; }; network = { format-wifi = "{essid} 󰖩"; format-ethernet = "eth 󰈀"; format-disconnected = "󰖪"; }; battery = { states = { warning = 30; critical = 15; }; format = "{capacity}% {icon}"; format-charging = "{capacity}% 󰂄"; format-plugged = "{capacity}% 󰚥"; format-icons = [ "󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹" ]; }; }; }; style = '' * { font-family: "JetBrainsMono Nerd Font"; font-size: 13px; } window#waybar { background: #222222; color: #bbbbbb; } #pulseaudio, #network, #battery { padding: 0 12px; } #battery.warning { color: #e5c07b; } #battery.critical { color: #e06c75; } #battery.charging { color: #98c379; } ''; }; }