{-# LANGUAGE RecordWildCards #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Hooks.DynamicIcons -- Copyright : (c) Will Pierlot -- License : BSD3-style (see LICENSE) -- -- Maintainer : Will Pierlot -- Stability : unstable -- Portability : unportable -- -- Dynamically augment workspace names logged to a status bar via DynamicLog -- based on the contents (windows) of the workspace. ----------------------------------------------------------------------------- module XMonad.Hooks.DynamicIcons ( -- * Usage -- $usage -- * Creating Dynamic Icons dynamicLogIconsWithPP, appIcon, -- * Customization dynamicIconsPP, getWorkspaceIcons, IconConfig(..), iconsFmtAppend, iconsFmtReplace, wrapUnwords, ) where import XMonad import qualified XMonad.StackSet as S import qualified Data.Map as M import XMonad.Hooks.DynamicLog import XMonad.Prelude (for, (<&>), (<=<), (>=>)) -- $usage -- Dynamically augment Workspace's 'WorkspaceId' as shown on a status bar -- based on the 'Window's inside the Workspace. -- -- Icons are specified by a @Query [String]@, which is something like a -- 'ManageHook' (and uses the same syntax) that returns a list of 'String's -- (icons). This 'Query' is evaluated for each window and the results are -- joined together. 'appIcon' is a useful shortcut here. -- -- For example: -- -- > myIcons :: Query [String] -- > myIcons = composeAll -- > [ className =? "discord" --> appIcon "\xfb6e" -- > , className =? "Discord" --> appIcon "\xf268" -- > , className =? "Firefox" --> appIcon "\63288" -- > , className =? "Spotify" <||> className =? "spotify" --> appIcon "阮" -- > ] -- -- then you can add the hook to your config: -- -- > main = xmonad $ … $ def -- > { logHook = dynamicLogIconsWithPP icons xmobarPP -- > , … } -- -- Here is an example of this -- -- <> -- -- Note: You can use any string you want here. -- The example shown here uses NerdFont Icons to represent open applications. -- -- If you want to customize formatting and/or combine this with other -- 'PP' extensions like "XMonad.Util.ClickableWorkspaces", here's a more -- advanced example how to do that: -- -- > myIconConfig = def{ iconConfigIcons = myIcons, iconConfigFmt = iconsFmtAppend concat } -- > main = xmonad $ … $ def -- > { logHook = xmonadPropLog =<< dynamicLogString =<< clickablePP =<< -- > dynamicIconsPP myIconConfig xmobarPP -- > , … } -- | Shortcut for configuring single icons. appIcon :: String -> Query [String] appIcon = pure . pure -- | Adjusts the 'PP' and then runs 'dynamicLogWithPP' dynamicLogIconsWithPP :: Query [String] -- ^ The 'IconSet' to use -> PP -- ^ The 'PP' to alter -> X () -- ^ The resulting 'X' action dynamicLogIconsWithPP q = dynamicLogWithPP <=< dynamicIconsPP def{ iconConfigIcons = q } -- | Modify "XMonad.Hooks.DynamicLog"\'s pretty-printing format to augment -- workspace names with icons based on the contents (windows) of the workspace. dynamicIconsPP :: IconConfig -> PP -> X PP dynamicIconsPP ic pp = getWorkspaceIcons ic <&> \ren -> pp{ ppRename = ppRename pp >=> ren } -- | Returns a function for 'ppRename' that augments workspaces with icons -- according to the provided 'IconConfig'. getWorkspaceIcons :: IconConfig -> X (String -> WindowSpace -> String) getWorkspaceIcons IconConfig{..} = fmt <$> getWorkspaceIcons' iconConfigIcons where fmt icons s w = iconConfigFmt s (M.findWithDefault [] (S.tag w) icons) getWorkspaceIcons' :: Query [String] -> X (M.Map WorkspaceId [String]) getWorkspaceIcons' q = do ws <- gets (S.workspaces . windowset) is <- for ws $ foldMap (runQuery q) . S.integrate' . S.stack pure $ M.fromList (zip (map S.tag ws) is) -- | Datatype for expanded 'Icon' configurations data IconConfig = IconConfig { iconConfigIcons :: Query [String] -- ^ What icons to use for each window. , iconConfigFmt :: WorkspaceId -> [String] -> String -- ^ How to format the result, see 'iconsFmtReplace', 'iconsFmtAppend'. } instance Default IconConfig where def = IconConfig { iconConfigIcons = mempty , iconConfigFmt = iconsFmtReplace (wrapUnwords "{" "}") } -- | 'iconConfigFmt' that replaces the workspace name with icons, if any. -- -- First parameter specifies how to concatenate multiple icons. Useful values -- include: 'concat', 'unwords', 'wrapUnwords'. -- -- ==== __Examples__ -- -- >>> iconsFmtReplace concat "1" [] -- "1" -- -- >>> iconsFmtReplace concat "1" ["A", "B"] -- "AB" -- -- >>> iconsFmtReplace (wrapUnwords "{" "}") "1" ["A", "B"] -- "{A B}" iconsFmtReplace :: ([String] -> String) -> WorkspaceId -> [String] -> String iconsFmtReplace cat ws is | null is = ws | otherwise = cat is -- | 'iconConfigFmt' that appends icons to the workspace name. -- -- First parameter specifies how to concatenate multiple icons. Useful values -- include: 'concat', 'unwords', 'wrapUnwords'. -- -- ==== __Examples__ -- -- >>> iconsFmtAppend concat "1" [] -- "1" -- -- >>> iconsFmtAppend concat "1" ["A", "B"] -- "1:AB" iconsFmtAppend :: ([String] -> String) -> WorkspaceId -> [String] -> String iconsFmtAppend cat ws is | null is = ws | otherwise = ws ++ ':' : cat is -- | Join words with spaces, and wrap the result in delimiters unless there -- was exactly one element. -- -- ==== __Examples__ -- -- >>> wrapUnwords "{" "}" ["A", "B"] -- "{A B}" -- -- >>> wrapUnwords "{" "}" ["A"] -- "A" -- -- >>> wrapUnwords "{" "}" [] -- "" wrapUnwords :: String -> String -> [String] -> String wrapUnwords _ _ [x] = x wrapUnwords l r xs = wrap l r (unwords xs)