aboutsummaryrefslogtreecommitdiff
path: root/src/Rahm/Desktop/XMobarLog.hs
blob: ea71ef29014fb1e223c6e9aec5d60a267fb9818a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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 "<fc=%s><fn=3> " pendingBufferColor
            tell pendingBuffer
            tell "</fn></fc>"

            tell " "
            tell (toChangeLayoutAction layoutXpm)
            tell " "
            tell $ logLevelToXMobar loglevel

            forM_ theaters $ \case
              (Just n, _, True) -> do
                tell "<fn=1><fc=#ffffff>"
                tell $ toTheaterAction n
                tell " </fc></fn>"
              (Just n, _, False) -> do
                tell "<fn=2><fc=#888888>"
                tell $ toTheaterAction n
                tell " </fc></fn>"
              _ -> return ()

            unless (null theaters) $ do
              tell "<fc=#888888>| </fc>"

            forM_ wss $ \(t, ws) -> do
              case t of
                Current -> tell "<fn=1><fc=#ff8888>"
                Visible -> tell "<fn=6><fc=#8888ff>"
                Hidden -> tell "<fn=2><fc=#888888>"
              tell $ toAction $ S.tag ws
              tell "</fc></fn> "

            tell "<fc=#888888>| </fc>"
            tell "<fc=#ff8888><fn=3>"
            tell title
            tell "</fn></fc>"

    logs Trace "XMobar: %s" log
    X.io $ hPutStrLn xmproc log
  where
    toAction [ch]
      | isAsciiUpper ch
          || isAsciiLower ch
          || isDigit ch =
        printf "<action=`xdotool key 'Super_L+g' '%s'` button=1><action=`xdotool key 'Super_L+Shift_L+g' '%s'` button=3>%s</action></action>" [ch] [ch] [ch]
    toAction ch = ch

    toTheaterAction [ch]
      | isAsciiUpper ch
          || isAsciiLower ch
          || isDigit ch =
        printf "<action=`xdotool key 'Super_L+Shift_L+g' '%s'` button=1>%s</action>" [ch] [ch]
    toTheaterAction ch = ch

    toChangeLayoutAction :: String -> String
    toChangeLayoutAction =
      printf
        "<action=`xdotool key --delay 50 'Super_L+space' 'Super_L+n'` button=1>\
        \<action=`xdotool key --delay 50 'Super_L+space' 'Super_L+p'` button=3>\
        \%s</action></action>"

    logLevelToXMobar Trace = "<fn=3><fc=#88ffff>[Trace]</fc></fn>   "
    logLevelToXMobar Debug = "<fn=3><fc=#ff88ff>[Debug]</fc></fn>   "
    logLevelToXMobar Warn = "<fn=3><fc=#ffff88>[Warn] </fc></fn>   "
    logLevelToXMobar Error = "<fn=3><fc=#ff8888>[Error]</fc></fn>   "
    logLevelToXMobar Fatal = "<fn=3><fc=#888888>[Fatal]</fc></fn>   "
    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)