Skip to content

Commit

Permalink
Added support for dynamic status bars.
Browse files Browse the repository at this point in the history
This is heavily inspired by "XMonad.Hooks.DynamicBars", but it can be
used with any status-bar.
  • Loading branch information
TheMC47 committed Apr 4, 2021
1 parent 27bc02c commit 1d3ae60
Showing 1 changed file with 112 additions and 2 deletions.
114 changes: 112 additions & 2 deletions XMonad/Hooks/StatusBar.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{-# LANGUAGE FlexibleContexts, TypeApplications #-}
{-# LANGUAGE FlexibleContexts, TypeApplications, TupleSections #-}
-----------------------------------------------------------------------------
-- |
-- Module : XMonad.Hooks.StatusBar
Expand Down Expand Up @@ -39,6 +39,11 @@ module XMonad.Hooks.StatusBar (
-- * Multiple Status Bars
-- $multiple

-- * Dynamic Status Bars
-- $dynamic
dynamicSBs',
dynamicSBs,

-- * Property Logging utilities
xmonadPropLog,
xmonadPropLog',
Expand All @@ -54,6 +59,9 @@ module XMonad.Hooks.StatusBar (
import Control.Exception (SomeException, try)
import Control.Monad (void)
import qualified Codec.Binary.UTF8.String as UTF8 (encode)
import Data.Foldable (traverse_)
import Data.List (partition, (\\))
import Data.Monoid (All(..))
import qualified Data.Map as M
import System.Posix.Signals (sigTERM, signalProcessGroup)
import System.Posix.Types (ProcessID)
Expand All @@ -68,6 +76,8 @@ import qualified XMonad.StackSet as W
import qualified XMonad.Util.ExtensibleState as XS
import XMonad.Util.Run

import Graphics.X11.Xrandr (xrrSelectInput)

-- $usage
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
--
Expand Down Expand Up @@ -314,7 +324,7 @@ statusBarPipe cmd xpp = do
-- Here's an example of what such declarative configuration of multiple status
-- bars may look like:
--
-- > -- Make sure to setup the xmobar config accordingly
-- > -- Make sure to setup the xmobar configs accordingly
-- > xmobarTop = statusBarPropTo "_XMONAD_LOG_1" "xmobar -x 0 ~/.config/xmobar/xmobarrc_top" (pure ppTop)
-- > xmobarBottom = statusBarPropTo "_XMONAD_LOG_2" "xmobar -x 0 ~/.config/xmobar/xmobarrc_bottom" (pure ppBottom)
-- > xmobar1 = statusBarPropTo "_XMONAD_LOG_3" "xmobar -x 1 ~/.config/xmobar/xmobarrc1" (pure pp1)
Expand Down Expand Up @@ -346,7 +356,107 @@ statusBarPipe cmd xpp = do
--
-- By using the new interface, the config becomes more declarative and there's
-- less room for errors.
--
-- The only *problem* now is that the status bars will not be updated when your screen
-- configuration changes (by plugging in a monitor, for example). Check the section
-- on dynamic status bars for how to do that.

-- $dynamic
-- Using multiple status bars just by combining them with '<>' works well
-- as long as the screen configuration does not change often. If it does,
-- you should use 'dynamicSBs': by providing a function that creates
-- status bars, it takes care of setting up the event hook, the log hook
-- and the startup hook necessary to make the status bars, well, dynamic.
--
-- > xmobarTop = statusBarPropTo "_XMONAD_LOG_1" "xmobar -x 0 ~/.config/xmobar/xmobarrc_top" (pure ppTop)
-- > xmobarBottom = statusBarPropTo "_XMONAD_LOG_2" "xmobar -x 0 ~/.config/xmobar/xmobarrc_bottom" (pure ppBottom)
-- > xmobar1 = statusBarPropTo "_XMONAD_LOG_3" "xmobar -x 1 ~/.config/xmobar/xmobarrc1" (pure pp1)
-- >
-- > barSpawner :: ScreenId -> StatusBarConfig
-- > barSpawner 0 = xmobarTop <> xmobarBottom -- two bars on the main screen
-- > barSpawner 1 = xmobar1
-- > barSpawner _ = memtpy -- nothing on the rest of the screens
-- >
-- > main = xmonad $ dynamicSBs barSpawner (def { ... })
--
-- If this does not work (for example, because you have to use 'statusBarPipe'),
-- you can use 'dynamicSBs'':
--
-- > barSpawner :: X (ScreenId -> StatusBarConfig)
-- > barSpawner = do
-- > xmobarTop <- io $ statusBarPipe "xmobar -x 0 ~/.config/xmobar/xmobarrc_top" (pure ppTop)
-- > xmobarBottom <- io $ statusBarPipe "xmobar -x 0 ~/.config/xmobar/xmobarrc_bottom" (pure ppBottom)
-- > xmobar1 <- io $ statusBarPipe "xmobar -x 1 ~/.config/xmobar/xmobarrc1" (pure pp1)
-- > return $ \case -- this nicer syntax needs {-# LANGUAGE LambdaCase #-} at the top of the file.
-- > 0 -> xmobarTop <> xmobarBottom
-- > 1 -> xmobar1
-- > _ -> mempty
--
-- Note also that this interface can be used with one screen, or if
-- the screen configuration doesn't change.

newtype ActiveSBs = ASB {getASBs :: [(ScreenId, StatusBarConfig)]}

instance ExtensionClass ActiveSBs where
initialValue = ASB []

-- | Given a function to create status bars, 'dynamicSBs'
-- adds the dynamic status bar capabilities to the config.
--
-- Heavily inspired by "XMonad.Hooks.DynamicBars"
dynamicSBs :: (ScreenId -> StatusBarConfig) -> XConfig l -> XConfig l
dynamicSBs = dynamicSBs' . pure

-- | Like 'dynamicSBs', but takes a funcion wrapped in 'X' instead. Useful for
-- using with 'statusBarPipe'
dynamicSBs' :: X (ScreenId -> StatusBarConfig) -> XConfig l -> XConfig l
dynamicSBs' f conf = conf
{ startupHook = startupHook conf
>> setupEventHandler
>> killAllStatusBars
>> updateSBs f
, logHook = logHook conf >> logSBs
, handleEventHook = eventHookSBs f <> handleEventHook conf
}

-- | Given the function to create status bars, update
-- the status bars by killing those that shouldn't be
-- visible anymore and creates any missing status bars
updateSBs :: X (ScreenId -> StatusBarConfig) -> X ()
updateSBs xf = do
f <- xf
actualScreens <- withWindowSet $ return . map W.screen . W.screens
(toKeep, toKill) <-
partition ((`elem` actualScreens) . fst) . getASBs <$> XS.get
-- Kill the status bars
cleanSBs (map snd toKill)
-- Create new status bars if needed
let missing = actualScreens \\ map fst toKeep
added = map (\s -> (s, f s)) missing
traverse_ (sbStartupHook . snd) added
XS.put (ASB (toKeep ++ added))

-- | Handles 'RRScreenChangeNotifyEvent' by updating the
-- status bars.
eventHookSBs :: X (ScreenId -> StatusBarConfig) -> Event -> X All
eventHookSBs f RRScreenChangeNotifyEvent{} = updateSBs f >> return (All True)
eventHookSBs _ _ = return (All True)

-- | Run 'sbLogHook' for the saved 'StatusBarConfig's
logSBs :: X ()
logSBs = XS.get >>= traverse_ (sbLogHook . snd) . getASBs

-- | Subscribe to the 'RRScreenChangeNotifyEvent'
setupEventHandler :: X ()
setupEventHandler = do
dpy <- asks display
root <- asks theRoot
io $ xrrSelectInput dpy root rrScreenChangeNotifyMask

-- | Kill the given 'StatusBarConfig's from the given
-- list
cleanSBs :: [StatusBarConfig] -> X ()
cleanSBs = traverse_ sbCleanupHook

-- | The default property xmonad writes to. (@_XMONAD_LOG@).
xmonadDefProp :: String
Expand Down

0 comments on commit 1d3ae60

Please sign in to comment.