ENH make xmobar check for dbus services before starting

This commit is contained in:
Nathan Dwarshuis 2021-06-21 23:41:57 -04:00
parent d5d01308c2
commit 688d6ff405
10 changed files with 312 additions and 136 deletions

View File

@ -11,8 +11,12 @@ module Main (main) where
-- * Theme integration with xmonad (shared module imported below)
-- * A custom Locks plugin from my own forked repo
import Control.Monad (filterM)
import Data.List
import DBus
import Xmobar.Plugins.Bluetooth
import Xmobar.Plugins.Device
import Xmobar.Plugins.IntelBacklight
@ -21,35 +25,186 @@ import Xmobar.Plugins.VPN
import XMonad (getXMonadDir)
import XMonad.Hooks.DynamicLog (wrap, xmobarColor)
import XMonad.Internal.DBus.Common (xmonadBus)
import XMonad.Internal.DBus.Control (pathExists)
import XMonad.Internal.DBus.IntelBacklight (blPath)
import XMonad.Internal.DBus.Screensaver (ssPath)
import qualified XMonad.Internal.Theme as T
import Xmobar
sep :: String
sep = xmobarColor T.backdropFgColor "" " : "
aSep :: String
aSep = "}{"
lSep :: Char
lSep = '}'
rSep :: Char
rSep = '{'
pSep :: String
pSep = "%"
myTemplate :: String
myTemplate = formatTemplate left right
data BarRegions = BarRegions
{ brLeft :: [CmdSpec]
, brCenter :: [CmdSpec]
, brRight :: [CmdSpec]
} deriving Show
data CmdSpec = CmdSpec
{ csAlias :: String
, csDepends :: Maybe DBusDepends
, csRunnable :: Runnable
} deriving Show
data DBusDepends = DBusDepends
{ ddBus :: BusName
, ddPath :: ObjectPath
, ddSys :: Bool
} deriving Show
sysDepends :: BusName -> ObjectPath -> DBusDepends
sysDepends b p = DBusDepends b p True
sesDepends :: BusName -> ObjectPath -> DBusDepends
sesDepends b p = DBusDepends b p False
concatRegions :: BarRegions -> [CmdSpec]
concatRegions (BarRegions l c r) = l ++ c ++ r
mapRegionsM :: Monad m => ([CmdSpec] -> m [CmdSpec]) -> BarRegions -> m BarRegions
mapRegionsM f (BarRegions l c r) = do
l' <- f l
c' <- f c
r' <- f r
return $ BarRegions l' c' r'
filterSpecs :: [CmdSpec] -> IO [CmdSpec]
filterSpecs = filterM (maybe (return True) exists . csDepends)
where
formatTemplate l r = fmtAliases l ++ aSep ++ fmtAliases r ++ " "
left = [ "UnsafeStdinReader" ]
right = [ "wlp0s20f3wi"
, "enp7s0f1"
, "vpn"
, "bluetooth"
, "alsa:default:Master"
, "battery"
, "intelbacklight"
, "screensaver"
, "locks"
, "date"
exists DBusDepends { ddBus = b, ddPath = p, ddSys = s } = pathExists s b p
myCommands :: BarRegions
myCommands = BarRegions
{ brLeft =
[ CmdSpec
{ csAlias = "UnsafeStdinReader"
, csDepends = Nothing
, csRunnable = Run UnsafeStdinReader
}
]
fmtAliases = intercalate sep . map (wrap pSep pSep)
, brCenter = []
, brRight =
[ CmdSpec
{ csAlias = "wlp0s20f3wi"
, csDepends = Nothing
, csRunnable = Run
$ Wireless "wlp0s20f3"
[ "-t", "<qualityipat><essid>"
, "--"
, "--quality-icon-pattern", "<icon=wifi_%%.xpm/>"
] 5
}
, CmdSpec
{ csAlias = "enp7s0f1"
, csDepends = Just $ sysDepends devBus devPath
, csRunnable = Run
$ Device ("enp7s0f1", "<fn=2>\xf0e8</fn>", T.fgColor, T.backdropFgColor) 5
}
, CmdSpec
{ csAlias = vpnAlias
, csDepends = Just $ sysDepends vpnBus vpnPath
, csRunnable = Run
$ VPN ("<fn=2>\xf023</fn>", T.fgColor, T.backdropFgColor) 5
}
, CmdSpec
{ csAlias = btAlias
, csDepends = Just $ sysDepends btBus btPath
, csRunnable = Run
$ Bluetooth ("<fn=2>\xf293</fn>", T.fgColor, T.backdropFgColor) 5
}
, CmdSpec
{ csAlias = "alsa:default:Master"
, csDepends = Nothing
, csRunnable = Run
$ Alsa "default" "Master"
[ "-t", "<status><volume>%"
, "--"
, "-O", "<fn=1>\xf028</fn>"
, "-o", "<fn=1>\xf026 </fn>"
, "-c", T.fgColor
, "-C", T.fgColor
]
}
, CmdSpec
{ csAlias = "battery"
, csDepends = Nothing
, csRunnable = Run
$ Battery
[ "--template", "<acstatus><left>"
, "--Low", "10"
, "--High", "80"
, "--low", "red"
, "--normal", T.fgColor
, "--high", T.fgColor
, "--"
, "-P"
, "-o" , "<fn=1>\xf0e7</fn>"
, "-O" , "<fn=1>\xf1e6</fn>"
, "-i" , "<fn=1>\xf1e6</fn>"
] 50
}
, CmdSpec
{ csAlias = "intelbacklight"
, csDepends = Just $ sesDepends xmonadBus blPath
, csRunnable = Run $ IntelBacklight "<fn=1>\xf185</fn>"
}
, CmdSpec
{ csAlias = ssAlias
, csDepends = Just $ sesDepends xmonadBus ssPath
, csRunnable = Run
$ Screensaver ("<fn=1>\xf254</fn>", T.fgColor, T.backdropFgColor)
}
, CmdSpec
{ csAlias = "locks"
, csDepends = Nothing
, csRunnable = Run
$ Locks
[ "-N", "<fn=3>\x1f13d</fn>"
, "-n", xmobarColor T.backdropFgColor "" "<fn=3>\x1f13d</fn>"
, "-C", "<fn=3>\x1f132</fn>"
, "-c", xmobarColor T.backdropFgColor "" "<fn=3>\x1f132</fn>"
, "-s", ""
, "-S", ""
, "-d", "<fn=3> </fn>"
]
}
, CmdSpec
{ csAlias = "date"
, csDepends = Nothing
, csRunnable = Run $ Date "%Y-%m-%d %H:%M:%S " "date" 10
}
]
}
fmtSpecs :: [CmdSpec] -> String
fmtSpecs = intercalate sep . fmap go
where
go CmdSpec { csAlias = a } = wrap pSep pSep a
fmtRegions :: BarRegions -> String
fmtRegions BarRegions { brLeft = l, brCenter = c, brRight = r } =
fmtSpecs l ++ [lSep] ++ fmtSpecs c ++ [rSep] ++ fmtSpecs r
barFont :: String
barFont = T.fmtFontXFT T.font
@ -79,8 +234,8 @@ blockFont = T.fmtFontXFT T.font
, T.weight = Just T.Bold
}
config :: String -> Config
config confDir = defaultConfig
config :: BarRegions -> String -> Config
config br confDir = defaultConfig
{ font = barFont
, additionalFonts = [ iconFont, iconFontLarge, blockFont ]
, textOffset = 16
@ -92,8 +247,8 @@ config confDir = defaultConfig
, borderColor = T.bordersColor
, sepChar = pSep
, alignSep = aSep
, template = myTemplate
, alignSep = [lSep, rSep]
, template = fmtRegions br
, lowerOnStart = False
, hideOnStart = False
@ -104,61 +259,11 @@ config confDir = defaultConfig
-- store the icons with the xmonad/xmobar stack project
, iconRoot = confDir ++ "/icons"
, commands =
[ Run $ Alsa "default" "Master"
[ "-t", "<status><volume>%"
, "--"
, "-O", "<fn=1>\xf028</fn>"
, "-o", "<fn=1>\xf026 </fn>"
, "-c", T.fgColor
, "-C", T.fgColor
]
, Run $ Battery [ "--template", "<acstatus><left>"
, "--Low", "10"
, "--High", "80"
, "--low", "red"
, "--normal", T.fgColor
, "--high", T.fgColor
, "--"
, "-P"
, "-o" , "<fn=1>\xf0e7</fn>"
, "-O" , "<fn=1>\xf1e6</fn>"
, "-i" , "<fn=1>\xf1e6</fn>"
] 50
, Run $ IntelBacklight "<fn=1>\xf185</fn>"
, Run $ Wireless "wlp0s20f3"
[ "-t", "<qualityipat><essid>"
, "--"
, "--quality-icon-pattern", "<icon=wifi_%%.xpm/>"
] 5
, Run $ Device
("enp7s0f1", "<fn=2>\xf0e8</fn>", T.fgColor, T.backdropFgColor) 5
, Run $ Locks
[ "-N", "<fn=3>\x1f13d</fn>"
, "-n", xmobarColor T.backdropFgColor "" "<fn=3>\x1f13d</fn>"
, "-C", "<fn=3>\x1f132</fn>"
, "-c", xmobarColor T.backdropFgColor "" "<fn=3>\x1f132</fn>"
, "-s", ""
, "-S", ""
, "-d", "<fn=3> </fn>"
]
, Run $ Date "%Y-%m-%d %H:%M:%S" "date" 10
, Run $ Screensaver ("<fn=1>\xf254</fn>", T.fgColor, T.backdropFgColor)
, Run $ Bluetooth ("<fn=2>\xf293</fn>", T.fgColor, T.backdropFgColor) 5
, Run UnsafeStdinReader
, Run $ VPN ("<fn=2>\xf023</fn>", T.fgColor, T.backdropFgColor) 5
]
, commands = csRunnable <$> concatRegions br
}
main :: IO ()
main = xmobar . config =<< getXMonadDir
main = do
br <- mapRegionsM filterSpecs myCommands
dir <- getXMonadDir
xmobar $ config br dir

