taffybar: bottom bar + fix Hyprland workspace widget config

This commit is contained in:
2026-02-12 22:57:15 -08:00
committed by Kat Huang
parent f1a26f6be9
commit 0dcf4a7cd6
4 changed files with 161 additions and 134 deletions

View File

@@ -85,7 +85,12 @@
(hself.callCabal2nix "dbus-menu" (hself.callCabal2nix "dbus-menu"
(pkgs.lib.cleanSource (dbus-menu.outPath or dbus-menu)) (pkgs.lib.cleanSource (dbus-menu.outPath or dbus-menu))
{ inherit (pkgs) gtk3; }) { inherit (pkgs) gtk3; })
(_: { doCheck = false; doHaddock = false; }); (_: {
doCheck = false;
doHaddock = false;
# Needed for GHC 9.12 (template-haskell bound too strict upstream).
jailbreak = true;
});
status-notifier-item = status-notifier-item =
pkgs.haskell.lib.overrideCabal pkgs.haskell.lib.overrideCabal

View File

@@ -1,4 +1,10 @@
# Taffybar log levels # Taffybar log levels
# Format: LoggerName: LEVEL # Format: LoggerName: LEVEL
# Levels: DEBUG INFO NOTICE WARNING ERROR CRITICAL ALERT EMERGENCY # Levels: DEBUG INFO NOTICE WARNING ERROR CRITICAL ALERT EMERGENCY
System.Taffybar: DEBUG
StatusNotifier: DEBUG
System.Taffybar.Context: DEBUG
System.Taffybar.Information.Hyprland: DEBUG
System.Taffybar.Widget.Workspaces: DEBUG
Graphics.UI.GIGtkStrut: DEBUG
System.Taffybar.DBus.Toggle: DEBUG System.Taffybar.DBus.Toggle: DEBUG

View File

