taffybar: refresh config and add helpers

- Remove stack config in favor of cabal/flake
- Add helper scripts for running/restarting and screenshots
- Update bar CSS/HS config
This commit is contained in:
2026-02-05 12:01:30 -08:00
committed by Kat Huang
parent 937b1c218c
commit 769fe29de0
9 changed files with 186 additions and 122 deletions

View File

@@ -0,0 +1,17 @@
set shell := ["bash", "-euo", "pipefail", "-c"]
# Run taffybar in the foreground (blocks the terminal).
run:
@scripts/taffybar-run
# Restart taffybar in the background using cabal run.
restart:
@scripts/taffybar-restart
# Capture the reserved top area (taffybar) on the focused monitor.
screenshot:
@scripts/taffybar-screenshot
# Capture the reserved top area on all monitors (one file per monitor).
screenshot-all:
@scripts/taffybar-screenshot-all

View File

@@ -0,0 +1,17 @@
@define-color accent #f1b2b2;
@define-color bar-background rgba(12, 15, 24, 0.55);
@define-color bar-gradient-start rgba(20, 26, 40, 0.68);
@define-color bar-gradient-end rgba(12, 18, 30, 0.68);
@define-color bar-border rgba(255, 255, 255, 0.06);
@define-color menu-background-color #1f202a;
@define-color menu-font-color #e7e4ee;
@define-color menu-highlight #3a3550;
@define-color font-color #e7e4ee;
@define-color font-muted #b8b1c6;
@define-color pill-background rgba(48, 52, 69, 0.92);
@define-color pill-border rgba(92, 95, 120, 0.85);
@define-color pill-highlight rgba(255, 255, 255, 0.10);
@define-color pill-shadow rgba(0, 0, 0, 0.28);
@define-color transparent rgba(0.0, 0.0, 0.0, 0.0);
@define-color white #ffffff;
@define-color black #000000;

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
pkill -u "$USER" -x taffybar || true
cd "$root"
setsid -f direnv exec . cabal run >/tmp/taffybar.log 2>&1
echo "Started taffybar in the background. Logs: /tmp/taffybar.log"

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$root"
exec direnv exec . cabal run

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
monitors="$(hyprctl monitors -j)"
height="$(jq -r '.[] | select(.focused) | .reserved[1]' <<<"$monitors")"
geo="$(jq -r '.[] | select(.focused) | "\(.x),\(.y) \(.width)x\(.reserved[1])"' <<<"$monitors")"
if [[ -z "$geo" || "$height" == "0" ]]; then
echo "No top reserved area found on focused monitor." >&2
echo "Is taffybar reserving space?" >&2
exit 1
fi
out="/tmp/taffybar-$(date +%Y%m%d-%H%M%S).png"
grim -g "$geo" "$out"
echo "$out"

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
ts="$(date +%Y%m%d-%H%M%S)"
found="0"
while IFS=$'\t' read -r name geo; do
found="1"
out="/tmp/taffybar-${name}-${ts}.png"
grim -g "$geo" "$out"
echo "$out"
done < <(hyprctl monitors -j | jq -r '.[] | select(.reserved[1] > 0) | "\(.name)\t\(.x),\(.y) \(.width)x\(.reserved[1])"')
if [[ "$found" == "0" ]]; then
echo "No monitors with a top reserved area found." >&2
exit 1
fi

View File

