refactor(taffybar): unify workspace widgets across backends

This commit is contained in:
2026-02-20 02:42:55 -08:00
committed by Kat Huang
parent 0517dd74f3
commit 384d2dfd6a
2 changed files with 57 additions and 104 deletions

View File

@@ -7,12 +7,13 @@ module Main (main) where
import Control.Concurrent (threadDelay) import Control.Concurrent (threadDelay)
import Control.Monad (when) import Control.Monad (when)
import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Trans.Reader (asks)
import Data.Char (toLower) import Data.Char (toLower)
import Data.IORef (IORef, modifyIORef', newIORef, readIORef) import Data.IORef (IORef, modifyIORef', newIORef, readIORef)
import Data.Int (Int32) import Data.Int (Int32)
import Data.List (nub, sortOn, stripPrefix) import Data.List (nub, sortOn, stripPrefix)
import qualified Data.Map as M import qualified Data.Map as M
import Data.Maybe (catMaybes, fromMaybe, listToMaybe, mapMaybe, maybeToList) import Data.Maybe (fromMaybe, listToMaybe, mapMaybe, maybeToList)
import Data.Ratio ((%)) import Data.Ratio ((%))
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
@@ -28,11 +29,18 @@ import System.Environment.XDG.BaseDir (getUserConfigFile)
import System.FilePath (takeDirectory) import System.FilePath (takeDirectory)
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,
backend,
detectBackend,
runX11Def,
)
import System.Taffybar.DBus import System.Taffybar.DBus
import System.Taffybar.DBus.Toggle import System.Taffybar.DBus.Toggle
import System.Taffybar.Hooks (withLogLevels) import System.Taffybar.Hooks (withLogLevels)
import System.Taffybar.Information.EWMHDesktopInfo (WorkspaceId (..)) import System.Taffybar.Information.EWMHDesktopInfo (WorkspaceId (..))
import qualified System.Taffybar.Information.Workspaces.Model as WorkspaceModel
import System.Taffybar.Information.Memory (MemoryInfo (..), parseMeminfo) import System.Taffybar.Information.Memory (MemoryInfo (..), parseMeminfo)
import System.Taffybar.Information.X11DesktopInfo import System.Taffybar.Information.X11DesktopInfo
import System.Taffybar.SimpleConfig import System.Taffybar.SimpleConfig
@@ -51,9 +59,8 @@ import System.Taffybar.Widget.SNITray
import qualified System.Taffybar.Widget.ScreenLock as ScreenLock import qualified System.Taffybar.Widget.ScreenLock as ScreenLock
import System.Taffybar.Widget.Util (backgroundLoop, buildContentsBox, buildIconLabelBox, loadPixbufByName, widgetSetClassGI) import System.Taffybar.Widget.Util (backgroundLoop, buildContentsBox, buildIconLabelBox, loadPixbufByName, widgetSetClassGI)
import qualified System.Taffybar.Widget.Wlsunset as Wlsunset 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 qualified System.Taffybar.Widget.Workspaces.Hyprland as Hyprland
import qualified System.Taffybar.Widget.Workspaces as Workspaces
import System.Taffybar.WindowIcon (pixBufFromColor) import System.Taffybar.WindowIcon (pixBufFromColor)
import Text.Printf (printf) import Text.Printf (printf)
import Text.Read (readMaybe) import Text.Read (readMaybe)
@@ -76,13 +83,20 @@ x11FullWorkspaceNames =
where where
go = zip [WorkspaceId i | i <- [0 ..]] go = zip [WorkspaceId i | i <- [0 ..]]
x11WorkspaceLabelSetter :: X11Workspaces.Workspace -> X11Workspaces.WorkspacesIO String remapNSP :: String -> String
x11WorkspaceLabelSetter workspace = remapNSP "NSP" = "S"
remapNSP . fromMaybe "" . lookup (X11Workspaces.workspaceIdx workspace) remapNSP n = n
<$> X11Workspaces.liftX11Def [] x11FullWorkspaceNames
where workspaceLabelSetter :: WorkspaceModel.WorkspaceInfo -> TaffyIO String
remapNSP "NSP" = "S" workspaceLabelSetter workspace = do
remapNSP n = n backendType <- asks backend
let identity = WorkspaceModel.workspaceIdentity workspace
fallbackLabel = remapNSP $ T.unpack (WorkspaceModel.workspaceName identity)
case (backendType, WorkspaceModel.workspaceNumericId identity) of
(BackendX11, Just workspaceId) -> do
fullNames <- runX11Def [] x11FullWorkspaceNames
return $ remapNSP $ fromMaybe fallbackLabel (lookup (WorkspaceId workspaceId) fullNames)
_ -> return fallbackLabel
-- ** Logging -- ** Logging
@@ -127,21 +141,9 @@ 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 workspaceIconCandidates :: WorkspaceModel.WindowInfo -> [Text]
-- usually not something we want visible in the workspace widget. workspaceIconCandidates windowData =
isSpecialHyprWorkspace :: Hyprland.HyprlandWorkspace -> Bool let baseNames = WorkspaceModel.windowClassHints windowData
isSpecialHyprWorkspace ws =
let name = T.toLower $ T.pack $ Hyprland.workspaceName ws
in T.isPrefixOf "special" name || Hyprland.workspaceIdx ws < 0
hyprlandIconCandidates :: Hyprland.HyprlandWindow -> [Text]
hyprlandIconCandidates windowData =
let baseNames =
map T.pack $
catMaybes
[ Hyprland.windowClass windowData,
Hyprland.windowInitialClass windowData
]
remapped = concatMap lookupIconRemap baseNames remapped = concatMap lookupIconRemap baseNames
remappedExpanded = concatMap iconNameVariants remapped remappedExpanded = concatMap iconNameVariants remapped
baseExpanded = concatMap iconNameVariants baseNames baseExpanded = concatMap iconNameVariants baseNames
@@ -152,8 +154,8 @@ 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) workspaceIconFromCandidate :: Int32 -> Text -> TaffyIO (Maybe Gdk.Pixbuf)
hyprlandIconFromCandidate size name workspaceIconFromCandidate size name
| isPathCandidate name = | isPathCandidate name =
liftIO $ getPixbufFromFilePath (T.unpack name) liftIO $ getPixbufFromFilePath (T.unpack name)
| otherwise = | otherwise =
@@ -161,11 +163,11 @@ hyprlandIconFromCandidate size name
(Hyprland.getWindowIconFromDesktopEntryByAppId size (T.unpack name)) (Hyprland.getWindowIconFromDesktopEntryByAppId size (T.unpack name))
(liftIO $ loadPixbufByName size name) (liftIO $ loadPixbufByName size name)
hyprlandManualIconGetter :: Hyprland.HyprlandWindowIconPixbufGetter workspaceManualIconGetter :: Workspaces.WindowIconPixbufGetter
hyprlandManualIconGetter = workspaceManualIconGetter =
Hyprland.handleIconGetterException $ \size windowData -> Workspaces.handleIconGetterException $ \size windowData ->
foldl maybeTCombine (return Nothing) $ foldl maybeTCombine (return Nothing) $
map (hyprlandIconFromCandidate size) (hyprlandIconCandidates windowData) map (workspaceIconFromCandidate size) (workspaceIconCandidates windowData)
fallbackIconPixbuf :: Int32 -> TaffyIO (Maybe Gdk.Pixbuf) fallbackIconPixbuf :: Int32 -> TaffyIO (Maybe Gdk.Pixbuf)
fallbackIconPixbuf size = do fallbackIconPixbuf size = do
@@ -189,10 +191,17 @@ fallbackIconPixbuf size = do
Just _ -> return result Just _ -> return result
Nothing -> Just <$> pixBufFromColor size 0x5f5f5fff Nothing -> Just <$> pixBufFromColor size 0x5f5f5fff
hyprlandFallbackIcon :: Hyprland.HyprlandWindowIconPixbufGetter workspaceFallbackIcon :: Workspaces.WindowIconPixbufGetter
hyprlandFallbackIcon size _ = workspaceFallbackIcon size _ =
fallbackIconPixbuf size fallbackIconPixbuf size
workspaceWindowIconGetter :: Workspaces.WindowIconPixbufGetter
workspaceWindowIconGetter =
workspaceManualIconGetter
<|||> Workspaces.getWindowIconPixbufFromChrome
<|||> Workspaces.defaultGetWindowIconPixbuf
<|||> workspaceFallbackIcon
-- ** Host Overrides -- ** Host Overrides
defaultCssFiles :: [FilePath] defaultCssFiles :: [FilePath]
@@ -240,75 +249,19 @@ windowsWidget :: TaffyIO Gtk.Widget
windowsWidget = windowsWidget =
decorateWithClassAndBoxM "windows" (windowsNew defaultWindowsConfig) decorateWithClassAndBoxM "windows" (windowsNew defaultWindowsConfig)
x11WorkspacesWidget :: TaffyIO Gtk.Widget workspacesWidget :: TaffyIO Gtk.Widget
x11WorkspacesWidget = workspacesWidget = Workspaces.workspacesNew cfg
flip widgetSetClassGI "workspaces"
=<< X11Workspaces.workspacesNew cfg
where where
common =
(X11Workspaces.workspacesCommonConfig X11Workspaces.defaultWorkspacesConfig)
{ WorkspaceWidgetConfig.minIcons = 1,
WorkspaceWidgetConfig.getWindowIconPixbuf =
X11Workspaces.scaledWindowIconPixbufGetter $
X11Workspaces.getWindowIconPixbufFromChrome
<|||> X11Workspaces.unscaledDefaultGetWindowIconPixbuf
<|||> (\size _ -> fallbackIconPixbuf size),
WorkspaceWidgetConfig.widgetGap = 0,
WorkspaceWidgetConfig.showWorkspaceFn = X11Workspaces.hideEmpty,
WorkspaceWidgetConfig.labelSetter = x11WorkspaceLabelSetter,
WorkspaceWidgetConfig.widgetBuilder = X11Workspaces.buildLabelOverlayController
}
cfg = cfg =
(X11Workspaces.applyCommonWorkspacesConfig common X11Workspaces.defaultWorkspacesConfig) Workspaces.defaultWorkspacesConfig
{ X11Workspaces.updateRateLimitMicroseconds = 100000 { Workspaces.widgetGap = 0,
} Workspaces.minIcons = 1,
Workspaces.getWindowIconPixbuf = workspaceWindowIconGetter,
-- | Like 'buildWorkspaceIconLabelOverlay' but lets you choose the corner. Workspaces.labelSetter = workspaceLabelSetter,
buildAlignedOverlay :: Workspaces.showWorkspaceFn =
Gtk.Align -> Gtk.Align -> Gtk.Widget -> Gtk.Widget -> TaffyIO Gtk.Widget \workspace ->
buildAlignedOverlay halign valign iconsWidget labelWidget = liftIO $ do Workspaces.hideEmpty workspace
base <- buildContentsBox iconsWidget && not (WorkspaceModel.workspaceIsSpecial workspace)
ebox <- Gtk.eventBoxNew
_ <- widgetSetClassGI ebox "overlay-box"
Gtk.widgetSetHalign ebox halign
Gtk.widgetSetValign ebox valign
Gtk.containerAdd ebox labelWidget
overlayLabel <- Gtk.toWidget ebox
overlay <- Gtk.overlayNew
baseW <- Gtk.toWidget base
Gtk.containerAdd overlay baseW
Gtk.overlayAddOverlay overlay overlayLabel
Gtk.overlaySetOverlayPassThrough overlay overlayLabel True
Gtk.toWidget overlay
hyprlandWorkspacesWidget :: TaffyIO Gtk.Widget
hyprlandWorkspacesWidget =
flip widgetSetClassGI "workspaces"
=<< Hyprland.hyprlandWorkspacesNew cfg
where
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
),
-- Don't show Hyprland "special:*" workspaces.
WorkspaceWidgetConfig.showWorkspaceFn =
\ws ->
Hyprland.workspaceState ws /= X11Workspaces.Empty
&& not (isSpecialHyprWorkspace ws),
WorkspaceWidgetConfig.getWindowIconPixbuf =
hyprlandManualIconGetter
<|||> Hyprland.defaultHyprlandGetWindowIconPixbuf
<|||> hyprlandFallbackIcon
} }
clockWidget :: TaffyIO Gtk.Widget clockWidget :: TaffyIO Gtk.Widget
@@ -668,9 +621,9 @@ sniTrayWidget = do
startWidgetsForBackend :: Backend -> [TaffyIO Gtk.Widget] startWidgetsForBackend :: Backend -> [TaffyIO Gtk.Widget]
startWidgetsForBackend backend = startWidgetsForBackend backend =
case backend of case backend of
BackendX11 -> [x11WorkspacesWidget, layoutWidget, windowsWidget] BackendX11 -> [workspacesWidget, layoutWidget, windowsWidget]
-- These Wayland widgets are Hyprland-specific. -- These Wayland widgets are Hyprland-specific.
BackendWayland -> [hyprlandWorkspacesWidget] BackendWayland -> [workspacesWidget, windowsWidget]
endWidgetsForHost :: String -> [TaffyIO Gtk.Widget] endWidgetsForHost :: String -> [TaffyIO Gtk.Widget]
endWidgetsForHost hostName = endWidgetsForHost hostName =