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"
(pkgs.lib.cleanSource (dbus-menu.outPath or dbus-menu))
{ 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 =
pkgs.haskell.lib.overrideCabal

View File

@@ -1,4 +1,10 @@
# Taffybar log levels
# Format: LoggerName: LEVEL
# 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

View File

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