@@ -9,13 +9,15 @@ import Data.Int (Int32)
import Data.List (nub) import Data.List (nub)
import qualified Data.Map as M import qualified Data.Map as M
import Data.Maybe (catMaybes, fromMaybe, mapMaybe) import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
import Data.Ratio ((%))
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import qualified GI.GdkPixbuf.Objects.Pixbuf as Gdk import qualified GI.GdkPixbuf.Objects.Pixbuf as Gdk
import qualified GI.Gtk as Gtk import qualified GI.Gtk as Gtk
import Network.HostName (getHostName) import Network.HostName (getHostName)
import qualified StatusNotifier.Tray as SNITray (MenuBackend (HaskellDBusMenu), defaultTrayParams, trayMenuBackend, trayOverlayScale)
import System.Environment.XDG.BaseDir (getUserConfigFile) import System.Environment.XDG.BaseDir (getUserConfigFile)
import System.Log.Logger (Priority(WARNING), rootLoggerName, setLevel, updateGlobalLogger) import System.Log.Logger (Priority (WARNING), rootLoggerName, setLevel, updateGlobalLogger)
import System.Taffybar (startTaffybar) import System.Taffybar (startTaffybar)
import System.Taffybar.Context (Backend (BackendWayland, BackendX11), TaffyIO, detectBackend) import System.Taffybar.Context (Backend (BackendWayland, BackendX11), TaffyIO, detectBackend)
import System.Taffybar.DBus import System.Taffybar.DBus
@@ -24,32 +26,31 @@ import System.Taffybar.Hooks (withLogLevels)
import System.Taffybar.Information.EWMHDesktopInfo (WorkspaceId (..)) import System.Taffybar.Information.EWMHDesktopInfo (WorkspaceId (..))
import System.Taffybar.Information.X11DesktopInfo import System.Taffybar.Information.X11DesktopInfo
import System.Taffybar.SimpleConfig import System.Taffybar.SimpleConfig
import System.Taffybar.Util (getPixbufFromFilePath, (<|||>), maybeTCombine) import System.Taffybar.Util (getPixbufFromFilePath, maybeTCombine, (<|||>))
import System.Taffybar.Widget import System.Taffybar.Widget
import qualified System.Taffybar.Widget.HyprlandWorkspaces as Hyprland
import qualified System.Taffybar.Widget.NetworkManager as NetworkManager
import System.Taffybar.Widget.SNIMenu (withNmAppletMenu)
import qualified System.Taffybar.Widget.ASUS as ASUS import qualified System.Taffybar.Widget.ASUS as ASUS
import qualified System.Taffybar.Widget.NetworkManager as NetworkManager
import qualified System.Taffybar.Widget.PulseAudio as PulseAudio import qualified System.Taffybar.Widget.PulseAudio as PulseAudio
import qualified System.Taffybar.Widget.ScreenLock as ScreenLock import System.Taffybar.Widget.SNIMenu (withNmAppletMenu)
import qualified System.Taffybar.Widget.Wlsunset as Wlsunset
import Data.Ratio ((%))
import System.Taffybar.Widget.SNITray import System.Taffybar.Widget.SNITray
( sniTrayNewFromParams ( sniTrayNewFromParams,
, sniTrayThatStartsWatcherEvenThoughThisIsABadWayToDoIt sniTrayThatStartsWatcherEvenThoughThisIsABadWayToDoIt,
) )
import qualified StatusNotifier.Tray as SNITray (MenuBackend (HaskellDBusMenu), defaultTrayParams, trayMenuBackend, trayOverlayScale) import qualified System.Taffybar.Widget.ScreenLock as ScreenLock
import System.Taffybar.Widget.Util (buildContentsBox, buildIconLabelBox, loadPixbufByName, widgetSetClassGI) import System.Taffybar.Widget.Util (buildContentsBox, buildIconLabelBox, loadPixbufByName, widgetSetClassGI)
import qualified System.Taffybar.Widget.Workspaces as X11Workspaces import qualified System.Taffybar.Widget.Wlsunset as Wlsunset
import qualified System.Taffybar.Widget.Workspaces.Config as WorkspaceWidgetConfig
import qualified System.Taffybar.Widget.Workspaces.EWMH as X11Workspaces
import qualified System.Taffybar.Widget.Workspaces.Hyprland as Hyprland
import System.Taffybar.WindowIcon (pixBufFromColor) import System.Taffybar.WindowIcon (pixBufFromColor)
-- | Wrap the widget in a "TaffyBox" (via 'buildContentsBox') and add a CSS class. -- | Wrap the widget in a "TaffyBox" (via 'buildContentsBox') and add a CSS class.
decorateWithClassAndBox :: MonadIO m => Text -> Gtk.Widget -> m Gtk.Widget decorateWithClassAndBox :: (MonadIO m) => Text -> Gtk.Widget -> m Gtk.Widget
decorateWithClassAndBox klass widget = do decorateWithClassAndBox klass widget = do
boxed <- buildContentsBox widget boxed <- buildContentsBox widget
widgetSetClassGI boxed klass widgetSetClassGI boxed klass
decorateWithClassAndBoxM :: MonadIO m => Text -> m Gtk.Widget -> m Gtk.Widget decorateWithClassAndBoxM :: (MonadIO m) => Text -> m Gtk.Widget -> m Gtk.Widget
decorateWithClassAndBoxM klass builder = decorateWithClassAndBoxM klass builder =
builder >>= decorateWithClassAndBox klass builder >>= decorateWithClassAndBox klass
@@ -63,8 +64,8 @@ x11FullWorkspaceNames =
x11WorkspaceLabelSetter :: X11Workspaces.Workspace -> X11Workspaces.WorkspacesIO String x11WorkspaceLabelSetter :: X11Workspaces.Workspace -> X11Workspaces.WorkspacesIO String
x11WorkspaceLabelSetter workspace = x11WorkspaceLabelSetter workspace =
remapNSP . fromMaybe "" . lookup (X11Workspaces.workspaceIdx workspace) <$> remapNSP . fromMaybe "" . lookup (X11Workspaces.workspaceIdx workspace)
liftX11Def [] x11FullWorkspaceNames <$> liftX11Def [] x11FullWorkspaceNames
where where
remapNSP "NSP" = "S" remapNSP "NSP" = "S"
remapNSP n = n remapNSP n = n
@@ -80,7 +81,7 @@ iconRemap =
iconRemapMap :: M.Map Text [Text] iconRemapMap :: M.Map Text [Text]
iconRemapMap = iconRemapMap =
M.fromList [ (T.toLower k, v) | (k, v) <- iconRemap ] M.fromList [(T.toLower k, v) | (k, v) <- iconRemap]
lookupIconRemap :: Text -> [Text] lookupIconRemap :: Text -> [Text]
lookupIconRemap name = fromMaybe [] $ M.lookup (T.toLower name) iconRemapMap lookupIconRemap name = fromMaybe [] $ M.lookup (T.toLower name) iconRemapMap
@@ -121,9 +122,11 @@ isSpecialHyprWorkspace ws =
hyprlandIconCandidates :: Hyprland.HyprlandWindow -> [Text] hyprlandIconCandidates :: Hyprland.HyprlandWindow -> [Text]
hyprlandIconCandidates windowData = hyprlandIconCandidates windowData =
let baseNames = map T.pack $ catMaybes let baseNames =
[ Hyprland.windowClass windowData map T.pack $
, Hyprland.windowInitialClass windowData catMaybes
[ Hyprland.windowClass windowData,
Hyprland.windowInitialClass windowData
] ]
remapped = concatMap lookupIconRemap baseNames remapped = concatMap lookupIconRemap baseNames
remappedExpanded = concatMap iconNameVariants remapped remappedExpanded = concatMap iconNameVariants remapped
@@ -132,8 +135,8 @@ hyprlandIconCandidates windowData =
isPathCandidate :: Text -> Bool isPathCandidate :: Text -> Bool
isPathCandidate name = isPathCandidate name =
T.isInfixOf "/" name || T.isInfixOf "/" name
any (`T.isSuffixOf` name) [".png", ".svg", ".xpm"] || any (`T.isSuffixOf` name) [".png", ".svg", ".xpm"]
hyprlandIconFromCandidate :: Int32 -> Text -> TaffyIO (Maybe Gdk.Pixbuf) hyprlandIconFromCandidate :: Int32 -> Text -> TaffyIO (Maybe Gdk.Pixbuf)
hyprlandIconFromCandidate size name hyprlandIconFromCandidate size name
@@ -153,14 +156,14 @@ hyprlandManualIconGetter =
fallbackIconPixbuf :: Int32 -> TaffyIO (Maybe Gdk.Pixbuf) fallbackIconPixbuf :: Int32 -> TaffyIO (Maybe Gdk.Pixbuf)
fallbackIconPixbuf size = do fallbackIconPixbuf size = do
let fallbackNames = let fallbackNames =
[ "application-x-executable" [ "application-x-executable",
, "application" "application",
, "image-missing" "image-missing",
, "gtk-missing-image" "gtk-missing-image",
, "dialog-question" "dialog-question",
, "utilities-terminal" "utilities-terminal",
, "system-run" "system-run",
, "window" "window"
] ]
tryNames = tryNames =
foldl foldl
@@ -183,17 +186,17 @@ defaultCssFiles = ["palette.css", "taffybar.css"]
cssFilesByHostname :: [(String, [FilePath])] cssFilesByHostname :: [(String, [FilePath])]
cssFilesByHostname = cssFilesByHostname =
[ ("imalison-home", ["palette.css", "taffybar.css"]) [ ("imalison-home", ["palette.css", "taffybar.css"]),
, ("ryzen-shine", ["palette.css", "taffybar.css"]) ("ryzen-shine", ["palette.css", "taffybar.css"]),
, ("stevie-nixos", ["palette.css", "taffybar.css"]) ("stevie-nixos", ["palette.css", "taffybar.css"])
] ]
laptopHosts :: [String] laptopHosts :: [String]
laptopHosts = laptopHosts =
[ "adell" [ "adell",
, "stevie-nixos" "stevie-nixos",
, "strixi-minaj" "strixi-minaj",
, "jay-lenovo" "jay-lenovo"
] ]
cssFilesForHost :: String -> [FilePath] cssFilesForHost :: String -> [FilePath]
@@ -221,20 +224,25 @@ windowsWidget =
x11WorkspacesWidget :: TaffyIO Gtk.Widget x11WorkspacesWidget :: TaffyIO Gtk.Widget
x11WorkspacesWidget = x11WorkspacesWidget =
flip widgetSetClassGI "workspaces" =<< flip widgetSetClassGI "workspaces"
X11Workspaces.workspacesNew =<< X11Workspaces.workspacesNew cfg
X11Workspaces.defaultWorkspacesConfig where
{ X11Workspaces.minIcons = 1 common =
, X11Workspaces.getWindowIconPixbuf = (X11Workspaces.workspacesCommonConfig X11Workspaces.defaultWorkspacesConfig)
{ WorkspaceWidgetConfig.minIcons = 1,
WorkspaceWidgetConfig.getWindowIconPixbuf =
X11Workspaces.scaledWindowIconPixbufGetter $ X11Workspaces.scaledWindowIconPixbufGetter $
X11Workspaces.getWindowIconPixbufFromChrome <|||> X11Workspaces.getWindowIconPixbufFromChrome
X11Workspaces.unscaledDefaultGetWindowIconPixbuf <|||> <|||> X11Workspaces.unscaledDefaultGetWindowIconPixbuf
(\size _ -> fallbackIconPixbuf size) <|||> (\size _ -> fallbackIconPixbuf size),
, X11Workspaces.widgetGap = 0 WorkspaceWidgetConfig.widgetGap = 0,
, X11Workspaces.showWorkspaceFn = X11Workspaces.hideEmpty WorkspaceWidgetConfig.showWorkspaceFn = X11Workspaces.hideEmpty,
, X11Workspaces.updateRateLimitMicroseconds = 100000 WorkspaceWidgetConfig.labelSetter = x11WorkspaceLabelSetter,
, X11Workspaces.labelSetter = x11WorkspaceLabelSetter WorkspaceWidgetConfig.widgetBuilder = X11Workspaces.buildLabelOverlayController
, X11Workspaces.widgetBuilder = X11Workspaces.buildLabelOverlayController }
cfg =
(X11Workspaces.applyCommonWorkspacesConfig common X11Workspaces.defaultWorkspacesConfig)
{ X11Workspaces.updateRateLimitMicroseconds = 100000
} }
-- | Like 'buildWorkspaceIconLabelOverlay' but lets you choose the corner. -- | Like 'buildWorkspaceIconLabelOverlay' but lets you choose the corner.
@@ -257,26 +265,32 @@ buildAlignedOverlay halign valign iconsWidget labelWidget = liftIO $ do
hyprlandWorkspacesWidget :: TaffyIO Gtk.Widget hyprlandWorkspacesWidget :: TaffyIO Gtk.Widget
hyprlandWorkspacesWidget = hyprlandWorkspacesWidget =
flip widgetSetClassGI "workspaces" =<< flip widgetSetClassGI "workspaces"
Hyprland.hyprlandWorkspacesNew cfg =<< Hyprland.hyprlandWorkspacesNew cfg
where where
cfg = Hyprland.defaultHyprlandWorkspacesConfig base = Hyprland.defaultHyprlandWorkspacesConfig
{ Hyprland.widgetGap = 0 cfg =
, Hyprland.minIcons = 1 Hyprland.applyCommonHyprlandWorkspacesConfig common base
, Hyprland.widgetBuilder = common =
Hyprland.hyprlandBuildButtonController cfg (Hyprland.hyprlandWorkspacesCommonConfig base)
(Hyprland.hyprlandBuildCustomOverlayController { WorkspaceWidgetConfig.widgetGap = 0,
WorkspaceWidgetConfig.minIcons = 1,
WorkspaceWidgetConfig.widgetBuilder =
Hyprland.hyprlandBuildButtonController
cfg
( Hyprland.hyprlandBuildCustomOverlayController
(buildAlignedOverlay Gtk.AlignStart Gtk.AlignEnd) (buildAlignedOverlay Gtk.AlignStart Gtk.AlignEnd)
cfg) cfg
),
-- Don't show Hyprland "special:*" workspaces. -- Don't show Hyprland "special:*" workspaces.
, Hyprland.showWorkspaceFn = WorkspaceWidgetConfig.showWorkspaceFn =
\ws -> \ws ->
Hyprland.workspaceState ws /= X11Workspaces.Empty && Hyprland.workspaceState ws /= X11Workspaces.Empty
not (isSpecialHyprWorkspace ws) && not (isSpecialHyprWorkspace ws),
, Hyprland.getWindowIconPixbuf = WorkspaceWidgetConfig.getWindowIconPixbuf =
hyprlandManualIconGetter <|||> hyprlandManualIconGetter
Hyprland.defaultHyprlandGetWindowIconPixbuf <|||> <|||> Hyprland.defaultHyprlandGetWindowIconPixbuf
hyprlandFallbackIcon <|||> hyprlandFallbackIcon
} }
clockWidget :: TaffyIO Gtk.Widget clockWidget :: TaffyIO Gtk.Widget
@@ -285,8 +299,8 @@ clockWidget =
"clock" "clock"
( textClockNewWith ( textClockNewWith
defaultClockConfig defaultClockConfig
{ clockUpdateStrategy = RoundedTargetInterval 60 0.0 { clockUpdateStrategy = RoundedTargetInterval 60 0.0,
, clockFormatString = "%a %b %_d, 🕑%I:%M %p" clockFormatString = "%a %b %_d, 🕑%I:%M %p"
} }
) )
@@ -294,8 +308,8 @@ mprisWidget :: TaffyIO Gtk.Widget
mprisWidget = mprisWidget =
mpris2NewWithConfig mpris2NewWithConfig
MPRIS2Config MPRIS2Config
{ mprisWidgetWrapper = decorateWithClassAndBox "mpris" { mprisWidgetWrapper = decorateWithClassAndBox "mpris",
, updatePlayerWidget = updatePlayerWidget =
simplePlayerWidget simplePlayerWidget
defaultPlayerConfig defaultPlayerConfig
{ setNowPlayingLabel = playingText 20 20 { setNowPlayingLabel = playingText 20 20
@@ -314,9 +328,9 @@ backlightWidget =
"backlight" "backlight"
( backlightLabelNewChanWith ( backlightLabelNewChanWith
defaultBacklightWidgetConfig defaultBacklightWidgetConfig
{ backlightFormat = "☀ $percent$%" { backlightFormat = "☀ $percent$%",
, backlightUnknownFormat = "☀ n/a" backlightUnknownFormat = "☀ n/a",
, backlightTooltipFormat = backlightTooltipFormat =
Just "Device: $device$\nBrightness: $brightness$/$max$ ($percent$%)" Just "Device: $device$\nBrightness: $brightness$/$max$ ($percent$%)"
} }
) )
@@ -334,20 +348,22 @@ screenLockWidget =
decorateWithClassAndBoxM "screen-lock" $ decorateWithClassAndBoxM "screen-lock" $
ScreenLock.screenLockNewWithConfig ScreenLock.screenLockNewWithConfig
ScreenLock.defaultScreenLockConfig ScreenLock.defaultScreenLockConfig
{ ScreenLock.screenLockIcon = T.pack "\xF023" <> " Lock" } { ScreenLock.screenLockIcon = T.pack "\xF023" <> " Lock"
}
wlsunsetWidget :: TaffyIO Gtk.Widget wlsunsetWidget :: TaffyIO Gtk.Widget
wlsunsetWidget = wlsunsetWidget =
decorateWithClassAndBoxM "wlsunset" $ decorateWithClassAndBoxM "wlsunset" $
Wlsunset.wlsunsetNewWithConfig Wlsunset.wlsunsetNewWithConfig
Wlsunset.defaultWlsunsetWidgetConfig Wlsunset.defaultWlsunsetWidgetConfig
{ Wlsunset.wlsunsetWidgetIcon = T.pack "\xF0599" <> " Sun" } { Wlsunset.wlsunsetWidgetIcon = T.pack "\xF0599" <> " Sun"
}
sniTrayWidget :: TaffyIO Gtk.Widget sniTrayWidget :: TaffyIO Gtk.Widget
sniTrayWidget = sniTrayWidget =
decorateWithClassAndBoxM decorateWithClassAndBoxM
"sni-tray" "sni-tray"
(sniTrayNewFromParams (SNITray.defaultTrayParams { SNITray.trayMenuBackend = SNITray.HaskellDBusMenu, SNITray.trayOverlayScale = 1 % 3 })) (sniTrayNewFromParams (SNITray.defaultTrayParams {SNITray.trayMenuBackend = SNITray.HaskellDBusMenu, SNITray.trayOverlayScale = 1 % 3}))
-- ** Layout -- ** Layout
@@ -362,16 +378,16 @@ endWidgetsForHost :: String -> Backend -> [TaffyIO Gtk.Widget]
endWidgetsForHost hostName backend = endWidgetsForHost hostName backend =
let baseEndWidgets = [audioWidget, diskUsageWidget, networkWidget, screenLockWidget, wlsunsetWidget, mprisWidget, sniTrayWidget] let baseEndWidgets = [audioWidget, diskUsageWidget, networkWidget, screenLockWidget, wlsunsetWidget, mprisWidget, sniTrayWidget]
laptopEndWidgets = laptopEndWidgets =
[ batteryWidget [ batteryWidget,
, sniTrayWidget sniTrayWidget,
, asusWidget asusWidget,
, audioWidget audioWidget,
, diskUsageWidget diskUsageWidget,
, backlightWidget backlightWidget,
, networkWidget networkWidget,
, screenLockWidget screenLockWidget,
, wlsunsetWidget wlsunsetWidget,
, mprisWidget mprisWidget
] ]
in if hostName `elem` laptopHosts in if hostName `elem` laptopHosts
then laptopEndWidgets then laptopEndWidgets
@@ -380,14 +396,14 @@ endWidgetsForHost hostName backend =
mkSimpleTaffyConfig :: String -> Backend -> [FilePath] -> SimpleTaffyConfig mkSimpleTaffyConfig :: String -> Backend -> [FilePath] -> SimpleTaffyConfig
mkSimpleTaffyConfig hostName backend cssFiles = mkSimpleTaffyConfig hostName backend cssFiles =
defaultSimpleTaffyConfig defaultSimpleTaffyConfig
{ startWidgets = startWidgetsForBackend backend { startWidgets = startWidgetsForBackend backend,
, endWidgets = endWidgetsForHost hostName backend endWidgets = endWidgetsForHost hostName backend,
, barPosition = Top barPosition = Bottom,
, widgetSpacing = 0 widgetSpacing = 0,
, barPadding = 4 barPadding = 4,
, barHeight = ScreenRatio $ 1 / 33 barHeight = ScreenRatio $ 1 / 33,
, cssPaths = cssFiles cssPaths = cssFiles,
, centerWidgets = [clockWidget] centerWidgets = [clockWidget]
} }
-- ** Entry Point -- ** Entry Point