#+title: Interfacing with window managers Listed below are ways to interface xmobar with your window manager of choice. * Property-based Logging ** =XMonadLog= - Aliases to XMonadLog - Displays information from xmonad's =_XMONAD_LOG=. You can use this by using functions from the [[https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Hooks-DynamicLog.html][XMonad.Hooks.DynamicLog]] module. By using the =xmonadPropLog= function in your logHook, you can write the the above property. The following shows a minimal xmonad configuration that spawns xmobar and then writes to the =_XMONAD_LOG= property. #+begin_src haskell main = do spawn "xmobar" xmonad $ def { logHook = dynamicLogString defaultPP >>= xmonadPropLog } #+end_src This plugin can be used as a sometimes more convenient alternative to =StdinReader=. For instance, it allows you to (re)start xmobar outside xmonad. ** =UnsafeXMonadLog= - Aliases to UnsafeXMonadLog - Displays any text received by xmobar on the =_XMONAD_LOG= atom. - Will not do anything to the text received. This means you can pass xmobar dynamic actions. Be careful to escape (using ==) or remove tags from dynamic text that you pipe through to xmobar in this way. - Sample usage: Send the list of your workspaces, enclosed by actions tags, to xmobar. This enables you to switch to a workspace when you click on it in xmobar! #+begin_src shell ws1 ws2 #+end_src - If you use xmonad, It is advised that you still use =xmobarStrip= for the =ppTitle= in your logHook: #+begin_src haskell myPP = defaultPP { ppTitle = xmobarStrip } main = xmonad $ def { logHook = dynamicLogString myPP >>= xmonadPropLog } #+end_src ** =XPropertyLog PropName= - Aliases to =PropName= - Reads the X property named by =PropName= (a string) and displays its value. The [[../examples/xmonadpropwrite.hs][examples/xmonadpropwrite.hs script]] in xmobar's distribution can be used to set the given property from the output of any other program or script. ** =UnsafeXPropertyLog PropName= - Aliases to =PropName= - Same as =XPropertyLog= but the input is not filtered to avoid injection of actions (cf. =UnsafeXMonadLog=). The program writing the value of the read property is responsible of performing any needed cleanups. ** =NamedXPropertyLog PropName Alias= - Aliases to =Alias= - Same as =XPropertyLog= but a custom alias can be specified. ** =UnsafeNamedXPropertyLog PropName Alias= - Aliases to =Alias= - Same as =UnsafeXPropertyLog=, but a custom alias can be specified. * Logging via Stdin ** =StdinReader= - Aliases to StdinReader - Displays any text received by xmobar on its standard input. - Strips actions from the text received. This means you can't pass dynamic actions via stdin. This is safer than =UnsafeStdinReader= because there is no need to escape the content before passing it to xmobar's standard input. ** =UnsafeStdinReader= - Aliases to UnsafeStdinReader - Displays any text received by xmobar on its standard input. - Similar to [[=UnsafeXMonadLog=][UnsafeXMonadLog]], in the sense that it does not strip any actions from the received text, only using =stdin= and not a property atom of the root window. Please be equally carefully when using this as when using =UnsafeXMonadLog=! * Pipe-based Logging ** =PipeReader "default text:/path/to/pipe" Alias= - Reads its displayed output from the given pipe. - Prefix an optional default text separated by a colon - Expands environment variables in the first argument of syntax =${VAR}= or =$VAR= ** =MarqueePipeReader "default text:/path/to/pipe" (length, rate, sep) Alias= - Generally equivalent to PipeReader - Text is displayed as marquee with the specified length, rate in 10th seconds and separator when it wraps around #+begin_src haskell Run MarqueePipeReader "/tmp/testpipe" (10, 7, "+") "mpipe" #+end_src - Expands environment variables in the first argument ** =BufferedPipeReader Alias [(Timeout, Bool, "/path/to/pipe1"), ..]= - Display data from multiple pipes. - Timeout (in tenth of seconds) is the value after which the previous content is restored i.e. if there was already something from a previous pipe it will be put on display again, overwriting the current status. - A pipe with Timeout of 0 will be displayed permanently, just like =PipeReader= - The boolean option indicates whether new data for this pipe should make xmobar appear (unhide, reveal). In this case, the Timeout additionally specifies when the window should be hidden again. The output is restored in any case. - Use it for OSD-like status bars e.g. for setting the volume or brightness: #+begin_src haskell Run BufferedPipeReader "bpr" [ ( 0, False, "/tmp/xmobar_window" ) , ( 15, True, "/tmp/xmobar_status" ) ] #+end_src Have your window manager send window titles to =/tmp/xmobar_window=. They will always be shown and not reveal your xmobar. Sending some status information to =/tmp/xmobar_status= will reveal xmonad for 1.5 seconds and temporarily overwrite the window titles. - Take a look at [[../examples/status.sh][examples/status.sh]] - Expands environment variables for the pipe path * Handle-based Logging ** =HandleReader Handle Alias= - Display data from a Haskell =Handle= - This plugin is only useful if you are running xmobar from another Haskell program like XMonad. - You can use =System.Process.createPipe= to create a pair of =read= & =write= Handles. Pass the =read= Handle to HandleReader and write your output to the =write= Handle: #+begin_src haskell (readHandle, writeHandle) <- createPipe xmobarProcess <- forkProcess $ xmobar myConfig { commands = Run (HandleReader readHandle "handle") : commands myConfig } hPutStr writeHandle "Hello World" #+end_src * Software Transactional Memory When invoking xmobar from other Haskell code it can be easier and more performant to use shared memory. The following plugins leverage =Control.Concurrent.STM= to realize these gains for xmobar. ** =QueueReader (TQueue a) (a -> String) String= - Display data from a Haskell =TQueue a=. - This plugin is only useful if you are running xmobar from another haskell program like xmonad. - You should make an =IO= safe =TQueue a= with =Control.Concurrent.STM.newTQueueIO=. Write to it from the user code with =writeTQueue=, and read with =readTQueue=. A common use is to overwite =ppOutput= from =XMonad.Hooks.DynamicLog= as shown below. #+begin_src haskell main :: IO () main = do initThreads q <- STM.newTQueueIO @String bar <- forkOS $ xmobar myConf { commands = Run (QueueReader q id "XMonadLog") : commands myConf } xmonad $ def { logHook = logWorkspacesToQueue q } logWorkspacesToQueue :: STM.TQueue String -> X () logWorkspacesToQueue q = dynamicLogWithPP def { ppOutput = STM.atomically . STM.writeTQueue q } #+end_src Note that xmonad uses blocking Xlib calls in its event loop and isn't normally compiled with [[https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-concurrent.html][the threaded RTS]] so an xmobar thread running inside xmonad will suffer from delayed updates. It is thus necessary to enable =-threaded= when compiling xmonad configuration (=xmonad.hs=), e.g. by using a custom =~/.xmonad/build= script. * Example of using the DBus IPC interface with XMonad Bind the key which should {,un}map xmobar to a dummy value. This is necessary for {,un}grabKey in xmonad. #+begin_src haskell ((0, xK_Alt_L), pure ()) #+end_src Also, install =avoidStruts= layout modifier from =XMonad.Hooks.ManageDocks= Finally, install these two event hooks (=handleEventHook= in =XConfig=) =myDocksEventHook= is a replacement for =docksEventHook= which reacts on unmap events as well (which =docksEventHook= doesn't). #+begin_src haskell import qualified XMonad.Util.ExtensibleState as XS data DockToggleTime = DTT { lastTime :: Time } deriving (Eq, Show, Typeable) instance ExtensionClass DockToggleTime where initialValue = DTT 0 toggleDocksHook :: Int -> KeySym -> Event -> X All toggleDocksHook to ks ( KeyEvent { ev_event_display = d , ev_event_type = et , ev_keycode = ekc , ev_time = etime } ) = io (keysymToKeycode d ks) >>= toggleDocks >> return (All True) where toggleDocks kc | ekc == kc && et == keyPress = do safeSendSignal ["Reveal 0", "TogglePersistent"] XS.put ( DTT etime ) | ekc == kc && et == keyRelease = do gap <- XS.gets ( (-) etime . lastTime ) safeSendSignal [ "TogglePersistent" , "Hide " ++ show (if gap < 400 then to else 0) ] | otherwise = return () safeSendSignal s = catchX (io $ sendSignal s) (return ()) sendSignal = withSession . callSignal withSession mc = connectSession >>= \c -> callNoReply c mc >> disconnect c callSignal :: [String] -> MethodCall callSignal s = ( methodCall ( objectPath_ "/org/Xmobar/Control" ) ( interfaceName_ "org.Xmobar.Control" ) ( memberName_ "SendSignal" ) ) { methodCallDestination = Just $ busName_ "org.Xmobar.Control" , methodCallBody = map toVariant s } toggleDocksHook _ _ _ = return (All True) myDocksEventHook :: Event -> X All myDocksEventHook e = do when (et == mapNotify || et == unmapNotify) $ whenX ((not `fmap` (isClient w)) <&&> runQuery checkDock w) refresh return (All True) where w = ev_window e et = ev_event_type e #+end_src