module Rahm.Desktop.XMobarLog (XMobarLog, spawnXMobar, xMobarLogHook) where
import Control.Monad (forM_, unless)
import Control.Monad.Writer (execWriter, tell)
import Data.Char (isAsciiLower, isAsciiUpper, isDigit, isSpace)
import Data.IORef (newIORef, readIORef, writeIORef)
import Data.Maybe (isJust)
import Rahm.Desktop.Layout.Draw (drawLayout)
import Rahm.Desktop.Logger
( LogLevel (Debug, Error, Fatal, Trace, Warn),
getLogLevel,
logs,
)
import qualified Rahm.Desktop.StackSet as S
( Workspace (tag),
peek,
)
import Rahm.Desktop.Theater (getTheaters)
import Rahm.Desktop.Workspaces
( WorkspaceState (..),
getPopulatedWorkspaces,
)
import Rahm.Desktop.XMobarLog.PendingBuffer (getPendingBuffer)
import System.IO (Handle, hPutStrLn, hSetEncoding, utf8)
import Text.Printf (printf)
import XMonad (X)
import qualified XMonad as X (XState (windowset), gets, io)
import XMonad.Util.NamedWindows (getName)
import XMonad.Util.Run (runProcessWithInput, spawnPipe)
newtype XMobarLog = XMobarLog Handle
-- The log hook for XMobar. This is a custom log hook that does not use any
-- of the Xmonad dynamic log libraries.
--
-- This is because the given dynamic log libraries don't handle unicode properly
-- and this has been causing issues. It is also more flexible and frankly easier
-- to just DIY.
spawnXMobar :: IO XMobarLog
spawnXMobar = do
pipe <- spawnPipe "xmobar"
hSetEncoding pipe utf8
return (XMobarLog pipe)
-- getUname :: X String
-- getUname = do
-- readProcess
-- XMonad Log Hook meant to be used with the XMonad config logHook.
xMobarLogHook :: IO (XMobarLog -> X ())
xMobarLogHook = do
unameRef <- newIORef Nothing
-- (_, uname, _) <- readProcessWithExitCode "/usr/bin/uname" ["-r"] ""
-- putStrLn $ "Uname " ++ uname
return $ \(XMobarLog xmproc) -> do
(_, _, layoutXpm) <- drawLayout
loglevel <- getLogLevel
theaters <- filter (\(j, _, _) -> isJust j) <$> getTheaters
winset <- X.gets X.windowset
title <- maybe (pure "") (fmap show . getName) . S.peek $ winset
pendingBuffer'' <- getPendingBuffer
uname <- getUname unameRef
let pendingBufferSize = max 10 $ length uname
let (pendingBufferColor, pendingBuffer') =
if null pendingBuffer''
then ("#a0a0a0", uname)
else ("#f0a0a0,#202020", pendingBuffer'')
let pendingBuffer =
take pendingBufferSize $
reverse
( take pendingBufferSize (reverse pendingBuffer')
)
++ repeat ' '
let wss = getPopulatedWorkspaces winset
let log = trunc 80 $
execWriter $ do
tell $ printf " " pendingBufferColor
tell pendingBuffer
tell ""
tell " "
tell (toChangeLayoutAction layoutXpm)
tell " "
tell $ logLevelToXMobar loglevel
forM_ theaters $ \case
(Just n, _, True) -> do
tell ""
tell $ toTheaterAction n
tell " "
(Just n, _, False) -> do
tell ""
tell $ toTheaterAction n
tell " "
_ -> return ()
unless (null theaters) $ do
tell "| "
forM_ wss $ \(t, ws) -> do
case t of
Current -> tell ""
Visible -> tell ""
Hidden -> tell ""
tell $ toAction $ S.tag ws
tell " "
tell "| "
tell ""
tell title
tell ""
logs Trace "XMobar: %s" log
X.io $ hPutStrLn xmproc log
where
toAction [ch]
| isAsciiUpper ch
|| isAsciiLower ch
|| isDigit ch =
printf "%s" [ch] [ch] [ch]
toAction ch = ch
toTheaterAction [ch]
| isAsciiUpper ch
|| isAsciiLower ch
|| isDigit ch =
printf "%s" [ch] [ch]
toTheaterAction ch = ch
toChangeLayoutAction :: String -> String
toChangeLayoutAction =
printf
"\
\\
\%s"
logLevelToXMobar Trace = "[Trace] "
logLevelToXMobar Debug = "[Debug] "
logLevelToXMobar Warn = "[Warn] "
logLevelToXMobar Error = "[Error] "
logLevelToXMobar Fatal = "[Fatal] "
logLevelToXMobar _ = ""
getUname ref = X.io $ do
s <- readIORef ref
case s of
Nothing -> do
uname' <- runProcessWithInput "uname" ["-r"] ""
let uname = dropWhile isSpace (reverse $ dropWhile isSpace $ reverse uname')
writeIORef ref $ Just uname
return uname
Just uname -> return uname
-- Truncate an XMobar string to the provided number of _visible_ characters.
-- This is to keep long window titles from overrunning the whole bar.
trunc :: Int -> String -> String
trunc amt str = reverse $ trunc' False amt str []
where
trunc' _ _ [] acc = acc
trunc' ignore amt (a : as) acc =
case a of
'<' -> trunc' True amt as (a : acc)
'>' -> trunc' False amt as (a : acc)
_ ->
if ignore
then trunc' True amt as (a : acc)
else case amt of
0 -> trunc' False 0 as acc
3 -> trunc' False 0 as ("..." ++ acc)
_ -> trunc' False (amt - 1) as (a : acc)