xmonad-config/bin/xmobar.hs

508 lines
15 KiB
Haskell
Raw Normal View History

{-# LANGUAGE OverloadedStrings #-}
2020-04-01 20:17:47 -04:00
module Main (main) where
--------------------------------------------------------------------------------
-- | Xmobar binary
--
-- Features:
-- * Uses the 'UnsafeStdinReader' to receive the current workspace/layout config
-- from xmonad
-- * FontAwesome and Symbol fonts for icons
-- * Some custom plugins (imported below)
-- * Theme integration with xmonad (shared module imported below)
-- * A custom Locks plugin from my own forked repo
import Control.Monad
2022-07-09 17:44:14 -04:00
import Data.Internal.DBus
import Data.Internal.Dependency
2020-04-01 20:17:47 -04:00
import Data.List
import Data.Maybe
2020-04-01 20:17:47 -04:00
2022-12-26 17:56:55 -05:00
import RIO hiding (hFlush)
import qualified RIO.Text as T
2022-08-01 16:16:08 -04:00
import System.Environment
2021-06-23 23:08:50 -04:00
import System.IO
import System.IO.Error
2020-03-25 18:55:52 -04:00
import Xmobar.Plugins.Bluetooth
import Xmobar.Plugins.ClevoKeyboard
2020-05-28 23:17:17 -04:00
import Xmobar.Plugins.Device
2020-03-25 18:55:52 -04:00
import Xmobar.Plugins.IntelBacklight
import Xmobar.Plugins.Screensaver
import Xmobar.Plugins.VPN
2020-03-15 15:10:25 -04:00
2022-07-09 17:48:07 -04:00
import XMonad.Core hiding (config)
import XMonad.Internal.Command.Desktop
import XMonad.Internal.Command.Power
2021-11-21 17:54:00 -05:00
import XMonad.Internal.DBus.Brightness.ClevoKeyboard
import XMonad.Internal.DBus.Brightness.IntelBacklight
import XMonad.Internal.DBus.Control
import XMonad.Internal.DBus.Screensaver (ssSignalDep)
2022-07-09 17:48:07 -04:00
import XMonad.Internal.Process hiding (CmdSpec)
import qualified XMonad.Internal.Theme as XT
import Xmobar hiding
( iconOffset
2022-08-01 16:16:08 -04:00
, run
)
2021-11-27 17:33:02 -05:00
import Xmobar.Plugins.Common
2020-03-15 13:12:01 -04:00
2022-08-01 16:16:08 -04:00
2021-06-23 23:08:50 -04:00
main :: IO ()
2022-08-01 16:16:08 -04:00
main = getArgs >>= parse
parse :: [String] -> IO ()
parse [] = run
parse ["--deps"] = withCache printDeps
parse ["--test"] = void $ withCache . evalConfig =<< connectDBus
2022-08-01 16:16:08 -04:00
parse _ = usage
run :: IO ()
run = do
db <- connectDBus
c <- withCache $ evalConfig db
disconnectDBus db
-- this is needed to see any printed messages
hFlush stdout
xmobar c
evalConfig :: DBusState -> FIO Config
evalConfig db = do
cs <- getAllCommands <$> rightPlugins db
bf <- getTextFont
(ifs, ios) <- getIconFonts
d <- io $ cfgDir <$> getDirectories
return $ config bf ifs ios cs d
2022-08-01 16:16:08 -04:00
printDeps :: FIO ()
printDeps = do
db <- io connectDBus
2022-12-25 18:07:03 -05:00
let ps = sort $ nub $ fmap showFulfillment $ concatMap dumpFeature $ allFeatures db
io $ mapM_ (putStrLn . T.unpack) ps
2022-08-01 16:16:08 -04:00
io $ disconnectDBus db
usage :: IO ()
usage = putStrLn $ intercalate "\n"
[ "xmobar: run greatest taskbar"
, "xmobar --deps: print dependencies"
]
--------------------------------------------------------------------------------
-- | toplevel configuration
-- | The text font family
textFont :: Always XT.FontBuilder
textFont = fontAlways "XMobar Text Font" "DejaVu Sans Mono" defFontPkgs
-- | Offset of the text in the bar
textFontOffset :: Int
textFontOffset = 16
-- | Attributes for the bar font (size, weight, etc)
textFontData :: XT.FontData
textFontData = XT.defFontData { XT.weight = Just XT.Bold, XT.size = Just 11 }
-- | The icon font family
iconFont :: Sometimes XT.FontBuilder
iconFont = fontSometimes "XMobar Icon Font" "Symbols Nerd Font"
2022-12-25 18:15:53 -05:00
[Package Official "ttf-nerd-fonts-symbols-2048-em"]
-- | Offsets for the icons in the bar (relative to the text offset)
iconOffset :: BarFont -> Int
iconOffset IconSmall = 0
iconOffset IconMedium = 1
iconOffset IconLarge = 1
iconOffset IconXLarge = 2
-- | Sizes (in pixels) for the icon fonts
iconSize :: BarFont -> Int
iconSize IconSmall = 13
iconSize IconMedium = 15
iconSize IconLarge = 18
iconSize IconXLarge = 20
-- | Attributes for icon fonts
iconFontData :: Int -> XT.FontData
iconFontData s = XT.defFontData { XT.pixelsize = Just s, XT.size = Nothing }
-- | Global configuration
-- Note that the 'font' and 'textOffset' are assumed to pertain to one (and
-- only one) text font, and all other fonts are icon fonts. If this assumption
-- changes the code will need to change significantly
config :: T.Text -> [T.Text] -> [Int] -> BarRegions -> FilePath -> Config
config bf ifs ios br confDir = defaultConfig
{ font = T.unpack bf
, additionalFonts = fmap T.unpack ifs
, textOffset = textFontOffset
, textOffsets = ios
, bgColor = T.unpack XT.bgColor
, fgColor = T.unpack XT.fgColor
2021-06-23 23:08:50 -04:00
, position = BottomSize C 100 24
, border = NoBorder
, borderColor = T.unpack XT.bordersColor
, sepChar = T.unpack pSep
2021-06-23 23:08:50 -04:00
, alignSep = [lSep, rSep]
, template = T.unpack $ fmtRegions br
2021-06-23 23:08:50 -04:00
, lowerOnStart = False
, hideOnStart = False
, allDesktops = True
, overrideRedirect = True
, pickBroadest = False
, persistent = True
-- store the icons with the xmonad/xmobar stack project
, iconRoot = confDir ++ "/icons"
, commands = csRunnable <$> concatRegions br
}
--------------------------------------------------------------------------------
-- | plugin features
--
-- some commands depend on the presence of interfaces that can only be
-- determined at runtime; define these checks here
getAllCommands :: [Maybe CmdSpec] -> BarRegions
getAllCommands right = BarRegions
{ brLeft = [ CmdSpec
{ csAlias = "UnsafeStdinReader"
, csRunnable = Run UnsafeStdinReader
}
]
, brCenter = []
, brRight = catMaybes right
}
rightPlugins :: DBusState -> FIO [Maybe CmdSpec]
2022-08-01 16:16:08 -04:00
rightPlugins db = mapM evalFeature $ allFeatures db
++ [always' "date indicator" dateCmd]
where
always' n = Right . Always n . Always_ . FallbackAlone
allFeatures :: DBusState -> [Feature CmdSpec]
allFeatures DBusState { dbSesClient = ses, dbSysClient = sys } =
[ Left getWireless
, Left $ getEthernet sys
, Left $ getVPN sys
, Left $ getBt sys
, Left getAlsa
, Left getBattery
, Left $ getBl ses
, Left $ getCk ses
, Left $ getSs ses
, Right getLock
]
type BarFeature = Sometimes CmdSpec
2022-07-07 01:05:17 -04:00
-- TODO what if I don't have a wireless card?
getWireless :: BarFeature
getWireless = Sometimes "wireless status indicator" xpfWireless
[Subfeature (IORoot wirelessCmd $ Only readWireless) "sysfs path"]
2022-07-09 17:08:10 -04:00
getEthernet :: Maybe SysClient -> BarFeature
getEthernet cl = iconDBus "ethernet status indicator" xpfEthernet root tree
where
root useIcon tree' = DBusRoot (const . ethernetCmd useIcon) tree' cl
tree = And1 (Only readEthernet) (Only_ devDep)
getBattery :: BarFeature
getBattery = iconIO_ "battery level indicator" xpfBattery root tree
where
root useIcon = IORoot_ (batteryCmd useIcon)
tree = Only_ $ IOTest_ "Test if battery is present" []
2022-12-27 19:39:16 -05:00
$ io $ fmap (Msg LevelError) <$> hasBattery
2022-07-09 17:08:10 -04:00
getVPN :: Maybe SysClient -> BarFeature
getVPN cl = iconDBus_ "VPN status indicator" xpfVPN root $ toAnd_ vpnDep test
where
root useIcon tree = DBusRoot_ (const $ vpnCmd useIcon) tree cl
test = DBusIO $ IOTest_ "Use nmcli to test if VPN is present"
2022-12-27 19:39:16 -05:00
networkManagerPkgs $ io vpnPresent
2022-07-09 17:08:10 -04:00
getBt :: Maybe SysClient -> BarFeature
getBt = xmobarDBus "bluetooth status indicator" xpfBluetooth btDep btCmd
getAlsa :: BarFeature
getAlsa = iconIO_ "volume level indicator" (const True) root
$ Only_ $ sysExe [Package Official "alsa-utils"] "alsactl"
where
root useIcon = IORoot_ (alsaCmd useIcon)
2022-07-09 17:08:10 -04:00
getBl :: Maybe SesClient -> BarFeature
getBl = xmobarDBus "Intel backlight indicator" xpfIntelBacklight
intelBacklightSignalDep blCmd
2022-07-09 17:08:10 -04:00
getCk :: Maybe SesClient -> BarFeature
getCk = xmobarDBus "Clevo keyboard indicator" xpfClevoBacklight
clevoKeyboardSignalDep ckCmd
2022-07-09 17:08:10 -04:00
getSs :: Maybe SesClient -> BarFeature
getSs = xmobarDBus "screensaver indicator" (const True) ssSignalDep ssCmd
getLock :: Always CmdSpec
getLock = always1 "lock indicator" "icon indicator" root $ lockCmd fontifyAlt
where
root = IORoot_ (lockCmd fontifyIcon) $ Only_ iconDependency
--------------------------------------------------------------------------------
-- | bar feature constructors
xmobarDBus :: SafeClient c => T.Text -> XPQuery -> DBusDependency_ c
2022-07-09 17:08:10 -04:00
-> (Fontifier -> CmdSpec) -> Maybe c -> BarFeature
xmobarDBus n q dep cmd cl = iconDBus_ n q root (Only_ dep)
where
root useIcon tree = DBusRoot_ (const $ cmd useIcon) tree cl
iconIO_ :: T.Text -> XPQuery -> (Fontifier -> IOTree_ -> Root CmdSpec)
-> IOTree_ -> BarFeature
iconIO_ = iconSometimes' And_ Only_
iconDBus :: SafeClient c => T.Text -> XPQuery
2022-07-09 17:08:10 -04:00
-> (Fontifier -> DBusTree c p -> Root CmdSpec) -> DBusTree c p -> BarFeature
iconDBus = iconSometimes' And1 $ Only_ . DBusIO
iconDBus_ :: SafeClient c => T.Text -> XPQuery
2022-07-09 17:08:10 -04:00
-> (Fontifier -> DBusTree_ c -> Root CmdSpec) -> DBusTree_ c -> BarFeature
iconDBus_ = iconSometimes' And_ $ Only_ . DBusIO
iconSometimes' :: (t -> t_ -> t) -> (IODependency_ -> t_) -> T.Text -> XPQuery
-> (Fontifier -> t -> Root CmdSpec) -> t -> BarFeature
iconSometimes' c d n q r t = Sometimes n q
[ Subfeature icon "icon indicator"
, Subfeature text "text indicator"
]
where
icon = r fontifyIcon $ c t $ d iconDependency
text = r fontifyAlt t
2021-06-23 23:08:50 -04:00
--------------------------------------------------------------------------------
-- | command specifications
data BarRegions = BarRegions
{ brLeft :: [CmdSpec]
, brCenter :: [CmdSpec]
, brRight :: [CmdSpec]
} deriving Show
data CmdSpec = CmdSpec
{ csAlias :: T.Text
, csRunnable :: Runnable
} deriving Show
concatRegions :: BarRegions -> [CmdSpec]
concatRegions (BarRegions l c r) = l ++ c ++ r
wirelessCmd :: T.Text -> CmdSpec
wirelessCmd iface = CmdSpec
{ csAlias = T.append iface "wi"
, csRunnable = Run $ Wireless (T.unpack iface) args 5
}
where
args = fmap T.unpack
[ "-t", "<qualityipat><essid>"
, "--"
, "--quality-icon-pattern", "<icon=wifi_%%.xpm/>"
]
ethernetCmd :: Fontifier -> T.Text -> CmdSpec
ethernetCmd fontify iface = CmdSpec
{ csAlias = iface
, csRunnable = Run
$ Device (iface, fontify IconMedium "\xf0e8" "ETH", colors)
}
batteryCmd :: Fontifier -> CmdSpec
batteryCmd fontify = CmdSpec
{ csAlias = "battery"
, csRunnable = Run $ Battery args 50
}
2021-06-30 23:04:00 -04:00
where
fontify' = fontify IconSmall
args = fmap T.unpack
[ "--template", "<acstatus><left>"
, "--Low", "10"
, "--High", "80"
, "--low", "red"
, "--normal", XT.fgColor
, "--high", XT.fgColor
, "--"
, "-P"
, "-o" , fontify' "\xf0e7" "BAT"
, "-O" , fontify' "\xf1e6" "AC"
, "-i" , fontify' "\xf1e6" "AC"
]
vpnCmd :: Fontifier -> CmdSpec
vpnCmd fontify = CmdSpec
{ csAlias = vpnAlias
, csRunnable = Run $ VPN (fontify IconMedium "\xf023" "VPN", colors)
}
btCmd :: Fontifier -> CmdSpec
btCmd fontify = CmdSpec
2021-06-23 23:08:50 -04:00
{ csAlias = btAlias
, csRunnable = Run
$ Bluetooth (fontify' "\xf5b0" "+", fontify' "\xf5ae" "-") colors
2021-06-23 23:08:50 -04:00
}
2021-11-27 17:33:02 -05:00
where
fontify' i = fontify IconLarge i . T.append "BT"
alsaCmd :: Fontifier -> CmdSpec
alsaCmd fontify = CmdSpec
2021-06-23 23:08:50 -04:00
{ csAlias = "alsa:default:Master"
, csRunnable = Run
$ Alsa "default" "Master"
$ fmap T.unpack
2021-06-23 23:08:50 -04:00
[ "-t", "<status><volume>%"
, "--"
, "-O", fontify' "\xf028" "+"
, "-o", T.append (fontify' "\xf026" "-") " "
, "-c", XT.fgColor
, "-C", XT.fgColor
2021-06-23 23:08:50 -04:00
]
}
where
fontify' i = fontify IconSmall i . T.append "VOL"
blCmd :: Fontifier -> CmdSpec
blCmd fontify = CmdSpec
2021-11-21 00:42:40 -05:00
{ csAlias = blAlias
, csRunnable = Run $ IntelBacklight $ fontify IconSmall "\xf185" "BL: "
2021-06-23 23:08:50 -04:00
}
ckCmd :: Fontifier -> CmdSpec
ckCmd fontify = CmdSpec
{ csAlias = ckAlias
, csRunnable = Run $ ClevoKeyboard $ fontify IconSmall "\xf40b" "KB: "
}
ssCmd :: Fontifier -> CmdSpec
ssCmd fontify = CmdSpec
2021-06-23 23:08:50 -04:00
{ csAlias = ssAlias
, csRunnable = Run $ Screensaver (fontify IconSmall "\xf254" "SS", colors)
2021-06-23 23:08:50 -04:00
}
lockCmd :: Fontifier -> CmdSpec
lockCmd fontify = CmdSpec
2021-06-23 23:08:50 -04:00
{ csAlias = "locks"
, csRunnable = Run
$ Locks
$ fmap T.unpack
2021-11-27 17:33:02 -05:00
[ "-N", numIcon
, "-n", disabledColor numIcon
, "-C", capIcon
, "-c", disabledColor capIcon
2021-06-23 23:08:50 -04:00
, "-s", ""
, "-S", ""
2021-06-30 22:47:49 -04:00
, "-d", " "
2021-06-23 23:08:50 -04:00
]
}
2021-11-27 17:33:02 -05:00
where
numIcon = fontify' "\xf8a5" "N"
capIcon = fontify' "\xf657" "C"
fontify' = fontify IconXLarge
disabledColor = xmobarFGColor XT.backdropFgColor
2021-06-23 23:08:50 -04:00
dateCmd :: CmdSpec
dateCmd = CmdSpec
{ csAlias = "date"
, csRunnable = Run $ Date "%Y-%m-%d %H:%M:%S " "date" 10
}
2021-06-23 23:08:50 -04:00
--------------------------------------------------------------------------------
-- | low-level testing functions
2022-07-07 01:05:17 -04:00
vpnPresent :: IO (Maybe Msg)
2022-07-02 18:22:26 -04:00
vpnPresent =
go <$> tryIOError (readCreateProcessWithExitCode (proc "nmcli" args) "")
where
args = ["-c", "no", "-t", "-f", "TYPE", "c", "show"]
go (Right (ExitSuccess, out, _)) = if "vpn" `elem` lines out then Nothing
2022-12-26 17:56:55 -05:00
else Just $ Msg LevelError "vpn not found"
go (Right (ExitFailure c, _, err)) = Just $ Msg LevelError
$ T.concat ["vpn search exited with code "
2022-12-26 15:18:50 -05:00
, T.pack $ show c
, ": "
, T.pack err]
2022-12-26 17:56:55 -05:00
go (Left e) = Just $ Msg LevelError $ T.pack $ show e
--------------------------------------------------------------------------------
-- | text font
--
-- ASSUME there is only one text font for this entire configuration. This
-- will correspond to the first font/offset parameters in the config record.
getTextFont :: FIO T.Text
getTextFont = do
fb <- evalAlways textFont
return $ fb textFontData
2021-06-23 23:08:50 -04:00
--------------------------------------------------------------------------------
-- | icon fonts
2021-06-23 23:08:50 -04:00
getIconFonts :: FIO ([T.Text], [Int])
getIconFonts = do
fb <- evalSometimes iconFont
return $ maybe ([], []) apply fb
2021-11-21 23:55:19 -05:00
where
apply fb = unzip $ (\i -> (iconString fb i, iconOffset i + textFontOffset))
<$> iconFonts
2021-06-23 23:08:50 -04:00
data BarFont = IconSmall
2021-11-27 17:33:02 -05:00
| IconMedium
| IconLarge
| IconXLarge
deriving (Eq, Enum, Bounded, Show)
2021-06-23 23:08:50 -04:00
iconFonts :: [BarFont]
iconFonts = enumFrom minBound
2022-07-02 17:09:21 -04:00
iconString :: XT.FontBuilder -> BarFont -> T.Text
iconString fb i = fb $ iconFontData $ iconSize i
iconDependency :: IODependency_
iconDependency = IOSometimes_ iconFont
2022-07-02 17:09:21 -04:00
fontifyText :: BarFont -> T.Text -> T.Text
fontifyText fnt txt =
T.concat ["<fn=", T.pack $ show $ 1 + fromEnum fnt, ">", txt, "</fn>"]
type Fontifier = BarFont -> T.Text -> T.Text -> T.Text
fontifyAlt :: Fontifier
fontifyAlt _ _ alt = alt
fontifyIcon :: Fontifier
fontifyIcon f i _ = fontifyText f i
2021-11-27 17:33:02 -05:00
--------------------------------------------------------------------------------
-- | various formatting things
colors :: Colors
colors = Colors { colorsOn = XT.fgColor, colorsOff = XT.backdropFgColor }
2021-11-27 17:33:02 -05:00
sep :: T.Text
sep = xmobarFGColor XT.backdropFgColor " : "
2021-11-27 17:33:02 -05:00
lSep :: Char
lSep = '}'
rSep :: Char
rSep = '{'
2020-03-22 17:17:57 -04:00
pSep :: T.Text
2021-11-27 17:33:02 -05:00
pSep = "%"
fmtSpecs :: [CmdSpec] -> T.Text
fmtSpecs = T.intercalate sep . fmap go
2021-11-27 17:33:02 -05:00
where
go CmdSpec { csAlias = a } = T.concat [pSep, a, pSep]
2021-11-27 17:33:02 -05:00
fmtRegions :: BarRegions -> T.Text
fmtRegions BarRegions { brLeft = l, brCenter = c, brRight = r } = T.concat $
[fmtSpecs l, T.singleton lSep, fmtSpecs c, T.singleton rSep, fmtSpecs r]