@@ -1,53 +0,0 @@
flags: {}
extra-package-dbs: []
packages:
- .
- location: ./taffybar
extra-dep: true
- location: ../xmonad/xmonad-contrib
extra-dep: true
- location: ../xmonad/xmonad
extra-dep: true
# - location: ../../../../Projects/status-notifier-item
# extra-dep: true
# - location: ../../../../Projects/gtk-sni-tray
# extra-dep: true
extra-deps:
- ConfigFile-1.1.4
- broadcast-chan-0.2.0.2
- dbus-hslogger-0.1.0.1
- gi-cairo-connector-0.0.1
- gi-cairo-render-0.0.1
- gi-dbusmenu-0.4.6
- gi-dbusmenugtk3-0.4.7
- gi-gdkx11-3.0.8
- gi-xlib-2.0.7
- gtk-sni-tray-0.1.6.0
- gtk-strut-0.1.3.0
- haskell-gi-0.22.6
- rate-limit-1.4.1
- status-notifier-item-0.3.0.3
- time-units-1.0.0
- xml-helpers-1.0.0
resolver: nightly-2019-06-19
allow-newer: true
nix:
packages:
- cairo
- gcc
- gnome2.pango
- gobjectIntrospection
- gtk3
- hicolor-icon-theme
- libdbusmenu-glib
- libdbusmenu-gtk3
- libxml2
- numix-icon-theme-circle
- pkgconfig
- x11
- xorg.libX11
- xorg.libXext
- xorg.libXinerama
- xorg.libXrandr
- xorg.libXrender
- zlib

View File

