feat: add moonarch-waybar config merger wrapper
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 4s
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 4s
Waybar's include directive cannot merge arrays, making per-machine module customization impossible without duplicating the entire config. moonarch-waybar merges an optional ~/.config/waybar/userconfig with the system config, supporting prepend/append on module arrays and object merge for module definitions. Generates user style.css with @import of system styles on first run. System waybar config converted from JSONC to valid JSON for jq compatibility. Niri startup and hotkey updated to use the wrapper.
This commit is contained in:
parent
4dd8aae2f0
commit
2363e76b4a
11
CLAUDE.md
11
CLAUDE.md
@ -27,6 +27,17 @@ Waybar-Toggle für wlsunset (Wayland-nativer Blaufilter):
|
||||
- Signal SIGRTMIN+11 für sofortiges Waybar-Refresh
|
||||
- Scripts: `moonarch-nightlight` (Toggle), `moonarch-waybar-nightlight` (Status-JSON)
|
||||
|
||||
## Waybar Config Merger (moonarch-waybar)
|
||||
|
||||
Waybar wird über `moonarch-waybar` gestartet (nicht direkt). Der Wrapper merged eine optionale User-Config (`~/.config/waybar/userconfig`) mit der System-Config (`/etc/xdg/waybar/config`):
|
||||
- `prepend`/`append`-Keys in der userconfig erweitern `modules-left`/`modules-center`/`modules-right` Arrays
|
||||
- Alle anderen Top-Level-Keys werden als Modul-Definitionen per Object-Merge eingefügt
|
||||
- Merge wird nur bei Änderungen ausgeführt (Timestamp-Vergleich)
|
||||
- Bei Fehler: `notify-send` + `logger`, Waybar startet mit System-Config
|
||||
- Generiert `~/.config/waybar/style.css` mit `@import` der System-Styles falls nicht vorhanden
|
||||
- Benötigt `jq` (in PKGBUILD als Dependency)
|
||||
- System-Config muss valides JSON sein (kein JSONC)
|
||||
|
||||
## Konventionen
|
||||
|
||||
- Paketlisten sind einfache Textdateien, ein Paket pro Zeile, Kommentare mit `#`
|
||||
|
||||
59
defaults/bin/moonarch-waybar
Executable file
59
defaults/bin/moonarch-waybar
Executable file
@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# ABOUTME: Wrapper that merges system waybar config with per-machine userconfig.
|
||||
# ABOUTME: Handles array prepend/append that waybar's native include cannot do.
|
||||
|
||||
SYSTEM_CONFIG="/etc/xdg/waybar/config"
|
||||
SYSTEM_STYLE="/etc/xdg/waybar/style.css"
|
||||
USER_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/waybar"
|
||||
USERCONFIG="$USER_DIR/userconfig"
|
||||
OUTPUT="$USER_DIR/config"
|
||||
USER_STYLE="$USER_DIR/style.css"
|
||||
|
||||
merge_config() {
|
||||
mkdir -p "$USER_DIR"
|
||||
|
||||
if ! jq -s '
|
||||
.[0] as $sys | .[1] as $user |
|
||||
(($user.prepend // {}) | to_entries) as $prepends |
|
||||
(($user.append // {}) | to_entries) as $appends |
|
||||
$sys |
|
||||
reduce $prepends[] as $p (.;
|
||||
.[$p.key] = ($p.value + (.[$p.key] // []))
|
||||
) |
|
||||
reduce $appends[] as $a (.;
|
||||
.[$a.key] = ((.[$a.key] // []) + $a.value)
|
||||
) |
|
||||
($user | del(.prepend) | del(.append)) as $extras |
|
||||
. * $extras
|
||||
' "$SYSTEM_CONFIG" "$USERCONFIG" > "${OUTPUT}.tmp" 2>&1; then
|
||||
local err
|
||||
err=$(cat "${OUTPUT}.tmp")
|
||||
rm -f "${OUTPUT}.tmp"
|
||||
logger -t moonarch-waybar "Config merge failed: $err"
|
||||
notify-send -u critical "moonarch-waybar" "Config merge failed — using system config.\n$err"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mv "${OUTPUT}.tmp" "$OUTPUT"
|
||||
}
|
||||
|
||||
bootstrap_style() {
|
||||
if [[ ! -f "$USER_STYLE" ]]; then
|
||||
mkdir -p "$USER_DIR"
|
||||
cat > "$USER_STYLE" << 'CSS'
|
||||
/* Generated by moonarch-waybar — add custom styles below */
|
||||
@import url("/etc/xdg/waybar/style.css");
|
||||
CSS
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ -f "$USERCONFIG" ]]; then
|
||||
if [[ ! -f "$OUTPUT" ]] ||
|
||||
[[ "$USERCONFIG" -nt "$OUTPUT" ]] ||
|
||||
[[ "$SYSTEM_CONFIG" -nt "$OUTPUT" ]]; then
|
||||
merge_config
|
||||
fi
|
||||
bootstrap_style
|
||||
fi
|
||||
|
||||
exec waybar "$@"
|
||||
@ -79,7 +79,7 @@ layout {
|
||||
|
||||
// xwayland-satellite is managed automatically since niri 25.08
|
||||
// kanshi is managed via systemd user service (kanshi.service)
|
||||
spawn-at-startup "waybar"
|
||||
spawn-at-startup "moonarch-waybar"
|
||||
spawn-at-startup "swaync"
|
||||
spawn-at-startup "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1"
|
||||
spawn-at-startup "nm-applet" "--indicator"
|
||||
@ -126,7 +126,7 @@ binds {
|
||||
|
||||
Super+C hotkey-overlay-title=null { spawn "walker" "-s" "clipboard"; }
|
||||
|
||||
Alt+W { spawn-sh "killall waybar && waybar &"; }
|
||||
Alt+W { spawn-sh "killall waybar && moonarch-waybar &"; }
|
||||
|
||||
Super+E { spawn-sh "xdg-open ~"; }
|
||||
|
||||
|
||||
@ -115,7 +115,6 @@
|
||||
"on-scroll-down": "shift_down",
|
||||
"on-click-middle": "alarm-clock-applet"
|
||||
}
|
||||
// "on-click": "evolution"
|
||||
},
|
||||
"user": {
|
||||
"format": "{user}",
|
||||
@ -191,7 +190,6 @@
|
||||
"format": "{icon}",
|
||||
"icon-size": 16,
|
||||
"tooltip-format": "{title:.100}",
|
||||
// "sort-by-app-id": true,
|
||||
"on-click": "activate",
|
||||
"on-click-middle": "close",
|
||||
"on-right-middle": "minimize-raise",
|
||||
@ -204,7 +202,7 @@
|
||||
"firefox": "Firefox",
|
||||
"VSCodium": "Code",
|
||||
"codium": "Code",
|
||||
"Alacritty": "Terminal",
|
||||
"Alacritty": "Terminal"
|
||||
},
|
||||
"squash-list": [
|
||||
"firefox"
|
||||
@ -242,10 +240,8 @@
|
||||
"escape": true
|
||||
},
|
||||
"cava": {
|
||||
// "cava_config": "$XDG_CONFIG_HOME/cava/cava.conf",
|
||||
"framerate": 30,
|
||||
"autosens": 1,
|
||||
//"sensitivity": 50,
|
||||
"bars": 2,
|
||||
"lower_cutoff_freq": 50,
|
||||
"higher_cutoff_freq": 10000,
|
||||
@ -356,9 +352,8 @@
|
||||
"signal": 9
|
||||
},
|
||||
"bluetooth": {
|
||||
// "controller": "controller1", // specify the alias of the controller if there are more than 1 on the system
|
||||
"format": "",
|
||||
"format-disabled": "", // an empty format will hide the module
|
||||
"format-disabled": "",
|
||||
"format-connected": "<small>{num_connections}</small>",
|
||||
"tooltip-format": "{controller_alias}\t{controller_address}",
|
||||
"tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{device_enumerate}\t{device_battery_percentage}%",
|
||||
@ -410,13 +405,10 @@
|
||||
"niri/workspaces": {
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
// Named workspaces
|
||||
// (you need to configure them in niri)
|
||||
"browser": "",
|
||||
"discord": "",
|
||||
"chat": "<b></b>",
|
||||
|
||||
// Icons by state
|
||||
"active": "",
|
||||
"default": ""
|
||||
}
|
||||
@ -440,114 +432,56 @@
|
||||
"device": "intel_backlight"
|
||||
},
|
||||
"cffi/niri-workspaces-enhanced": {
|
||||
// Make sure to set the path to the install location on your system
|
||||
// "module_path": "~/.config/waybar/niri-workspaces-enhanced.so",
|
||||
"module_path": "/usr/lib/waybar/libwaybar_niri_workspaces_enhanced.so",
|
||||
// Format string for workspace labels. Available placeholders:
|
||||
// {index} - Workspace index number
|
||||
// {name} - Workspace name (might be empty)
|
||||
// {index-and-name} - Index followed by name if present (e.g., "1 Work")
|
||||
// {value} - Name if present, otherwise index
|
||||
// {separator} - ": " when icons are present, "" when empty
|
||||
// {window-icons} - Formatted icons for windows in workspace
|
||||
"format": "{window-icons}",
|
||||
// Apply separate styles to icons depending on current state
|
||||
"window-icon-format": {
|
||||
"default": "{icon}",
|
||||
"urgent": "<span foreground='red'>{icon}</span>",
|
||||
"focused": "<span foreground='blue'>{icon}</span>",
|
||||
"focused": "<span foreground='blue'>{icon}</span>"
|
||||
},
|
||||
// A mapping from window app_id to icon. Note that this module does
|
||||
// case-insensitive matching of app_ids, so capitalization doesn't matter.
|
||||
"window-icons": {
|
||||
"com.mitchellh.ghostty": "",
|
||||
"darktable": "",
|
||||
"foot": "",
|
||||
"google-chrome": "",
|
||||
"spotify": "",
|
||||
"steam": "",
|
||||
"steam": ""
|
||||
},
|
||||
// If no icon is found for a window, the default is used instead
|
||||
"window-icon-default": "*",
|
||||
"window-icon-default": "*"
|
||||
},
|
||||
"height": 40,
|
||||
"cffi/niri-windows": {
|
||||
// path where you placed the .so file
|
||||
"module_path": "/usr/lib/waybar-niri-windows.so",
|
||||
// configure the module's behavior
|
||||
"options": {
|
||||
// set the module mode
|
||||
// "graphical" (default): draw a minimap of windows in the current workspace
|
||||
// "text": draws symbols and a focus indicator for each column (mirrors v1 behavior)
|
||||
"mode": "graphical",
|
||||
|
||||
// ======= graphical mode options =======
|
||||
// when to show floating windows
|
||||
// - "always": always show floating window view, even if there are no floating windows
|
||||
// - "auto" (default): show floating window view if there are floating windows on the current workspace
|
||||
// - "never": never show floating windows
|
||||
"show-floating": "auto",
|
||||
// pick where the floating windows be shown relative to tiled windows
|
||||
// - "left": show floating windows on the left
|
||||
// - "right" (default): show floating windows on the right
|
||||
"floating-position": "right",
|
||||
// set minimum size of windows, in pixels (default: 1, minimum: 1)
|
||||
// if this value is too large to fit all windows (e.g. in a column with many windows),
|
||||
// it will be reduced
|
||||
"minimum-size": 1,
|
||||
// set spacing between windows/columns, in pixels (default: 1, minimum: 0)
|
||||
// if this value is too large, it will be reduced
|
||||
"spacing": 1,
|
||||
// set minimum size of windows, in pixels, to draw icons for (default: 0, minimum: 0)
|
||||
// if unset or 0, icons will only be drawn for tiled windows that are the only one in their column
|
||||
// if 1+, icons will be drawn for all windows where w >= icon-minimum-size and h >= icon-minimum-size
|
||||
// icons must be set in the "rules" section below for this to have any effect
|
||||
"icon-minimum-size": 0,
|
||||
// account for borders when calculating window sizes; see note below (default: 0, minimum: 0)
|
||||
"column-borders": 0, // border on .column
|
||||
"floating-borders": 0, // border on .floating
|
||||
// trigger actions on tile click (see https://yalter.github.io/niri/niri_ipc/enum.Action.html for available actions)
|
||||
// only actions that take a single window ID are supported
|
||||
// set to an empty string to disable
|
||||
"on-tile-click": "FocusWindow", // (default: FocusWindow)
|
||||
"on-tile-middle-click": "CloseWindow", // (default: CloseWindow)
|
||||
"on-tile-right-click": "", // (default: none)
|
||||
// add CSS classes/icons to windows based on their App ID/Title (see `niri msg windows`)
|
||||
// Go regular expression syntax is supported for app-id and title (see https://pkg.go.dev/regexp/syntax)
|
||||
// rules are checked in the order they are defined - first match wins and checking stops
|
||||
// set "continue" to true to also check and apply subsequent rules even if this rule matches
|
||||
// if multiple rules with icons are applied, the first one will be used
|
||||
// *icons are not drawn for floating windows by default*; set "icon-minimum-size" to enable (see above)
|
||||
"column-borders": 0,
|
||||
"floating-borders": 0,
|
||||
"on-tile-click": "FocusWindow",
|
||||
"on-tile-middle-click": "CloseWindow",
|
||||
"on-tile-right-click": "",
|
||||
"rules": [
|
||||
// .alacritty will be added to all windows with the App ID "Alacritty"
|
||||
// will be drawn in windows that match
|
||||
{ "app-id": "Alacritty", "class": "alacritty", "icon": "" },
|
||||
// .firefox will be added to all windows with the App ID "firefox"
|
||||
// subsequent rules are also checked and applied for firefox windows
|
||||
{ "app-id": "firefox", "class": "firefox", "continue": true },
|
||||
// .youtube-music will be added to all windows that have "YouTube Music" at the end of their title
|
||||
// will be drawn in windows that match
|
||||
{ "title": "YouTube Music$", "class": "youtube-music", "icon": "" }
|
||||
],
|
||||
|
||||
// ======= text mode options =======
|
||||
// customize the symbols used to draw the columns/windows
|
||||
"symbols": {
|
||||
"unfocused": "⋅",
|
||||
"focused": "⊙",
|
||||
"unfocused-floating": "∗",
|
||||
"focused-floating": "⊛",
|
||||
// text to display when there are no windows on the current workspace
|
||||
// if this is an empty string (default), the module will be hidden when there are no windows
|
||||
"empty": ""
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
// use niri IPC action names to trigger them (see https://yalter.github.io/niri/niri_ipc/enum.Action.html for available actions)
|
||||
// any action that has no fields is supported
|
||||
"on-scroll-up": "FocusColumnLeft",
|
||||
"on-scroll-down": "FocusColumnRight"
|
||||
// in graphical mode, don't configure click actions here—they're handled by the module above
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user