diff --git a/dotfiles/config/taffybar/flake.lock b/dotfiles/config/taffybar/flake.lock index 5e91a8c8..34c603af 100644 --- a/dotfiles/config/taffybar/flake.lock +++ b/dotfiles/config/taffybar/flake.lock @@ -115,11 +115,11 @@ "gtk-sni-tray": { "flake": false, "locked": { - "lastModified": 1760550917, - "narHash": "sha256-oELopLiVb7D1fzYHAiw/cbfNPHLQcOJ3Rz/I1gbk8IY=", + "lastModified": 1770069502, + "narHash": "sha256-jreuryLGfbyNwx5yEtOYGt2PX+uyGRsgmakYfCQ+OdM=", "owner": "taffybar", "repo": "gtk-sni-tray", - "rev": "f6d1bf5dd64ac0f532b03ae01f5e1cc051116f09", + "rev": "733a43f187b35bf65b443a7ede70d62f684932ef", "type": "github" }, "original": { @@ -148,16 +148,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1764252443, - "narHash": "sha256-U4G4dUSYWZYKtrF7/ozebD1B96at08SIhY4R9OaK1nw=", - "owner": "colonelpanic8", + "lastModified": 1770181073, + "narHash": "sha256-ksTL7P9QC1WfZasNlaAdLOzqD8x5EPyods69YBqxSfk=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "e1fc6c25b91d3d49dd02a156237721f12dbd86b2", + "rev": "bf922a59c5c9998a6584645f7d0de689512e444c", "type": "github" }, "original": { - "owner": "colonelpanic8", - "ref": "remove-gi-gtk-hs-patch", + "owner": "NixOS", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -310,18 +310,14 @@ ] }, "locked": { - "lastModified": 1760591109, - "narHash": "sha256-O21ayp5v2eccXdcwNjTK5ZB99ruK0Zt9CUaw5Rye42g=", - "owner": "taffybar", - "repo": "taffybar", - "rev": "b256a711416036ca124fc9b3f89e7c957535e465", - "type": "github" + "lastModified": 1770273353, + "narHash": "sha256-0VQfT0bxyU+xmEMLDRQq+hbJX35+PWPjSXiAVFGOlzQ=", + "path": "/home/imalison/dotfiles/dotfiles/config/taffybar/taffybar", + "type": "path" }, "original": { - "owner": "taffybar", - "ref": "master", - "repo": "taffybar", - "type": "github" + "path": "/home/imalison/dotfiles/dotfiles/config/taffybar/taffybar", + "type": "path" } }, "unstable": { @@ -349,11 +345,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1748252779, - "narHash": "sha256-kSyD/VDUX2m9c2vcuLBT2wnpYiVSHHlP9vuDTtsAtD8=", + "lastModified": 1764753633, + "narHash": "sha256-6552zbHzdNnkREnOluE6xePIib5cc/8Nc5OnPyHORUo=", "owner": "NorfairKing", "repo": "weeder-nix", - "rev": "388df7a6f00220d1960118e1ad37cd86150d2c5a", + "rev": "2203c43ab9f1c4e52c2cff8e3d01bbb53159b922", "type": "github" }, "original": { diff --git a/dotfiles/config/taffybar/flake.nix b/dotfiles/config/taffybar/flake.nix index 2c2e7d03..12a071ac 100644 --- a/dotfiles/config/taffybar/flake.nix +++ b/dotfiles/config/taffybar/flake.nix @@ -1,10 +1,10 @@ { inputs = { flake-utils.url = "github:numtide/flake-utils"; - nixpkgs.url = "github:colonelpanic8/nixpkgs/remove-gi-gtk-hs-patch"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; xmonad.url = "github:xmonad/xmonad/master"; taffybar = { - url = "github:taffybar/taffybar/master"; + url = "path:/home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"; inputs.nixpkgs.follows = "nixpkgs"; inputs.xmonad.follows = "xmonad"; }; @@ -16,7 +16,7 @@ taffybar = prev.haskell.lib.overrideCabal hsuper.taffybar (oa: { doHaddock = false; doCheck = false; - # Fix for GHC 9.4 where liftA2 is not in Prelude + # Legacy fix for older GHC (harmless on newer) postPatch = (oa.postPatch or "") + '' substituteInPlace src/System/Taffybar/DBus/Client/Util.hs \ --replace-fail "import Control.Monad (forM)" \ @@ -47,7 +47,7 @@ import Control.Applicative (liftA2)" final.xorg.libXtst.out ]; }; - defComp = { compiler = "ghc94"; }; + defComp = { compiler = "ghc98"; }; overlay = xmonad.lib.fromHOL hoverlay defComp; overlayList = [ taffybar.overlays.default overlay ]; in flake-utils.lib.eachDefaultSystem (system: @@ -57,10 +57,21 @@ import Control.Applicative (liftA2)" { devShell = hpkgs.shellFor { packages = p: [ p.imalison-taffybar p.taffybar ]; - nativeBuildInputs = with hpkgs; [ + nativeBuildInputs = (with hpkgs; [ cabal-install # ghcid ormolu implicit-hie haskell-language-server hlint + ]) ++ [ + pkgs.gdk-pixbuf + pkgs.librsvg ]; + shellHook = '' + if [ -z "''${GDK_PIXBUF_MODULE_FILE:-}" ]; then + export GDK_PIXBUF_MODULE_FILE="${pkgs.gdk-pixbuf}/lib/gdk-pixbuf-2.0/${pkgs.gdk-pixbuf.version}/loaders.cache" + fi + if [ -z "''${GDK_PIXBUF_MODULEDIR:-}" ]; then + export GDK_PIXBUF_MODULEDIR="${pkgs.gdk-pixbuf}/lib/gdk-pixbuf-2.0/${pkgs.gdk-pixbuf.version}/loaders" + fi + ''; }; defaultPackage = hpkgs.imalison-taffybar; }) // { diff --git a/dotfiles/config/taffybar/imalison-taffybar.cabal b/dotfiles/config/taffybar/imalison-taffybar.cabal index 2980372b..c7536092 100644 --- a/dotfiles/config/taffybar/imalison-taffybar.cabal +++ b/dotfiles/config/taffybar/imalison-taffybar.cabal @@ -22,6 +22,7 @@ executable taffybar , directory , filepath , gi-gtk3 + , gi-gdkpixbuf , gtk-sni-tray , gtk-strut , haskell-gi-base diff --git a/dotfiles/config/taffybar/taffybar b/dotfiles/config/taffybar/taffybar index 220cb5a3..05d7a1f8 160000 --- a/dotfiles/config/taffybar/taffybar +++ b/dotfiles/config/taffybar/taffybar @@ -1 +1 @@ -Subproject commit 220cb5a34d81543baebbb530731481cafba61138 +Subproject commit 05d7a1f8fa3af23f508301665932b9beae0cf3ed diff --git a/dotfiles/config/taffybar/taffybar.hs b/dotfiles/config/taffybar/taffybar.hs index 8aefadee..da9cb9f5 100644 --- a/dotfiles/config/taffybar/taffybar.hs +++ b/dotfiles/config/taffybar/taffybar.hs @@ -15,6 +15,7 @@ import qualified Data.Map as M import Data.Maybe import qualified Data.Text import Data.Time +import qualified GI.GdkPixbuf.Objects.Pixbuf as Gdk import qualified GI.Gtk as Gtk import qualified GI.Gtk.Objects.Overlay as Gtk import Network.HostName @@ -28,7 +29,7 @@ import System.Log.Handler.Simple import System.Log.Logger import System.Process import System.Taffybar -import System.Taffybar.Context (appendHook) +import System.Taffybar.Context (Backend(..), TaffyIO, appendHook, detectBackend) import System.Taffybar.DBus import System.Taffybar.DBus.Toggle import System.Taffybar.Hooks @@ -43,9 +44,12 @@ import System.Taffybar.Widget.Generic.Icon import System.Taffybar.Widget.Generic.PollingGraph import System.Taffybar.Widget.Generic.PollingLabel import System.Taffybar.Widget.Util -import System.Taffybar.Widget.Workspaces +import qualified System.Taffybar.Widget.HyprlandWorkspaces as Hyprland +import qualified System.Taffybar.Widget.Workspaces as X11Workspaces +import System.Taffybar.WindowIcon (pixBufFromColor) import Text.Printf import Text.Read hiding (lift) +import Data.Int (Int32) setClassAndBoundingBoxes :: MonadIO m => Data.Text.Text -> Gtk.Widget -> m Gtk.Widget setClassAndBoundingBoxes klass = buildContentsBox >=> flip widgetSetClassGI klass @@ -97,7 +101,7 @@ getFullWorkspaceNames = go <$> readAsListOfString Nothing "_NET_DESKTOP_FULL_NAM where go = zip [WorkspaceId i | i <- [0..]] workspaceNamesLabelSetter workspace = - remapNSP . fromMaybe "" . lookup (workspaceIdx workspace) <$> + remapNSP . fromMaybe "" . lookup (X11Workspaces.workspaceIdx workspace) <$> liftX11Def [] getFullWorkspaceNames where remapNSP "NSP" = "S" remapNSP n = n @@ -124,6 +128,106 @@ logDebug = do -- enableLogger "System.Taffybar.WindowIcon" DEBUG -- enableLogger "System.Taffybar.Widget.Generic.PollingLabel" DEBUG +iconRemap :: [(Data.Text.Text, [Data.Text.Text])] +iconRemap = + [ ("spotify", ["spotify-client", "spotify"]) + ] + +iconRemapMap :: M.Map Data.Text.Text [Data.Text.Text] +iconRemapMap = + M.fromList [ (Data.Text.toLower k, v) | (k, v) <- iconRemap ] + +lookupIconRemap :: Data.Text.Text -> [Data.Text.Text] +lookupIconRemap name = fromMaybe [] $ M.lookup (Data.Text.toLower name) iconRemapMap + +iconNameVariants :: Data.Text.Text -> [Data.Text.Text] +iconNameVariants raw = + let lower = Data.Text.toLower raw + stripped = fromMaybe lower (Data.Text.stripSuffix ".desktop" lower) + suffixes = ["-gtk", "-client", "-desktop"] + stripSuffixes name = + let variants = mapMaybe (`Data.Text.stripSuffix` name) suffixes + in nub $ variants ++ [name] + baseNames = stripSuffixes stripped ++ [raw] + toDash c + | c == ' ' || c == '_' || c == '.' || c == '/' = '-' + | otherwise = c + toUnderscore c + | c == ' ' || c == '-' || c == '.' || c == '/' = '_' + | otherwise = c + variantsFor name = + let dotted = + case Data.Text.splitOn "." name of + [] -> name + xs -> last xs + dashed = Data.Text.map toDash name + dashedDotted = Data.Text.map toDash dotted + underscored = Data.Text.map toUnderscore name + underscoredDotted = Data.Text.map toUnderscore dotted + in [dotted, dashed, dashedDotted, underscored, underscoredDotted, name] + in nub $ concatMap variantsFor baseNames + +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 windowData = + let baseNames = map Data.Text.pack $ catMaybes + [ Hyprland.windowClass windowData + , Hyprland.windowInitialClass windowData + ] + remapped = concatMap lookupIconRemap baseNames + remappedExpanded = concatMap iconNameVariants remapped + baseExpanded = concatMap iconNameVariants baseNames + in nub (remappedExpanded ++ baseExpanded) + +isPathCandidate :: Data.Text.Text -> Bool +isPathCandidate name = + Data.Text.isInfixOf "/" name || + any (`Data.Text.isSuffixOf` name) [".png", ".svg", ".xpm"] + +hyprlandIconFromCandidate size name + | isPathCandidate name = + liftIO $ getPixbufFromFilePath (Data.Text.unpack name) + | otherwise = + maybeTCombine + (Hyprland.getWindowIconFromDesktopEntryByAppId size (Data.Text.unpack name)) + (liftIO $ loadPixbufByName size name) + +hyprlandManualIconGetter :: Hyprland.HyprlandWindowIconPixbufGetter +hyprlandManualIconGetter = + Hyprland.handleIconGetterException $ \size windowData -> + foldl maybeTCombine (return Nothing) $ + map (hyprlandIconFromCandidate size) (hyprlandIconCandidates windowData) + +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" + ] + tryNames = + foldl + maybeTCombine + (return Nothing) + (map (liftIO . loadPixbufByName size) fallbackNames) + result <- tryNames + case result of + Just _ -> return result + Nothing -> Just <$> pixBufFromColor size 0x5f5f5fff + +hyprlandFallbackIcon :: Hyprland.HyprlandWindowIconPixbufGetter +hyprlandFallbackIcon size _ = + fallbackIconPixbuf size + cssFilesByHostname = [ ("uber-loaner", ["uber-loaner.css"]) , ("imalison-home", ["taffybar.css"]) @@ -136,7 +240,7 @@ main = do enableLogger "Graphics.UI.GIGtkStrut" DEBUG hostName <- getHostName - homeDirectory <- getHomeDirectory + backend <- detectBackend let relativeFiles = fromMaybe ["taffybar.css"] $ lookup hostName cssFilesByHostname cssFiles <- mapM (getUserConfigFile "taffybar") relativeFiles @@ -152,20 +256,32 @@ main = do windowsNew defaultWindowsConfig myWorkspaces = flip widgetSetClassGI "workspaces" =<< - workspacesNew defaultWorkspacesConfig + X11Workspaces.workspacesNew X11Workspaces.defaultWorkspacesConfig { minIcons = 1 , getWindowIconPixbuf = - scaledWindowIconPixbufGetter $ - getWindowIconPixbufFromChrome <|||> - unscaledDefaultGetWindowIconPixbuf <|||> - (\size _ -> lift $ loadPixbufByName size - "application-default-icon") + X11Workspaces.scaledWindowIconPixbufGetter $ + X11Workspaces.getWindowIconPixbufFromChrome <|||> + X11Workspaces.unscaledDefaultGetWindowIconPixbuf <|||> + (\size _ -> fallbackIconPixbuf size) , widgetGap = 0 - , showWorkspaceFn = hideEmpty + , showWorkspaceFn = X11Workspaces.hideEmpty , updateRateLimitMicroseconds = 100000 , labelSetter = workspaceNamesLabelSetter - , widgetBuilder = buildLabelOverlayController + , widgetBuilder = X11Workspaces.buildLabelOverlayController } + myHyprWorkspaces = + flip widgetSetClassGI "workspaces" =<< + Hyprland.hyprlandWorkspacesNew Hyprland.defaultHyprlandWorkspacesConfig + { Hyprland.widgetGap = 0 + , Hyprland.minIcons = 1 + , Hyprland.showWorkspaceFn = + (\ws -> Hyprland.workspaceState ws /= Hyprland.Empty && + not (isSpecialHyprWorkspace ws)) + , Hyprland.getWindowIconPixbuf = + hyprlandManualIconGetter <|||> + Hyprland.defaultHyprlandGetWindowIconPixbuf <|||> + hyprlandFallbackIcon + } myClock = deocrateWithSetClassAndBoxes "clock" $ textClockNewWith defaultClockConfig @@ -193,7 +309,7 @@ main = do ] fullEndWidgets = baseEndWidgets ++ [ myCPU, myMem, myNet, myMpris ] laptopEndWidgets = batteryWidgets ++ baseEndWidgets - baseConfig = + baseConfigX11 = defaultSimpleTaffyConfig { startWidgets = [ myWorkspaces, myLayout, myWindows ] , endWidgets = baseEndWidgets @@ -204,30 +320,65 @@ main = do , cssPaths = cssFiles , centerWidgets = [ myClock ] } + baseConfigWayland = + defaultSimpleTaffyConfig + { startWidgets = [ myHyprWorkspaces ] + , endWidgets = baseEndWidgets + , barPosition = Top + , widgetSpacing = 0 + , barPadding = 0 + , barHeight = ScreenRatio $ 1/27 + , cssPaths = cssFiles + , centerWidgets = [ myClock ] + } + x11Overrides = + [ ( "uber-loaner" + , baseConfigX11 { endWidgets = laptopEndWidgets } + ) + , ( "adell" + , baseConfigX11 { endWidgets = laptopEndWidgets } + ) + , ( "stevie-nixos" + , baseConfigX11 { endWidgets = laptopEndWidgets + , startWidgets = [ myWorkspaces, myLayout ] + } + ) + , ( "strixi-minaj" + , baseConfigX11 { endWidgets = laptopEndWidgets } + ) + , ( "jay-lenovo" + , baseConfigX11 { endWidgets = laptopEndWidgets } + ) + , ( "nixquick" + , baseConfigX11 { endWidgets = [ myTray , myMpris ] } + ) + ] + waylandOverrides = + [ ( "uber-loaner" + , baseConfigWayland { endWidgets = laptopEndWidgets } + ) + , ( "adell" + , baseConfigWayland { endWidgets = laptopEndWidgets } + ) + , ( "stevie-nixos" + , baseConfigWayland { endWidgets = laptopEndWidgets } + ) + , ( "strixi-minaj" + , baseConfigWayland { endWidgets = laptopEndWidgets } + ) + , ( "jay-lenovo" + , baseConfigWayland { endWidgets = laptopEndWidgets } + ) + , ( "nixquick" + , baseConfigWayland { endWidgets = [ myTray , myMpris ] } + ) + ] selectedConfig = - fromMaybe baseConfig $ lookup hostName - [ ( "uber-loaner" - , baseConfig { endWidgets = laptopEndWidgets } - ) - , ( "adell" - , baseConfig { endWidgets = laptopEndWidgets } - ) - , ( "stevie-nixos" - , baseConfig { endWidgets = laptopEndWidgets - , startWidgets = [myWorkspaces, myLayout] - } - ) - , ( "strixi-minaj" - , baseConfig { endWidgets = laptopEndWidgets } - ) - , ( "jay-lenovo" - , baseConfig { endWidgets = laptopEndWidgets } - ) - , ( "nixquick" - , baseConfig { endWidgets = [ myTray , myMpris ] } - ) - - ] + case backend of + BackendX11 -> + fromMaybe baseConfigX11 $ lookup hostName x11Overrides + BackendWayland -> + fromMaybe baseConfigWayland $ lookup hostName waylandOverrides simpleTaffyConfig = selectedConfig { centerWidgets = [ myClock ] -- , endWidgets = [] @@ -237,4 +388,4 @@ main = do appendHook (void $ getTrayHost False) $ withLogServer $ withToggleServer $ - toTaffyConfig simpleTaffyConfig + toTaffybarConfig simpleTaffyConfig