@@ -1,40 +1,46 @@
@define-color magenta #888ca6;
@define-color active-window-color @white;
@define-color urgent-window-color @taffy-blue;
@define-color font-color @white;
@define-color menu-background-color @white;
@define-color menu-font-color @black;
@define-color bar-background rgba(0, 0, 0, .6);
@define-color accent @red;
@define-color menu-highlight @magenta;
@define-color transparent rgba(0.0, 0.0, 0.0, 0.0);
@define-color white #FFFFFF;
@define-color black #000000;
/* Top level styling */ /* Top level styling */
.taffy-window {
background-color: @bar-background;
background-image: linear-gradient(to bottom, @bar-gradient-start, @bar-gradient-end);
border-bottom: 1px solid @bar-border;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
}
.taffy-window * { .taffy-window * {
font-family: "Noto Sans", sans-serif; font-family: "Iosevka Aile", "Noto Sans", sans-serif;
font-size: 10pt; font-size: 9pt;
font-weight: bold; font-weight: 600;
color: @font-color; color: @font-color;
background-color: @transparent; background-color: @transparent;
text-shadow: 1px 1px @font-shadow-color; text-shadow: none;
} }
.taffy-box { .taffy-box {
border-color: @white; border-width: 0px;
border-style: solid; background-color: @transparent;
background-color: @bar-background; padding: 0px;
padding: 2px; margin: 0px;
margin: 1px; border-radius: 0px;
border-radius: 4px; box-shadow: none;
}
.outer-pad {
background-color: @pill-background;
border: 1px solid @pill-border;
border-radius: 6px;
margin: 4px 6px;
box-shadow: 0 1px 0 @pill-highlight, 0 6px 14px @pill-shadow;
}
.inner-pad {
padding: 1px 8px;
border-radius: 5px;
} }
.contents { .contents {
padding: 2px; padding: 0px;
transition: background-color .5s; transition: background-color .2s;
opacity: 1; opacity: 1;
} }
@@ -47,20 +53,21 @@
.workspaces .contents { .workspaces .contents {
box-shadow: none; box-shadow: none;
border-radius: 4px; border-radius: 5px;
border-width: 1px; border-width: 0px;
border-style: solid; border-style: solid;
border-color: @transparent; border-color: @transparent;
padding: 0px 4px;
} }
.workspace-label { .workspace-label {
padding-right: 4px; padding-right: 6px;
padding-left: 2px; padding-left: 2px;
padding-top: 0px; padding-top: 0px;
font-size: 10pt; font-size: 9pt;
opacity: 1; opacity: 0.95;
font-weight: bold; font-weight: 600;
transition: color .5s; transition: color .2s;
} }
.contents .window-icon { .contents .window-icon {
@@ -72,7 +79,8 @@
} }
.active .contents { .active .contents {
background-color: rgba(0, 0, 0, 0.3); background-color: rgba(255, 255, 255, 0.10);
border-radius: 999px;
opacity: 1; opacity: 1;
} }
@@ -82,32 +90,35 @@
.active .overlay-box { .active .overlay-box {
padding: 0px; padding: 0px;
/* background-color: rgba(0, 0, 0, 1.0); */ border-color: @transparent;
border-color: @white; border-width: 0px;
border-width: 3px;
opacity: 1; opacity: 1;
} }
.visible .contents { .visible .contents {
background-color: rgba(255.0, 255.0, 255.0, 0.15); background-color: rgba(255, 255, 255, 0.06);
} }
.window-icon-container { .window-icon-container {
transition: opacity .5s, box-shadow .5s; transition: opacity .2s, box-shadow .2s;
opacity: 1; opacity: 1;
border-radius: 5px; border-radius: 5px;
transition: background-color 1s; transition: background-color .2s;
background-color: rgba(255, 255, 255, 0.04);
padding: 1px 4px;
border: 1px solid rgba(255, 255, 255, 0.08);
} }
/* This gives space for the box-shadow (they look like underlines) that follow. /* This gives space for the box-shadow (they look like underlines) that follow.
This will actually affect all widgets, (not just the workspace icons), but This will actually affect all widgets, (not just the workspace icons), but
that is what we want since we want the icons to look the same. */ that is what we want since we want the icons to look the same. */
.auto-size-image, .sni-tray { .auto-size-image, .sni-tray {
padding: 1px; padding: 1px 4px;
} }
.window-icon-container.active { .window-icon-container.active {
background-color: rgba(255.0, 255.0, 255.0, 0.4); background-color: rgba(255, 255, 255, 0.13);
border-color: rgba(255, 255, 255, 0.16);
} }
.window-icon-container.urgent { .window-icon-container.urgent {
@@ -132,7 +143,8 @@ button {
} }
button:checked, button:hover .Contents:hover { button:checked, button:hover .Contents:hover {
box-shadow: inset 0 -3px @accent; box-shadow: inset 0 -2px @accent;
background-color: rgba(255, 255, 255, 0.06);
} }
/* Menu styling */ /* Menu styling */
@@ -155,3 +167,13 @@ button:checked, button:hover .Contents:hover {
.taffy-window menuitem:hover > label, menuitem:hover > label { .taffy-window menuitem:hover > label, menuitem:hover > label {
color: @white; color: @white;
} }
.clock label,
.mpris label,
.battery-text label {
letter-spacing: 0.2px;
}
.mpris label {
color: @font-muted;
}

View File

@@ -167,6 +167,13 @@ iconNameVariants raw =
in [dotted, dashed, dashedDotted, underscored, underscoredDotted, name] in [dotted, dashed, dashedDotted, underscored, underscoredDotted, name]
in nub $ concatMap variantsFor baseNames in nub $ concatMap variantsFor baseNames
-- Hyprland "special" workspaces (e.g. "special:slack") are scratchpad-like and
-- usually not something we want visible in the workspace widget.
isSpecialHyprWorkspace :: Hyprland.HyprlandWorkspace -> Bool
isSpecialHyprWorkspace ws =
let name = Data.Text.toLower $ Data.Text.pack $ Hyprland.workspaceName ws
in Data.Text.isPrefixOf "special" name || Hyprland.workspaceIdx ws < 0
hyprlandIconCandidates :: Hyprland.HyprlandWindow -> [Data.Text.Text] hyprlandIconCandidates :: Hyprland.HyprlandWindow -> [Data.Text.Text]
hyprlandIconCandidates windowData = hyprlandIconCandidates windowData =
let baseNames = map Data.Text.pack $ catMaybes let baseNames = map Data.Text.pack $ catMaybes
@@ -224,11 +231,11 @@ hyprlandFallbackIcon size _ =
fallbackIconPixbuf size fallbackIconPixbuf size
cssFilesByHostname = cssFilesByHostname =
[ ("uber-loaner", ["uber-loaner.css"]) [ ("uber-loaner", ["palette.css", "uber-loaner.css"])
, ("imalison-home", ["taffybar.css"]) , ("imalison-home", ["palette.css", "taffybar.css"])
, ("ivanm-dfinity-razer", ["taffybar.css"]) , ("ivanm-dfinity-razer", ["palette.css", "taffybar.css"])
, ("ryzen-shine", ["taffybar.css"]) , ("ryzen-shine", ["palette.css", "taffybar.css"])
, ("stevie-nixos", ["taffybar.css"]) , ("stevie-nixos", ["palette.css", "taffybar.css"])
] ]
main = do main = do
@@ -236,7 +243,7 @@ main = do
hostName <- getHostName hostName <- getHostName
backend <- detectBackend backend <- detectBackend
let relativeFiles = fromMaybe ["taffybar.css"] $ lookup hostName cssFilesByHostname let relativeFiles = fromMaybe ["palette.css", "taffybar.css"] $ lookup hostName cssFilesByHostname
cssFiles <- mapM (getUserConfigFile "taffybar") relativeFiles cssFiles <- mapM (getUserConfigFile "taffybar") relativeFiles
let myCPU = deocrateWithSetClassAndBoxes "cpu" $ let myCPU = deocrateWithSetClassAndBoxes "cpu" $
@@ -252,23 +259,27 @@ main = do
myWorkspaces = myWorkspaces =
flip widgetSetClassGI "workspaces" =<< flip widgetSetClassGI "workspaces" =<<
X11Workspaces.workspacesNew X11Workspaces.defaultWorkspacesConfig X11Workspaces.workspacesNew X11Workspaces.defaultWorkspacesConfig
{ minIcons = 1 { X11Workspaces.minIcons = 1
, getWindowIconPixbuf = , X11Workspaces.getWindowIconPixbuf =
X11Workspaces.scaledWindowIconPixbufGetter $ X11Workspaces.scaledWindowIconPixbufGetter $
X11Workspaces.getWindowIconPixbufFromChrome <|||> X11Workspaces.getWindowIconPixbufFromChrome <|||>
X11Workspaces.unscaledDefaultGetWindowIconPixbuf <|||> X11Workspaces.unscaledDefaultGetWindowIconPixbuf <|||>
(\size _ -> fallbackIconPixbuf size) (\size _ -> fallbackIconPixbuf size)
, widgetGap = 0 , X11Workspaces.widgetGap = 0
, showWorkspaceFn = X11Workspaces.hideEmpty , X11Workspaces.showWorkspaceFn = X11Workspaces.hideEmpty
, updateRateLimitMicroseconds = 100000 , X11Workspaces.updateRateLimitMicroseconds = 100000
, labelSetter = workspaceNamesLabelSetter , X11Workspaces.labelSetter = workspaceNamesLabelSetter
, widgetBuilder = X11Workspaces.buildLabelOverlayController , X11Workspaces.widgetBuilder = X11Workspaces.buildLabelOverlayController
} }
myHyprWorkspaces = myHyprWorkspaces =
flip widgetSetClassGI "workspaces" =<< flip widgetSetClassGI "workspaces" =<<
Hyprland.hyprlandWorkspacesNew Hyprland.defaultHyprlandWorkspacesConfig Hyprland.hyprlandWorkspacesNew Hyprland.defaultHyprlandWorkspacesConfig
{ Hyprland.widgetGap = 0 { Hyprland.widgetGap = 0
, Hyprland.minIcons = 1 , Hyprland.minIcons = 1
-- Don't show Hyprland "special:*" workspaces.
, Hyprland.showWorkspaceFn =
(\ws -> Hyprland.workspaceState ws /= X11Workspaces.Empty &&
not (isSpecialHyprWorkspace ws))
, Hyprland.getWindowIconPixbuf = , Hyprland.getWindowIconPixbuf =
hyprlandManualIconGetter <|||> hyprlandManualIconGetter <|||>
Hyprland.defaultHyprlandGetWindowIconPixbuf <|||> Hyprland.defaultHyprlandGetWindowIconPixbuf <|||>
@@ -280,10 +291,11 @@ main = do
{ clockUpdateStrategy = RoundedTargetInterval 60 0.0 { clockUpdateStrategy = RoundedTargetInterval 60 0.0
, clockFormatString = "%a %b %_d, 🕑%I:%M %p" , clockFormatString = "%a %b %_d, 🕑%I:%M %p"
} }
myTray = deocrateWithSetClassAndBoxes "tray" $ -- Disabled for now: StatusNotifierWatcher errors under Hyprland.
sniTrayNewFromParams defaultTrayParams { trayLeftClickAction = PopupMenu -- myTray = deocrateWithSetClassAndBoxes "tray" $
, trayRightClickAction = Activate -- sniTrayNewFromParams defaultTrayParams { trayLeftClickAction = PopupMenu
} -- , trayRightClickAction = Activate
-- }
myMpris = mpris2NewWithConfig myMpris = mpris2NewWithConfig
MPRIS2Config { mprisWidgetWrapper = deocrateWithSetClassAndBoxes "mpris" . return MPRIS2Config { mprisWidgetWrapper = deocrateWithSetClassAndBoxes "mpris" . return
, updatePlayerWidget = , updatePlayerWidget =
@@ -296,8 +308,7 @@ main = do
deocrateWithSetClassAndBoxes "battery-text" $ textBatteryNew "$percentage$%" deocrateWithSetClassAndBoxes "battery-text" $ textBatteryNew "$percentage$%"
batteryWidgets = [myBatteryIcon, myBatteryText] batteryWidgets = [myBatteryIcon, myBatteryText]
baseEndWidgets = baseEndWidgets =
[ myTray [ myMpris
, myMpris
] ]
fullEndWidgets = baseEndWidgets ++ [ myCPU, myMem, myNet, myMpris ] fullEndWidgets = baseEndWidgets ++ [ myCPU, myMem, myNet, myMpris ]
laptopEndWidgets = batteryWidgets ++ baseEndWidgets laptopEndWidgets = batteryWidgets ++ baseEndWidgets
@@ -307,8 +318,8 @@ main = do
, endWidgets = baseEndWidgets , endWidgets = baseEndWidgets
, barPosition = Top , barPosition = Top
, widgetSpacing = 0 , widgetSpacing = 0
, barPadding = 0 , barPadding = 4
, barHeight = ScreenRatio $ 1/27 , barHeight = ScreenRatio $ 1/36
, cssPaths = cssFiles , cssPaths = cssFiles
, centerWidgets = [ myClock ] , centerWidgets = [ myClock ]
} }
@@ -318,8 +329,8 @@ main = do
, endWidgets = baseEndWidgets , endWidgets = baseEndWidgets
, barPosition = Top , barPosition = Top
, widgetSpacing = 0 , widgetSpacing = 0
, barPadding = 0 , barPadding = 4
, barHeight = ScreenRatio $ 1/27 , barHeight = ScreenRatio $ 1/36
, cssPaths = cssFiles , cssPaths = cssFiles
, centerWidgets = [ myClock ] , centerWidgets = [ myClock ]
} }
@@ -342,7 +353,7 @@ main = do
, baseConfigX11 { endWidgets = laptopEndWidgets } , baseConfigX11 { endWidgets = laptopEndWidgets }
) )
, ( "nixquick" , ( "nixquick"
, baseConfigX11 { endWidgets = [ myTray , myMpris ] } , baseConfigX11 { endWidgets = [ myMpris ] }
) )
] ]
waylandOverrides = waylandOverrides =
@@ -362,7 +373,7 @@ main = do
, baseConfigWayland { endWidgets = laptopEndWidgets } , baseConfigWayland { endWidgets = laptopEndWidgets }
) )
, ( "nixquick" , ( "nixquick"
, baseConfigWayland { endWidgets = [ myTray , myMpris ] } , baseConfigWayland { endWidgets = [ myMpris ] }
) )
] ]
selectedConfig = selectedConfig =
@@ -377,7 +388,6 @@ main = do
-- , startWidgets = [] -- , startWidgets = []
} }
startTaffybar $ startTaffybar $
appendHook (void $ getTrayHost False) $
withLogServer $ withLogServer $
withToggleServer $ withToggleServer $
toTaffybarConfig simpleTaffyConfig toTaffybarConfig simpleTaffyConfig