View File

@ -1,26 +1,32 @@
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- | Common internal DBus functions
module XMonad.Internal.DBus.Common
( callMethod
, callMethod'
, addMatchCallback
, xmonadBus
) where
import DBus
import DBus.Client
-- TODO export the bus name (org.xmonad)
xmonadBus :: BusName
xmonadBus = busName_ "org.xmonad"
-- | Call a method and return its result if successful
callMethod :: MethodCall -> IO (Maybe [Variant])
callMethod mc = do
client <- connectSession
-- TODO handle clienterrors here
reply <- call client mc { methodCallDestination = Just "org.xmonad" }
-- TODO not all methods warrent that we wait for a reply?
r <- callMethod' client (Just xmonadBus) mc
disconnect client
return r
callMethod' :: Client -> Maybe BusName -> MethodCall -> IO (Maybe [Variant])
callMethod' client bn mc = do
-- TODO handle clienterrors here
reply <- call client mc { methodCallDestination = bn }
-- TODO not all methods warrant that we wait for a reply? (see callNoReply)
return $ case reply of
Left _ -> Nothing
Right ret -> Just $ methodReturnBody ret

View File

@ -1,4 +1,4 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
--------------------------------------------------------------------------------
-- | High-level interface for managing XMonad's DBus
@ -7,18 +7,30 @@ module XMonad.Internal.DBus.Control
( Client
, startXMonadService
, stopXMonadService
, pathExists
, xmonadBus
) where
import Data.Either
import DBus
import DBus.Client
import XMonad.Internal.DBus.Common
import XMonad.Internal.DBus.IntelBacklight
import XMonad.Internal.DBus.Screensaver
import XMonad.Internal.Shell
introspectInterface :: InterfaceName
introspectInterface = interfaceName_ "org.freedesktop.DBus.Introspectable"
introspectMethod :: MemberName
introspectMethod = memberName_ "Introspect"
startXMonadService :: IO (Client, Maybe BacklightControls, MaybeExe SSControls)
startXMonadService = do
client <- connectSession
requestResult <- requestName client "org.xmonad" []
requestResult <- requestName client xmonadBus []
-- TODO if the client is not released on shutdown the owner will be
-- different
if requestResult /= NamePrimaryOwner then do
@ -32,7 +44,14 @@ startXMonadService = do
stopXMonadService :: Client -> IO ()
stopXMonadService client = do
_ <- releaseName client "org.xmonad"
_ <- releaseName client xmonadBus
disconnect client
return ()
pathExists :: Bool -> BusName -> ObjectPath -> IO Bool
pathExists sysbus n p = do
client <- if sysbus then connectSystem else connectSession
r <- call client (methodCall p introspectInterface introspectMethod)
{ methodCallDestination = Just n }
disconnect client
return $ isRight r

View File

@ -1,5 +1,3 @@
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- | DBus module for Intel Backlight control
@ -12,6 +10,7 @@ module XMonad.Internal.DBus.IntelBacklight
, exportIntelBacklight
, matchSignal
, hasBacklight
, blPath
, BacklightControls(..)
) where
@ -149,43 +148,43 @@ hasBacklight = fromRight False <$> hasBacklight'
-- integer and emit a signal with the same brightness value. Additionally, there
-- is one method to get the current brightness.
path :: ObjectPath
path = "/intelbacklight"
blPath :: ObjectPath
blPath = objectPath_ "/intelbacklight"
interface :: InterfaceName
interface = "org.xmonad.Brightness"
interface = interfaceName_ "org.xmonad.Brightness"
memCurrentBrightness :: MemberName
memCurrentBrightness = "CurrentBrightness"
memCurrentBrightness = memberName_ "CurrentBrightness"
memGetBrightness :: MemberName
memGetBrightness = "GetBrightness"
memGetBrightness = memberName_ "GetBrightness"
memMaxBrightness :: MemberName
memMaxBrightness = "MaxBrightness"
memMaxBrightness = memberName_ "MaxBrightness"
memMinBrightness :: MemberName
memMinBrightness = "MinBrightness"
memMinBrightness = memberName_ "MinBrightness"
memIncBrightness :: MemberName
memIncBrightness = "IncBrightness"
memIncBrightness = memberName_ "IncBrightness"
memDecBrightness :: MemberName
memDecBrightness = "DecBrightness"
memDecBrightness = memberName_ "DecBrightness"
brSignal :: Signal
brSignal = signal path interface memCurrentBrightness
brSignal = signal blPath interface memCurrentBrightness
-- { signalDestination = Just "org.xmonad" }
brMatcher :: MatchRule
brMatcher = matchAny
{ matchPath = Just path
{ matchPath = Just blPath
, matchInterface = Just interface
, matchMember = Just memCurrentBrightness
}
callBacklight :: MemberName -> IO ()
callBacklight method = void $ callMethod $ methodCall path interface method
callBacklight method = void $ callMethod $ methodCall blPath interface method
bodyGetBrightness :: [Variant] -> Maybe Brightness
bodyGetBrightness [b] = fromVariant b :: Maybe Brightness
@ -211,7 +210,7 @@ exportIntelBacklight' client = do
maxval <- getMaxRawBrightness -- assume the max value will never change
let stepsize = maxBrightness `div` steps
let emit' = emitBrightness client
export client path defaultInterface
export client blPath defaultInterface
{ interfaceName = interface
, interfaceMethods =
[ autoMethod memMaxBrightness $ emit' =<< setBrightness maxval maxBrightness
@ -245,7 +244,7 @@ callDecBrightness = callBacklight memDecBrightness
callGetBrightness :: IO (Maybe Brightness)
callGetBrightness = do
reply <- callMethod $ methodCall path interface memGetBrightness
reply <- callMethod $ methodCall blPath interface memGetBrightness
return $ reply >>= bodyGetBrightness
matchSignal :: (Maybe Brightness -> IO ()) -> IO SignalHandler

View File

@ -1,5 +1,3 @@
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- | DBus module for X11 screensave/DPMS control
@ -8,6 +6,7 @@ module XMonad.Internal.DBus.Screensaver
, callToggle
, callQuery
, matchSignal
, ssPath
, SSControls(..)
) where
@ -56,27 +55,27 @@ query = do
-- with the new state when called. Define another method to get the current
-- state.
path :: ObjectPath
path = "/screensaver"
ssPath :: ObjectPath
ssPath = objectPath_ "/screensaver"
interface :: InterfaceName
interface = "org.xmonad.Screensaver"
interface = interfaceName_ "org.xmonad.Screensaver"
memState :: MemberName
memState = "State"
memState = memberName_ "State"
memToggle :: MemberName
memToggle = "Toggle"
memToggle = memberName_ "Toggle"
memQuery :: MemberName
memQuery = "Query"
memQuery = memberName_ "Query"
sigCurrentState :: Signal
sigCurrentState = signal path interface memState
sigCurrentState = signal ssPath interface memState
ruleCurrentState :: MatchRule
ruleCurrentState = matchAny
{ matchPath = Just path
{ matchPath = Just ssPath
, matchInterface = Just interface
, matchMember = Just memState
}
@ -103,7 +102,7 @@ exportScreensaver client = do
exportScreensaver' :: Client -> IO SSControls
exportScreensaver' client = do
export client path defaultInterface
export client ssPath defaultInterface
{ interfaceName = interface
, interfaceMethods =
[ autoMethod memToggle $ emitState client =<< toggle
@ -113,11 +112,11 @@ exportScreensaver' client = do
return $ SSControls { ssToggle = callToggle }
callToggle :: IO ()
callToggle = void $ callMethod $ methodCall path interface memToggle
callToggle = void $ callMethod $ methodCall ssPath interface memToggle
callQuery :: IO (Maybe SSState)
callQuery = do
reply <- callMethod $ methodCall path interface memQuery
reply <- callMethod $ methodCall ssPath interface memQuery
return $ reply >>= bodyGetCurrentState
matchSignal :: (Maybe SSState -> IO ()) -> IO SignalHandler

View File

@ -6,7 +6,12 @@
--
-- Use the bluez interface on DBus to check status
module Xmobar.Plugins.Bluetooth (Bluetooth(..)) where
module Xmobar.Plugins.Bluetooth
( Bluetooth(..)
, btAlias
, btBus
, btPath
) where
import DBus
import DBus.Client
@ -19,11 +24,21 @@ data Bluetooth = Bluetooth (String, String, String) Int
callGetPowered :: Client -> IO (Either MethodError Variant)
callGetPowered client =
getProperty client (methodCall "/org/bluez/hci0" "org.bluez.Adapter1" "Powered")
{ methodCallDestination = Just "org.bluez" }
getProperty client (methodCall btPath "org.bluez.Adapter1" "Powered")
{ methodCallDestination = Just btBus }
btBus :: BusName
btBus = "org.bluez"
-- TODO this feels like something that shouldn't be hardcoded
btPath :: ObjectPath
btPath = "/org/bluez/hci0"
btAlias :: String
btAlias = "bluetooth"
instance Exec Bluetooth where
alias (Bluetooth _ _) = "bluetooth"
alias (Bluetooth _ _) = btAlias
rate (Bluetooth _ r) = r
run (Bluetooth (text, colorOn, colorOff) _) = do
client <- connectSystem

View File

@ -1,6 +1,10 @@
{-# LANGUAGE OverloadedStrings #-}
module Xmobar.Plugins.Device where
module Xmobar.Plugins.Device
( Device(..)
, devBus
, devPath
) where
-- TOOD this name can be more general
--------------------------------------------------------------------------------
@ -23,15 +27,18 @@ import Xmobar
data Device = Device (String, String, String, String) Int
deriving (Read, Show)
busName :: BusName
busName = "org.freedesktop.NetworkManager"
devBus :: BusName
devBus = "org.freedesktop.NetworkManager"
devPath :: ObjectPath
devPath = "/org/freedesktop/NetworkManager"
getDevice :: Client -> String -> IO (Maybe ObjectPath)
getDevice client iface = do
let mc = methodCall "/org/freedesktop/NetworkManager"
let mc = methodCall devPath
"org.freedesktop.NetworkManager" "GetDeviceByIpIface"
reply <- call client $ mc { methodCallBody = [toVariant iface]
, methodCallDestination = Just busName
, methodCallDestination = Just devBus
}
return $ case reply of
Left _ -> Nothing
@ -45,7 +52,7 @@ getDeviceConnected client objectPath = do
"org.freedesktop.NetworkManager.Device"
"Ip4Connectivity"
either (const Nothing) (fmap ((> 1) :: Word32 -> Bool) . fromVariant)
<$> getProperty client mc { methodCallDestination = Just busName }
<$> getProperty client mc { methodCallDestination = Just devBus }
instance Exec Device where
alias (Device (iface, _, _, _) _) = iface

View File

@ -6,7 +6,10 @@
-- Use the custom DBus interface exported by the XMonad process so I can react
-- to signals spawned by commands
module Xmobar.Plugins.IntelBacklight (IntelBacklight(..)) where
module Xmobar.Plugins.IntelBacklight
( IntelBacklight(..)
, blAlias
) where
import Control.Concurrent
import Control.Monad
@ -17,8 +20,11 @@ import XMonad.Internal.DBus.IntelBacklight
newtype IntelBacklight = IntelBacklight String deriving (Read, Show)
blAlias :: String
blAlias = "intelbacklight"
instance Exec IntelBacklight where
alias (IntelBacklight _) = "intelbacklight"
alias (IntelBacklight _) = blAlias
start (IntelBacklight icon) cb = do
_ <- matchSignal $ cb . formatBrightness
cb . formatBrightness =<< callGetBrightness

View File

@ -6,7 +6,10 @@
-- Use the custom DBus interface exported by the XMonad process so I can react
-- to signals spawned by commands
module Xmobar.Plugins.Screensaver (Screensaver(..)) where
module Xmobar.Plugins.Screensaver
( Screensaver(..)
, ssAlias
) where
import Control.Concurrent
import Control.Monad
@ -19,8 +22,11 @@ import XMonad.Internal.DBus.Screensaver
newtype Screensaver = Screensaver (String, String, String)
deriving (Read, Show)
ssAlias :: String
ssAlias = "screensaver"
instance Exec Screensaver where
alias (Screensaver _) = "screensaver"
alias (Screensaver _) = ssAlias
start (Screensaver (text, colorOn, colorOff)) cb = do
_ <- matchSignal $ cb . fmtState
cb . fmtState =<< callQuery

View File

@ -6,7 +6,12 @@
--
-- Use the NetworkManger interface on DBus to check status
module Xmobar.Plugins.VPN (VPN(..)) where
module Xmobar.Plugins.VPN
( VPN(..)
, vpnAlias
, vpnBus
, vpnPath
) where
import DBus
import DBus.Client
@ -19,12 +24,21 @@ data VPN = VPN (String, String, String) Int
callConnectionType :: Client -> IO (Either MethodError Variant)
callConnectionType client =
getProperty client (methodCall "/org/freedesktop/NetworkManager"
getProperty client (methodCall vpnPath
"org.freedesktop.NetworkManager" "PrimaryConnectionType")
{ methodCallDestination = Just "org.freedesktop.NetworkManager" }
{ methodCallDestination = Just vpnBus }
vpnBus :: BusName
vpnBus = "org.freedesktop.NetworkManager"
vpnPath :: ObjectPath
vpnPath = "/org/freedesktop/NetworkManager"
vpnAlias :: String
vpnAlias = "vpn"
instance Exec VPN where
alias (VPN _ _) = "vpn"
alias (VPN _ _) = vpnAlias
rate (VPN _ r) = r
run (VPN (text, colorOn, colorOff) _) = do
client <- connectSystem