Compare commits
98 Commits
fix_proces
...
master
Author | SHA1 | Date |
---|---|---|
Nathan Dwarshuis | 51ebf01786 | |
Nathan Dwarshuis | c35be51dd4 | |
Nathan Dwarshuis | 24430eaeb7 | |
Nathan Dwarshuis | 8a1345ae4b | |
Nathan Dwarshuis | a65cd669dc | |
Nathan Dwarshuis | 3ab6ccf45b | |
Nathan Dwarshuis | 8064b01c90 | |
Nathan Dwarshuis | 80c3d33010 | |
Nathan Dwarshuis | d9b1886db6 | |
Nathan Dwarshuis | 841bf0b5c8 | |
Nathan Dwarshuis | 87eee7a2b9 | |
Nathan Dwarshuis | cc5670f2f1 | |
Nathan Dwarshuis | 171fa489ca | |
Nathan Dwarshuis | 78ba3173c3 | |
Nathan Dwarshuis | 58b68f298c | |
Nathan Dwarshuis | 98e0a2943d | |
Nathan Dwarshuis | 13ddeb3ba7 | |
Nathan Dwarshuis | 700f42d65c | |
Nathan Dwarshuis | 2f6eeb5cdb | |
Nathan Dwarshuis | f814ac9217 | |
Nathan Dwarshuis | 0a4edb6bf2 | |
Nathan Dwarshuis | 250d5c5eed | |
Nathan Dwarshuis | 770f1dc1dd | |
Nathan Dwarshuis | 09909ac779 | |
Nathan Dwarshuis | fe61b0192d | |
Nathan Dwarshuis | 96cb9298d7 | |
Nathan Dwarshuis | 71e86f2233 | |
Nathan Dwarshuis | 7d5a82bd07 | |
Nathan Dwarshuis | 2712ebdf37 | |
Nathan Dwarshuis | 3cc7e02416 | |
Nathan Dwarshuis | 24f0f034f0 | |
Nathan Dwarshuis | 1142732dca | |
Nathan Dwarshuis | 6c3d8c3eaf | |
Nathan Dwarshuis | a61b17502d | |
Nathan Dwarshuis | 0d024ab649 | |
Nathan Dwarshuis | 003b0ce937 | |
Nathan Dwarshuis | a0cdcce146 | |
Nathan Dwarshuis | f95079ba5e | |
Nathan Dwarshuis | f0451891b8 | |
Nathan Dwarshuis | 5b2c66033a | |
Nathan Dwarshuis | 66550a08a6 | |
Nathan Dwarshuis | bfa7f40818 | |
Nathan Dwarshuis | 774fba0c71 | |
Nathan Dwarshuis | 6891238793 | |
Nathan Dwarshuis | 0895586cf7 | |
Nathan Dwarshuis | 12b68f7377 | |
Nathan Dwarshuis | 1cf9e3e8bd | |
Nathan Dwarshuis | 394eca3ad2 | |
Nathan Dwarshuis | adfbb92136 | |
Nathan Dwarshuis | db7011bfd8 | |
Nathan Dwarshuis | 6c23813693 | |
Nathan Dwarshuis | 524818decf | |
Nathan Dwarshuis | 8eb97f3eec | |
Nathan Dwarshuis | c1fef3c4c4 | |
Nathan Dwarshuis | 9ec24b63a0 | |
Nathan Dwarshuis | b64742b925 | |
Nathan Dwarshuis | 27b32fb03e | |
Nathan Dwarshuis | c29a43a024 | |
Nathan Dwarshuis | 097e4e19fc | |
Nathan Dwarshuis | 37f607d817 | |
Nathan Dwarshuis | 9d7ca49357 | |
Nathan Dwarshuis | 69ed4839da | |
Nathan Dwarshuis | cc094bb071 | |
Nathan Dwarshuis | 2948610785 | |
Nathan Dwarshuis | 7432a8f841 | |
Nathan Dwarshuis | 04a7a70747 | |
Nathan Dwarshuis | 6848fbe01f | |
Nathan Dwarshuis | 5912e70526 | |
Nathan Dwarshuis | e0913a461d | |
Nathan Dwarshuis | 76011dc6d6 | |
Nathan Dwarshuis | 1b4480ac3a | |
Nathan Dwarshuis | 17ebd0137f | |
Nathan Dwarshuis | 6b3cfd5857 | |
Nathan Dwarshuis | 00f899ed9a | |
Nathan Dwarshuis | ac743daa32 | |
Nathan Dwarshuis | b2416153e6 | |
Nathan Dwarshuis | e0a186dd18 | |
Nathan Dwarshuis | 2ef652ebe1 | |
Nathan Dwarshuis | 43345f8ce0 | |
Nathan Dwarshuis | 4afaf9af10 | |
Nathan Dwarshuis | 89eacd63aa | |
Nathan Dwarshuis | 335fa7b460 | |
Nathan Dwarshuis | b3f07ba590 | |
Nathan Dwarshuis | dea4ab6585 | |
Nathan Dwarshuis | 0e1b117639 | |
Nathan Dwarshuis | 91ff25a8d2 | |
Nathan Dwarshuis | f875b7c71d | |
Nathan Dwarshuis | 609048f6b6 | |
Nathan Dwarshuis | 4206893967 | |
Nathan Dwarshuis | 745a548baf | |
Nathan Dwarshuis | 8a217d08eb | |
Nathan Dwarshuis | fcb454bc29 | |
Nathan Dwarshuis | 05f1165cc1 | |
Nathan Dwarshuis | 4951c2a35e | |
Nathan Dwarshuis | 8c20a4668d | |
Nathan Dwarshuis | 3b8c6b0f4f | |
Nathan Dwarshuis | a997cac7a3 | |
Nathan Dwarshuis | f6c0596716 |
|
@ -1,6 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
|
||||||
|
|
||||||
-- | Start a VirtualBox instance with a sentinel wrapper process.
|
-- | Start a VirtualBox instance with a sentinel wrapper process.
|
||||||
--
|
--
|
||||||
-- The only reason why this is needed is because I want to manage virtualboxes
|
-- The only reason why this is needed is because I want to manage virtualboxes
|
||||||
|
@ -43,7 +40,7 @@ runAndWait [n] = do
|
||||||
p <- vmPID i
|
p <- vmPID i
|
||||||
liftIO $ mapM_ waitUntilExit p
|
liftIO $ mapM_ waitUntilExit p
|
||||||
err = logError "Could not get machine ID"
|
err = logError "Could not get machine ID"
|
||||||
runAndWait _ = liftIO $ putStrLn "Usage: vbox-start VBOXNAME"
|
runAndWait _ = logInfo "Usage: vbox-start VBOXNAME"
|
||||||
|
|
||||||
vmLaunch :: T.Text -> RIO SimpleApp ()
|
vmLaunch :: T.Text -> RIO SimpleApp ()
|
||||||
vmLaunch i = do
|
vmLaunch i = do
|
||||||
|
|
201
bin/xmobar.hs
201
bin/xmobar.hs
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
-- | Xmobar binary
|
-- | Xmobar binary
|
||||||
--
|
--
|
||||||
-- Features:
|
-- Features:
|
||||||
|
@ -12,16 +10,15 @@
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
|
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
|
import GHC.Enum (enumFrom)
|
||||||
|
import Options.Applicative
|
||||||
import RIO hiding (hFlush)
|
import RIO hiding (hFlush)
|
||||||
import qualified RIO.ByteString.Lazy as BL
|
import RIO.FilePath
|
||||||
import RIO.List
|
import RIO.List
|
||||||
import RIO.Process
|
import qualified RIO.NonEmpty as NE
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import System.IO
|
|
||||||
import UnliftIO.Environment
|
|
||||||
import XMonad.Core hiding (config)
|
import XMonad.Core hiding (config)
|
||||||
import XMonad.Internal.Command.Desktop
|
|
||||||
import XMonad.Internal.Command.Power
|
import XMonad.Internal.Command.Power
|
||||||
import XMonad.Internal.DBus.Brightness.ClevoKeyboard
|
import XMonad.Internal.DBus.Brightness.ClevoKeyboard
|
||||||
import XMonad.Internal.DBus.Brightness.IntelBacklight
|
import XMonad.Internal.DBus.Brightness.IntelBacklight
|
||||||
|
@ -32,33 +29,58 @@ import Xmobar hiding
|
||||||
( iconOffset
|
( iconOffset
|
||||||
, run
|
, run
|
||||||
)
|
)
|
||||||
|
import Xmobar.Plugins.ActiveConnection
|
||||||
import Xmobar.Plugins.Bluetooth
|
import Xmobar.Plugins.Bluetooth
|
||||||
import Xmobar.Plugins.ClevoKeyboard
|
import Xmobar.Plugins.ClevoKeyboard
|
||||||
import Xmobar.Plugins.Common
|
import Xmobar.Plugins.Common
|
||||||
import Xmobar.Plugins.Device
|
|
||||||
import Xmobar.Plugins.IntelBacklight
|
import Xmobar.Plugins.IntelBacklight
|
||||||
import Xmobar.Plugins.Screensaver
|
import Xmobar.Plugins.Screensaver
|
||||||
import Xmobar.Plugins.VPN
|
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = getArgs >>= parse
|
main = parse >>= xio
|
||||||
|
|
||||||
parse :: [String] -> IO ()
|
parse :: IO XOpts
|
||||||
parse [] = run
|
parse = execParser opts
|
||||||
parse ["--deps"] = withCache printDeps
|
where
|
||||||
parse ["--test"] = void $ withCache . evalConfig =<< connectDBus
|
parseOpts = parseDeps <|> parseTest <|> pure XRun
|
||||||
parse _ = usage
|
opts =
|
||||||
|
info (parseOpts <**> helper) $
|
||||||
|
fullDesc <> header "xmobar: the best taskbar ever"
|
||||||
|
|
||||||
run :: IO ()
|
data XOpts = XDeps | XTest | XRun
|
||||||
|
|
||||||
|
parseDeps :: Parser XOpts
|
||||||
|
parseDeps =
|
||||||
|
flag'
|
||||||
|
XDeps
|
||||||
|
(long "deps" <> short 'd' <> help "print dependencies")
|
||||||
|
|
||||||
|
parseTest :: Parser XOpts
|
||||||
|
parseTest =
|
||||||
|
flag'
|
||||||
|
XTest
|
||||||
|
(long "test" <> short 't' <> help "test dependencies without running")
|
||||||
|
|
||||||
|
xio :: XOpts -> IO ()
|
||||||
|
xio o = case o of
|
||||||
|
XDeps -> hRunXIO False stderr printDeps
|
||||||
|
XTest -> hRunXIO False stderr $ withDBus_ Nothing Nothing evalConfig
|
||||||
|
XRun -> runXIO "xmobar.log" run
|
||||||
|
|
||||||
|
run :: XIO ()
|
||||||
run = do
|
run = do
|
||||||
db <- connectDBus
|
-- IDK why this is needed, I thought this was default
|
||||||
c <- withCache $ evalConfig db
|
liftIO $ hSetBuffering stdout LineBuffering
|
||||||
disconnectDBus db
|
-- this isn't totally necessary except for the fact that killing xmobar
|
||||||
-- this is needed to see any printed messages
|
-- will make it print something about catching SIGTERM, and without
|
||||||
hFlush stdout
|
-- linebuffering it usually only prints the first few characters (even then
|
||||||
xmobar c
|
-- it only prints 10-20% of the time)
|
||||||
|
liftIO $ hSetBuffering stderr LineBuffering
|
||||||
|
-- TODO do these dbus things really need to remain connected?
|
||||||
|
c <- withDBus Nothing Nothing evalConfig
|
||||||
|
liftIO $ xmobar c
|
||||||
|
|
||||||
evalConfig :: DBusState -> FIO Config
|
evalConfig :: DBusState -> XIO Config
|
||||||
evalConfig db = do
|
evalConfig db = do
|
||||||
cs <- getAllCommands <$> rightPlugins db
|
cs <- getAllCommands <$> rightPlugins db
|
||||||
bf <- getTextFont
|
bf <- getTextFont
|
||||||
|
@ -66,21 +88,14 @@ evalConfig db = do
|
||||||
d <- io $ cfgDir <$> getDirectories
|
d <- io $ cfgDir <$> getDirectories
|
||||||
return $ config bf ifs ios cs d
|
return $ config bf ifs ios cs d
|
||||||
|
|
||||||
printDeps :: FIO ()
|
printDeps :: XIO ()
|
||||||
printDeps = do
|
printDeps = withDBus_ Nothing Nothing $ \db ->
|
||||||
db <- io connectDBus
|
mapM_ logInfo $
|
||||||
let ps = sort $ nub $ fmap showFulfillment $ concatMap dumpFeature $ allFeatures db
|
fmap showFulfillment $
|
||||||
io $ mapM_ (putStrLn . T.unpack) ps
|
sort $
|
||||||
io $ disconnectDBus db
|
nub $
|
||||||
|
concatMap dumpFeature $
|
||||||
usage :: IO ()
|
allFeatures db
|
||||||
usage =
|
|
||||||
putStrLn $
|
|
||||||
intercalate
|
|
||||||
"\n"
|
|
||||||
[ "xmobar: run greatest taskbar"
|
|
||||||
, "xmobar --deps: print dependencies"
|
|
||||||
]
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- toplevel configuration
|
-- toplevel configuration
|
||||||
|
@ -103,7 +118,7 @@ iconFont =
|
||||||
fontSometimes
|
fontSometimes
|
||||||
"XMobar Icon Font"
|
"XMobar Icon Font"
|
||||||
"Symbols Nerd Font"
|
"Symbols Nerd Font"
|
||||||
[Package Official "ttf-nerd-fonts-symbols-2048-em"]
|
[Package Official "ttf-nerd-fonts-symbols"]
|
||||||
|
|
||||||
-- | Offsets for the icons in the bar (relative to the text offset)
|
-- | Offsets for the icons in the bar (relative to the text offset)
|
||||||
iconOffset :: BarFont -> Int
|
iconOffset :: BarFont -> Int
|
||||||
|
@ -149,7 +164,7 @@ config bf ifs ios br confDir =
|
||||||
, pickBroadest = False
|
, pickBroadest = False
|
||||||
, persistent = True
|
, persistent = True
|
||||||
, -- store the icons with the xmonad/xmobar stack project
|
, -- store the icons with the xmonad/xmobar stack project
|
||||||
iconRoot = confDir ++ "/icons"
|
iconRoot = confDir </> "assets" </> "icons"
|
||||||
, commands = csRunnable <$> concatRegions br
|
, commands = csRunnable <$> concatRegions br
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +187,7 @@ getAllCommands right =
|
||||||
, brRight = catMaybes right
|
, brRight = catMaybes right
|
||||||
}
|
}
|
||||||
|
|
||||||
rightPlugins :: DBusState -> FIO [Maybe CmdSpec]
|
rightPlugins :: DBusState -> XIO [Maybe CmdSpec]
|
||||||
rightPlugins db =
|
rightPlugins db =
|
||||||
mapM evalFeature $
|
mapM evalFeature $
|
||||||
allFeatures db
|
allFeatures db
|
||||||
|
@ -204,11 +219,11 @@ getWireless =
|
||||||
xpfWireless
|
xpfWireless
|
||||||
[Subfeature (IORoot wirelessCmd $ Only readWireless) "sysfs path"]
|
[Subfeature (IORoot wirelessCmd $ Only readWireless) "sysfs path"]
|
||||||
|
|
||||||
getEthernet :: Maybe SysClient -> BarFeature
|
getEthernet :: Maybe NamedSysConnection -> BarFeature
|
||||||
getEthernet cl = iconDBus "ethernet status indicator" xpfEthernet root tree
|
getEthernet cl = iconDBus_ "ethernet status indicator" xpfEthernet root (Only_ devDep)
|
||||||
where
|
where
|
||||||
root useIcon tree' = DBusRoot (const . ethernetCmd useIcon) tree' cl
|
root useIcon tree' =
|
||||||
tree = And1 (Only readEthernet) (Only_ devDep)
|
DBusRoot_ (const $ ethernetCmd useIcon) tree' cl
|
||||||
|
|
||||||
getBattery :: BarFeature
|
getBattery :: BarFeature
|
||||||
getBattery = iconIO_ "battery level indicator" xpfBattery root tree
|
getBattery = iconIO_ "battery level indicator" xpfBattery root tree
|
||||||
|
@ -220,18 +235,12 @@ getBattery = iconIO_ "battery level indicator" xpfBattery root tree
|
||||||
io $
|
io $
|
||||||
fmap (Msg LevelError) <$> hasBattery
|
fmap (Msg LevelError) <$> hasBattery
|
||||||
|
|
||||||
getVPN :: Maybe SysClient -> BarFeature
|
getVPN :: Maybe NamedSysConnection -> BarFeature
|
||||||
getVPN cl = iconDBus_ "VPN status indicator" xpfVPN root $ toAnd_ vpnDep test
|
getVPN cl = iconDBus_ "VPN status indicator" xpfVPN root (Only_ devDep)
|
||||||
where
|
where
|
||||||
root useIcon tree = DBusRoot_ (const $ vpnCmd useIcon) tree cl
|
root useIcon tree = DBusRoot_ (const $ vpnCmd useIcon) tree cl
|
||||||
test =
|
|
||||||
DBusIO $
|
|
||||||
IOTest_
|
|
||||||
"Use nmcli to test if VPN is present"
|
|
||||||
networkManagerPkgs
|
|
||||||
vpnPresent
|
|
||||||
|
|
||||||
getBt :: Maybe SysClient -> BarFeature
|
getBt :: Maybe NamedSysConnection -> BarFeature
|
||||||
getBt = xmobarDBus "bluetooth status indicator" xpfBluetooth btDep btCmd
|
getBt = xmobarDBus "bluetooth status indicator" xpfBluetooth btDep btCmd
|
||||||
|
|
||||||
getAlsa :: BarFeature
|
getAlsa :: BarFeature
|
||||||
|
@ -242,7 +251,7 @@ getAlsa =
|
||||||
where
|
where
|
||||||
root useIcon = IORoot_ (alsaCmd useIcon)
|
root useIcon = IORoot_ (alsaCmd useIcon)
|
||||||
|
|
||||||
getBl :: Maybe SesClient -> BarFeature
|
getBl :: Maybe NamedSesConnection -> BarFeature
|
||||||
getBl =
|
getBl =
|
||||||
xmobarDBus
|
xmobarDBus
|
||||||
"Intel backlight indicator"
|
"Intel backlight indicator"
|
||||||
|
@ -250,7 +259,7 @@ getBl =
|
||||||
intelBacklightSignalDep
|
intelBacklightSignalDep
|
||||||
blCmd
|
blCmd
|
||||||
|
|
||||||
getCk :: Maybe SesClient -> BarFeature
|
getCk :: Maybe NamedSesConnection -> BarFeature
|
||||||
getCk =
|
getCk =
|
||||||
xmobarDBus
|
xmobarDBus
|
||||||
"Clevo keyboard indicator"
|
"Clevo keyboard indicator"
|
||||||
|
@ -258,7 +267,7 @@ getCk =
|
||||||
clevoKeyboardSignalDep
|
clevoKeyboardSignalDep
|
||||||
ckCmd
|
ckCmd
|
||||||
|
|
||||||
getSs :: Maybe SesClient -> BarFeature
|
getSs :: Maybe NamedSesConnection -> BarFeature
|
||||||
getSs = xmobarDBus "screensaver indicator" (const True) ssSignalDep ssCmd
|
getSs = xmobarDBus "screensaver indicator" (const True) ssSignalDep ssCmd
|
||||||
|
|
||||||
getLock :: Always CmdSpec
|
getLock :: Always CmdSpec
|
||||||
|
@ -275,7 +284,7 @@ xmobarDBus
|
||||||
-> XPQuery
|
-> XPQuery
|
||||||
-> DBusDependency_ c
|
-> DBusDependency_ c
|
||||||
-> (Fontifier -> CmdSpec)
|
-> (Fontifier -> CmdSpec)
|
||||||
-> Maybe c
|
-> Maybe (NamedConnection c)
|
||||||
-> BarFeature
|
-> BarFeature
|
||||||
xmobarDBus n q dep cmd cl = iconDBus_ n q root (Only_ dep)
|
xmobarDBus n q dep cmd cl = iconDBus_ n q root (Only_ dep)
|
||||||
where
|
where
|
||||||
|
@ -289,18 +298,16 @@ iconIO_
|
||||||
-> BarFeature
|
-> BarFeature
|
||||||
iconIO_ = iconSometimes' And_ Only_
|
iconIO_ = iconSometimes' And_ Only_
|
||||||
|
|
||||||
iconDBus
|
-- iconDBus
|
||||||
:: SafeClient c
|
-- :: T.Text
|
||||||
=> T.Text
|
-- -> XPQuery
|
||||||
-> XPQuery
|
-- -> (Fontifier -> DBusTree c p -> Root CmdSpec)
|
||||||
-> (Fontifier -> DBusTree c p -> Root CmdSpec)
|
-- -> DBusTree c p
|
||||||
-> DBusTree c p
|
-- -> BarFeature
|
||||||
-> BarFeature
|
-- iconDBus = iconSometimes' And1 $ Only_ . DBusIO
|
||||||
iconDBus = iconSometimes' And1 $ Only_ . DBusIO
|
|
||||||
|
|
||||||
iconDBus_
|
iconDBus_
|
||||||
:: SafeClient c
|
:: T.Text
|
||||||
=> T.Text
|
|
||||||
-> XPQuery
|
-> XPQuery
|
||||||
-> (Fontifier -> DBusTree_ c -> Root CmdSpec)
|
-> (Fontifier -> DBusTree_ c -> Root CmdSpec)
|
||||||
-> DBusTree_ c
|
-> DBusTree_ c
|
||||||
|
@ -362,13 +369,19 @@ wirelessCmd iface =
|
||||||
, "<icon=wifi_%%.xpm/>"
|
, "<icon=wifi_%%.xpm/>"
|
||||||
]
|
]
|
||||||
|
|
||||||
ethernetCmd :: Fontifier -> T.Text -> CmdSpec
|
ethernetCmd :: Fontifier -> CmdSpec
|
||||||
ethernetCmd fontify iface =
|
ethernetCmd = connCmd "\xf0e8" "ETH" ("vlan" :| ["802-3-ethernet"])
|
||||||
|
|
||||||
|
vpnCmd :: Fontifier -> CmdSpec
|
||||||
|
vpnCmd = connCmd "\xf023" "VPN" ("tun" :| [])
|
||||||
|
|
||||||
|
connCmd :: T.Text -> T.Text -> NE.NonEmpty T.Text -> Fontifier -> CmdSpec
|
||||||
|
connCmd icon abbr contypes fontify =
|
||||||
CmdSpec
|
CmdSpec
|
||||||
{ csAlias = iface
|
{ csAlias = connAlias contypes
|
||||||
, csRunnable =
|
, csRunnable =
|
||||||
Run $
|
Run $
|
||||||
Device (iface, fontify IconMedium "\xf0e8" "ETH", colors)
|
ActiveConnection (contypes, fontify IconMedium icon abbr, colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
batteryCmd :: Fontifier -> CmdSpec
|
batteryCmd :: Fontifier -> CmdSpec
|
||||||
|
@ -404,20 +417,13 @@ batteryCmd fontify =
|
||||||
, fontify' "\xf1e6" "AC"
|
, fontify' "\xf1e6" "AC"
|
||||||
]
|
]
|
||||||
|
|
||||||
vpnCmd :: Fontifier -> CmdSpec
|
|
||||||
vpnCmd fontify =
|
|
||||||
CmdSpec
|
|
||||||
{ csAlias = vpnAlias
|
|
||||||
, csRunnable = Run $ VPN (fontify IconMedium "\xf023" "VPN", colors)
|
|
||||||
}
|
|
||||||
|
|
||||||
btCmd :: Fontifier -> CmdSpec
|
btCmd :: Fontifier -> CmdSpec
|
||||||
btCmd fontify =
|
btCmd fontify =
|
||||||
CmdSpec
|
CmdSpec
|
||||||
{ csAlias = btAlias
|
{ csAlias = btAlias
|
||||||
, csRunnable =
|
, csRunnable =
|
||||||
Run $
|
Run $
|
||||||
Bluetooth (fontify' "\xf5b0" "+", fontify' "\xf5ae" "-") colors
|
Bluetooth (fontify' "\x0f00b1" "+", fontify' "\x0f00af" "-") colors
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
fontify' i = fontify IconLarge i . T.append "BT"
|
fontify' i = fontify IconLarge i . T.append "BT"
|
||||||
|
@ -458,7 +464,7 @@ ckCmd :: Fontifier -> CmdSpec
|
||||||
ckCmd fontify =
|
ckCmd fontify =
|
||||||
CmdSpec
|
CmdSpec
|
||||||
{ csAlias = ckAlias
|
{ csAlias = ckAlias
|
||||||
, csRunnable = Run $ ClevoKeyboard $ fontify IconSmall "\xf40b" "KB: "
|
, csRunnable = Run $ ClevoKeyboard $ fontify IconSmall "\xf11c" "KB: "
|
||||||
}
|
}
|
||||||
|
|
||||||
ssCmd :: Fontifier -> CmdSpec
|
ssCmd :: Fontifier -> CmdSpec
|
||||||
|
@ -494,8 +500,8 @@ lockCmd fontify =
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
numIcon = fontify' "\xf8a5" "N"
|
numIcon = fontify' "\x0f03a6" "N"
|
||||||
capIcon = fontify' "\xf657" "C"
|
capIcon = fontify' "\x0f0bf1" "C"
|
||||||
fontify' = fontify IconXLarge
|
fontify' = fontify IconXLarge
|
||||||
disabledColor = xmobarFGColor XT.backdropFgColor
|
disabledColor = xmobarFGColor XT.backdropFgColor
|
||||||
|
|
||||||
|
@ -506,36 +512,13 @@ dateCmd =
|
||||||
, csRunnable = Run $ Date "%Y-%m-%d %H:%M:%S " "date" 10
|
, csRunnable = Run $ Date "%Y-%m-%d %H:%M:%S " "date" 10
|
||||||
}
|
}
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- low-level testing functions
|
|
||||||
|
|
||||||
vpnPresent :: FIO (Maybe Msg)
|
|
||||||
vpnPresent = do
|
|
||||||
res <- proc "nmcli" args readProcess
|
|
||||||
return $ case res of
|
|
||||||
(ExitSuccess, out, _)
|
|
||||||
| "vpn" `elem` BL.split 10 out -> Nothing
|
|
||||||
| otherwise -> Just $ Msg LevelError "vpn not found"
|
|
||||||
(ExitFailure c, _, err) ->
|
|
||||||
Just $
|
|
||||||
Msg LevelError $
|
|
||||||
T.concat
|
|
||||||
[ "vpn search exited with code "
|
|
||||||
, T.pack $ show c
|
|
||||||
, ": "
|
|
||||||
, T.decodeUtf8With T.lenientDecode $
|
|
||||||
BL.toStrict err
|
|
||||||
]
|
|
||||||
where
|
|
||||||
args = ["-c", "no", "-t", "-f", "TYPE", "c", "show"]
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- text font
|
-- text font
|
||||||
--
|
--
|
||||||
-- ASSUME there is only one text font for this entire configuration. This
|
-- ASSUME there is only one text font for this entire configuration. This
|
||||||
-- will correspond to the first font/offset parameters in the config record.
|
-- will correspond to the first font/offset parameters in the config record.
|
||||||
|
|
||||||
getTextFont :: FIO T.Text
|
getTextFont :: XIO T.Text
|
||||||
getTextFont = do
|
getTextFont = do
|
||||||
fb <- evalAlways textFont
|
fb <- evalAlways textFont
|
||||||
return $ fb textFontData
|
return $ fb textFontData
|
||||||
|
@ -543,7 +526,7 @@ getTextFont = do
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- icon fonts
|
-- icon fonts
|
||||||
|
|
||||||
getIconFonts :: FIO ([T.Text], [Int])
|
getIconFonts :: XIO ([T.Text], [Int])
|
||||||
getIconFonts = do
|
getIconFonts = do
|
||||||
fb <- evalSometimes iconFont
|
fb <- evalSometimes iconFont
|
||||||
return $ maybe ([], []) apply fb
|
return $ maybe ([], []) apply fb
|
||||||
|
|
368
bin/xmonad.hs
368
bin/xmonad.hs
|
@ -1,20 +1,16 @@
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- XMonad binary
|
-- XMonad binary
|
||||||
|
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
|
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
import Data.Text.IO (hPutStrLn)
|
import Data.Text.IO (hPutStrLn)
|
||||||
import Graphics.X11.Types
|
import Graphics.X11.Types
|
||||||
import Graphics.X11.Xlib.Atom
|
import Graphics.X11.Xlib.Atom
|
||||||
import Graphics.X11.Xlib.Extras
|
import Graphics.X11.Xlib.Extras
|
||||||
|
import Options.Applicative hiding (action)
|
||||||
import RIO
|
import RIO
|
||||||
import RIO.Directory
|
import RIO.Directory
|
||||||
import RIO.List
|
import RIO.List
|
||||||
|
@ -25,8 +21,7 @@ import System.Process
|
||||||
( getPid
|
( getPid
|
||||||
, getProcessExitCode
|
, getProcessExitCode
|
||||||
)
|
)
|
||||||
import UnliftIO.Environment
|
import XMonad hiding (display)
|
||||||
import XMonad
|
|
||||||
import XMonad.Actions.CopyWindow
|
import XMonad.Actions.CopyWindow
|
||||||
import XMonad.Actions.CycleWS
|
import XMonad.Actions.CycleWS
|
||||||
import XMonad.Actions.PhysicalScreens
|
import XMonad.Actions.PhysicalScreens
|
||||||
|
@ -50,6 +45,7 @@ import XMonad.Internal.DBus.Removable
|
||||||
import XMonad.Internal.DBus.Screensaver
|
import XMonad.Internal.DBus.Screensaver
|
||||||
import XMonad.Internal.Shell hiding (proc)
|
import XMonad.Internal.Shell hiding (proc)
|
||||||
import qualified XMonad.Internal.Theme as XT
|
import qualified XMonad.Internal.Theme as XT
|
||||||
|
import XMonad.Layout.Decoration
|
||||||
import XMonad.Layout.MultiToggle
|
import XMonad.Layout.MultiToggle
|
||||||
import XMonad.Layout.NoBorders
|
import XMonad.Layout.NoBorders
|
||||||
import XMonad.Layout.NoFrillsDecoration
|
import XMonad.Layout.NoFrillsDecoration
|
||||||
|
@ -65,15 +61,37 @@ import XMonad.Util.NamedActions
|
||||||
import XMonad.Util.WorkspaceCompare
|
import XMonad.Util.WorkspaceCompare
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = getArgs >>= parse
|
main = parse >>= xio
|
||||||
|
|
||||||
parse :: [String] -> IO ()
|
parse :: IO XOpts
|
||||||
parse [] = run
|
parse = execParser opts
|
||||||
parse ["--deps"] = withCache printDeps
|
where
|
||||||
-- parse ["--test"] = void $ withCache . evalConf =<< connectDBusX
|
parseOpts = parseDeps <|> parseTest <|> pure XRun
|
||||||
parse _ = usage
|
opts =
|
||||||
|
info (parseOpts <**> helper) $
|
||||||
|
fullDesc <> header "xmonad: the best window manager ever"
|
||||||
|
|
||||||
run :: IO ()
|
data XOpts = XDeps | XTest | XRun
|
||||||
|
|
||||||
|
parseDeps :: Parser XOpts
|
||||||
|
parseDeps =
|
||||||
|
flag'
|
||||||
|
XDeps
|
||||||
|
(long "deps" <> short 'd' <> help "print dependencies")
|
||||||
|
|
||||||
|
parseTest :: Parser XOpts
|
||||||
|
parseTest =
|
||||||
|
flag'
|
||||||
|
XTest
|
||||||
|
(long "test" <> short 't' <> help "test dependencies without running")
|
||||||
|
|
||||||
|
xio :: XOpts -> IO ()
|
||||||
|
xio o = case o of
|
||||||
|
XDeps -> hRunXIO False stderr printDeps
|
||||||
|
XTest -> undefined
|
||||||
|
XRun -> runXIO "xmonad.log" run
|
||||||
|
|
||||||
|
run :: XIO ()
|
||||||
run = do
|
run = do
|
||||||
-- These first two commands are only significant when xmonad is restarted.
|
-- These first two commands are only significant when xmonad is restarted.
|
||||||
-- The 'launch' function below this will turn off buffering (so flushes are
|
-- The 'launch' function below this will turn off buffering (so flushes are
|
||||||
|
@ -88,31 +106,31 @@ run = do
|
||||||
-- signal handlers to carry over to the top.
|
-- signal handlers to carry over to the top.
|
||||||
uninstallSignalHandlers
|
uninstallSignalHandlers
|
||||||
hSetBuffering stdout LineBuffering
|
hSetBuffering stdout LineBuffering
|
||||||
withCache $ do
|
withDBusX_ $ \db -> do
|
||||||
withDBusX $ \db -> do
|
let fs = features $ dbSysClient db
|
||||||
let sys = dbSysClient db
|
withDBusInterfaces db (fsDBusExporters fs) $ \unexporters -> do
|
||||||
let fs = features sys
|
|
||||||
startDBusInterfaces db fs
|
|
||||||
withXmobar $ \xmobarP -> do
|
withXmobar $ \xmobarP -> do
|
||||||
withChildDaemons fs $ \ds -> do
|
withChildDaemons fs $ \ds -> do
|
||||||
let ts = ThreadState ds (Just xmobarP)
|
let toClean = Cleanup ds (Just xmobarP) unexporters
|
||||||
startRemovableMon db fs
|
void $ executeSometimes $ fsRemovableMon fs $ dbSysClient db
|
||||||
startPowerMon fs
|
void $ async $ void $ executeSometimes $ fsPowerMon fs
|
||||||
dws <- startDynWorkspaces fs
|
dws <- startDynWorkspaces fs
|
||||||
kbs <- filterExternal <$> evalExternal (fsKeys fs ts db)
|
runIO <- askRunInIO
|
||||||
|
let cleanup = runCleanup runIO toClean db
|
||||||
|
kbs <- filterExternal <$> evalExternal (fsKeys fs runIO cleanup db)
|
||||||
sk <- evalAlways $ fsShowKeys fs
|
sk <- evalAlways $ fsShowKeys fs
|
||||||
ha <- evalAlways $ fsACPIHandler fs
|
ha <- evalAlways $ fsACPIHandler fs
|
||||||
tt <- evalAlways $ fsTabbedTheme fs
|
tt <- evalAlways $ fsTabbedTheme fs
|
||||||
let conf =
|
let conf =
|
||||||
ewmh $
|
ewmh $
|
||||||
addKeymap dws sk kbs $
|
addKeymap dws (liftIO . runIO . sk) kbs $
|
||||||
docks $
|
docks $
|
||||||
def
|
def
|
||||||
{ terminal = myTerm
|
{ terminal = myTerm
|
||||||
, modMask = myModMask
|
, modMask = myModMask
|
||||||
, layoutHook = myLayouts tt
|
, layoutHook = myLayouts tt
|
||||||
, manageHook = myManageHook dws
|
, manageHook = myManageHook dws
|
||||||
, handleEventHook = myEventHook ha
|
, handleEventHook = myEventHook runIO ha
|
||||||
, startupHook = myStartupHook
|
, startupHook = myStartupHook
|
||||||
, workspaces = myWorkspaces
|
, workspaces = myWorkspaces
|
||||||
, logHook = myLoghook xmobarP
|
, logHook = myLoghook xmobarP
|
||||||
|
@ -121,32 +139,21 @@ run = do
|
||||||
, normalBorderColor = T.unpack XT.bordersColor
|
, normalBorderColor = T.unpack XT.bordersColor
|
||||||
, focusedBorderColor = T.unpack XT.selectedBordersColor
|
, focusedBorderColor = T.unpack XT.selectedBordersColor
|
||||||
}
|
}
|
||||||
io $ runXMonad conf
|
runXMonad conf
|
||||||
where
|
where
|
||||||
startRemovableMon db fs =
|
|
||||||
void $
|
|
||||||
executeSometimes $
|
|
||||||
fsRemovableMon fs $
|
|
||||||
dbSysClient db
|
|
||||||
startPowerMon fs = void $ async $ void $ executeSometimes $ fsPowerMon fs
|
|
||||||
startDynWorkspaces fs = do
|
startDynWorkspaces fs = do
|
||||||
dws <- catMaybes <$> mapM evalSometimes (fsDynWorkspaces fs)
|
dws <- catMaybes <$> mapM evalSometimes (fsDynWorkspaces fs)
|
||||||
void $ io $ async $ runWorkspaceMon dws
|
void $ async $ runWorkspaceMon dws
|
||||||
return dws
|
return dws
|
||||||
|
|
||||||
runXMonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO ()
|
runXMonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> XIO ()
|
||||||
runXMonad conf = do
|
runXMonad conf = do
|
||||||
dirs <- getCreateDirectories
|
dirs <- getCreateDirectories
|
||||||
launch conf dirs
|
liftIO $ launch conf dirs
|
||||||
|
|
||||||
startDBusInterfaces :: DBusState -> FeatureSet -> FIO ()
|
getCreateDirectories :: XIO Directories
|
||||||
startDBusInterfaces db fs =
|
|
||||||
mapM_ (\f -> executeSometimes $ f $ dbSesClient db) $
|
|
||||||
fsDBusExporters fs
|
|
||||||
|
|
||||||
getCreateDirectories :: IO Directories
|
|
||||||
getCreateDirectories = do
|
getCreateDirectories = do
|
||||||
ds <- getDirectories
|
ds <- liftIO getDirectories
|
||||||
mapM_ (createIfMissing ds) [dataDir, cfgDir, cacheDir]
|
mapM_ (createIfMissing ds) [dataDir, cfgDir, cacheDir]
|
||||||
return ds
|
return ds
|
||||||
where
|
where
|
||||||
|
@ -154,19 +161,19 @@ getCreateDirectories = do
|
||||||
let d = f ds
|
let d = f ds
|
||||||
r <- tryIO $ createDirectoryIfMissing True d
|
r <- tryIO $ createDirectoryIfMissing True d
|
||||||
case r of
|
case r of
|
||||||
(Left e) -> print e
|
(Left e) -> logError $ display e
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
|
|
||||||
data FeatureSet = FeatureSet
|
data FeatureSet = FeatureSet
|
||||||
{ fsKeys :: ThreadState -> DBusState -> [KeyGroup FeatureX]
|
{ fsKeys :: (XIO () -> IO ()) -> X () -> DBusState -> [KeyGroup FeatureX]
|
||||||
, fsDBusExporters :: [Maybe SesClient -> SometimesIO]
|
, fsDBusExporters :: [Maybe NamedSesConnection -> Sometimes (XIO (), XIO ())]
|
||||||
, fsPowerMon :: SometimesIO
|
, fsPowerMon :: SometimesIO
|
||||||
, fsRemovableMon :: Maybe SysClient -> SometimesIO
|
, fsRemovableMon :: Maybe NamedSysConnection -> SometimesIO
|
||||||
, fsDaemons :: [Sometimes (FIO (Process () () ()))]
|
, fsDaemons :: [Sometimes (XIO (Process () () ()))]
|
||||||
, fsACPIHandler :: Always (String -> X ())
|
, fsACPIHandler :: Always (String -> X ())
|
||||||
, fsTabbedTheme :: Always Theme
|
, fsTabbedTheme :: Always Theme
|
||||||
, fsDynWorkspaces :: [Sometimes DynWorkspace]
|
, fsDynWorkspaces :: [Sometimes DynWorkspace]
|
||||||
, fsShowKeys :: Always ([((KeyMask, KeySym), NamedAction)] -> X ())
|
, fsShowKeys :: Always ([((KeyMask, KeySym), NamedAction)] -> XIO ())
|
||||||
}
|
}
|
||||||
|
|
||||||
tabbedFeature :: Always Theme
|
tabbedFeature :: Always Theme
|
||||||
|
@ -176,7 +183,7 @@ tabbedFeature = Always "theme for tabbed windows" $ Option sf fallback
|
||||||
niceTheme = IORoot XT.tabbedTheme $ fontTree XT.defFontFamily defFontPkgs
|
niceTheme = IORoot XT.tabbedTheme $ fontTree XT.defFontFamily defFontPkgs
|
||||||
fallback = Always_ $ FallbackAlone $ XT.tabbedTheme XT.fallbackFont
|
fallback = Always_ $ FallbackAlone $ XT.tabbedTheme XT.fallbackFont
|
||||||
|
|
||||||
features :: Maybe SysClient -> FeatureSet
|
features :: Maybe NamedSysConnection -> FeatureSet
|
||||||
features cl =
|
features cl =
|
||||||
FeatureSet
|
FeatureSet
|
||||||
{ fsKeys = externalBindings
|
{ fsKeys = externalBindings
|
||||||
|
@ -190,8 +197,12 @@ features cl =
|
||||||
, fsDaemons = [runNetAppDaemon cl, runAutolock]
|
, fsDaemons = [runNetAppDaemon cl, runAutolock]
|
||||||
}
|
}
|
||||||
|
|
||||||
startXmobar :: FIO (Process Handle () ())
|
withXmobar :: (Process Handle () () -> XIO a) -> XIO a
|
||||||
|
withXmobar = bracket startXmobar stopXmobar
|
||||||
|
|
||||||
|
startXmobar :: XIO (Process Handle () ())
|
||||||
startXmobar = do
|
startXmobar = do
|
||||||
|
logInfo "starting xmobar child process"
|
||||||
p <- proc "xmobar" [] start
|
p <- proc "xmobar" [] start
|
||||||
io $ hSetBuffering (getStdin p) LineBuffering
|
io $ hSetBuffering (getStdin p) LineBuffering
|
||||||
return p
|
return p
|
||||||
|
@ -201,76 +212,87 @@ startXmobar = do
|
||||||
. setStdin createPipe
|
. setStdin createPipe
|
||||||
. setCreateGroup True
|
. setCreateGroup True
|
||||||
|
|
||||||
startChildDaemons :: FeatureSet -> FIO [Process () () ()]
|
stopXmobar
|
||||||
startChildDaemons fs = catMaybes <$> mapM executeSometimes (fsDaemons fs)
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Process Handle () ()
|
||||||
withDBusX :: (DBusState -> FIO a) -> FIO a
|
-> m ()
|
||||||
withDBusX = bracket (io connectDBusX) cleanup
|
stopXmobar p = do
|
||||||
where
|
|
||||||
cleanup db = do
|
|
||||||
logInfo "unregistering xmonad from DBus"
|
|
||||||
io $ disconnectDBus db
|
|
||||||
|
|
||||||
withChildDaemons :: FeatureSet -> ([Process () () ()] -> FIO a) -> FIO a
|
|
||||||
withChildDaemons fs = bracket (startChildDaemons fs) cleanup
|
|
||||||
where
|
|
||||||
cleanup ps = do
|
|
||||||
logInfo "stopping child processes"
|
|
||||||
mapM_ (io . killNoWait) ps
|
|
||||||
|
|
||||||
withXmobar :: (Process Handle () () -> FIO a) -> FIO a
|
|
||||||
withXmobar = bracket startXmobar cleanup
|
|
||||||
where
|
|
||||||
cleanup p = do
|
|
||||||
logInfo "stopping xmobar child process"
|
logInfo "stopping xmobar child process"
|
||||||
io $ killNoWait p
|
io $ killNoWait p
|
||||||
|
|
||||||
printDeps :: FIO ()
|
withChildDaemons
|
||||||
printDeps = do
|
:: FeatureSet
|
||||||
db <- io connectDBus
|
-> ([(Utf8Builder, Process () () ())] -> XIO a)
|
||||||
(i, f, d) <- allFeatures db
|
-> XIO a
|
||||||
io $
|
withChildDaemons fs = bracket (startChildDaemons fs) stopChildDaemons
|
||||||
mapM_ (putStrLn . T.unpack) $
|
|
||||||
|
startChildDaemons :: FeatureSet -> XIO [(Utf8Builder, Process () () ())]
|
||||||
|
startChildDaemons fs = catMaybes <$> mapM start (fsDaemons fs)
|
||||||
|
where
|
||||||
|
start s@(Sometimes sname _ _) = do
|
||||||
|
let sname_ = Utf8Builder $ encodeUtf8Builder sname
|
||||||
|
res <- executeSometimes s
|
||||||
|
case res of
|
||||||
|
Just p -> do
|
||||||
|
logInfo $ "starting child process: " <> sname_
|
||||||
|
return $ Just (sname_, p)
|
||||||
|
-- don't log anything here since presumably the feature itself will log
|
||||||
|
-- an error if it fails during execution
|
||||||
|
_ -> return Nothing
|
||||||
|
|
||||||
|
stopChildDaemons
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> [(Utf8Builder, Process () () ())]
|
||||||
|
-> m ()
|
||||||
|
stopChildDaemons = mapM_ stop
|
||||||
|
where
|
||||||
|
stop (n, p) = do
|
||||||
|
logInfo $ "stopping child process: " <> n
|
||||||
|
liftIO $ killNoWait p
|
||||||
|
|
||||||
|
printDeps :: XIO ()
|
||||||
|
printDeps = withDBus_ Nothing Nothing $ \db -> do
|
||||||
|
runIO <- askRunInIO
|
||||||
|
let mockCleanup = runCleanup runIO mockClean db
|
||||||
|
let bfs =
|
||||||
|
concatMap (fmap kbMaybeAction . kgBindings) $
|
||||||
|
externalBindings runIO mockCleanup db
|
||||||
|
let dbus =
|
||||||
|
fmap (\f -> f $ dbSesClient db) dbusExporters
|
||||||
|
:: [Sometimes (XIO (), XIO ())]
|
||||||
|
let others = [runRemovableMon $ dbSysClient db, runPowermon]
|
||||||
|
-- TODO might be better to use glog for this?
|
||||||
|
mapM_ logInfo $
|
||||||
fmap showFulfillment $
|
fmap showFulfillment $
|
||||||
sort $
|
sort $
|
||||||
nub $
|
nub $
|
||||||
concat $
|
concat $
|
||||||
fmap dumpFeature f ++ fmap dumpSometimes i ++ fmap dumpSometimes d
|
fmap dumpSometimes dbus
|
||||||
io $ disconnectDBus db
|
++ fmap dumpSometimes others
|
||||||
|
++ fmap dumpSometimes allDWs'
|
||||||
allFeatures :: DBusState -> FIO ([SometimesIO], [FeatureX], [Sometimes DynWorkspace])
|
++ fmap dumpFeature bfs
|
||||||
allFeatures db = do
|
|
||||||
let bfs =
|
|
||||||
concatMap (fmap kbMaybeAction . kgBindings) $
|
|
||||||
externalBindings ts db
|
|
||||||
let dbus = fmap (\f -> f $ dbSesClient db) dbusExporters
|
|
||||||
let others = [runRemovableMon $ dbSysClient db, runPowermon]
|
|
||||||
return (dbus ++ others, Left runScreenLock : bfs, allDWs')
|
|
||||||
where
|
where
|
||||||
ts = ThreadState {tsChildPIDs = [], tsXmobar = Nothing}
|
mockClean = Cleanup {clChildren = [], clXmobar = Nothing, clDBusUnexporters = []}
|
||||||
|
|
||||||
usage :: IO ()
|
|
||||||
usage =
|
|
||||||
putStrLn $
|
|
||||||
intercalate
|
|
||||||
"\n"
|
|
||||||
[ "xmonad: run greatest window manager"
|
|
||||||
, "xmonad --deps: print dependencies"
|
|
||||||
]
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Concurrency configuration
|
-- Concurrency configuration
|
||||||
|
|
||||||
data ThreadState = ThreadState
|
data Cleanup = Cleanup
|
||||||
{ tsChildPIDs :: [Process () () ()]
|
{ clChildren :: [(Utf8Builder, Process () () ())]
|
||||||
, tsXmobar :: Maybe (Process Handle () ())
|
, clXmobar :: Maybe (Process Handle () ())
|
||||||
|
, clDBusUnexporters :: [XIO ()]
|
||||||
}
|
}
|
||||||
|
|
||||||
runCleanup :: ThreadState -> DBusState -> X ()
|
runCleanup
|
||||||
runCleanup ts db = io $ do
|
:: (XIO () -> IO ())
|
||||||
mapM_ killNoWait $ tsXmobar ts
|
-> Cleanup
|
||||||
mapM_ killNoWait $ tsChildPIDs ts
|
-> DBusState
|
||||||
disconnectDBusX db
|
-> X ()
|
||||||
|
runCleanup runIO ts db = liftIO $ runIO $ do
|
||||||
|
mapM_ stopXmobar $ clXmobar ts
|
||||||
|
stopChildDaemons $ clChildren ts
|
||||||
|
sequence_ $ clDBusUnexporters ts
|
||||||
|
disconnectDBus db
|
||||||
|
|
||||||
-- | Kill a process (group) after xmonad has already started
|
-- | Kill a process (group) after xmonad has already started
|
||||||
-- This is necessary (as opposed to 'stopProcess' from rio) because a) xmonad
|
-- This is necessary (as opposed to 'stopProcess' from rio) because a) xmonad
|
||||||
|
@ -323,9 +345,6 @@ vmTag = "VM"
|
||||||
xsaneTag :: String
|
xsaneTag :: String
|
||||||
xsaneTag = "XSANE"
|
xsaneTag = "XSANE"
|
||||||
|
|
||||||
f5Tag :: String
|
|
||||||
f5Tag = "F5VPN"
|
|
||||||
|
|
||||||
gimpDynamicWorkspace :: Sometimes DynWorkspace
|
gimpDynamicWorkspace :: Sometimes DynWorkspace
|
||||||
gimpDynamicWorkspace = sometimesIO_ "gimp workspace" "gimp" tree dw
|
gimpDynamicWorkspace = sometimesIO_ "gimp workspace" "gimp" tree dw
|
||||||
where
|
where
|
||||||
|
@ -398,31 +417,11 @@ xsaneDynamicWorkspace =
|
||||||
}
|
}
|
||||||
c = "Xsane"
|
c = "Xsane"
|
||||||
|
|
||||||
f5vpnDynamicWorkspace :: Sometimes DynWorkspace
|
|
||||||
f5vpnDynamicWorkspace =
|
|
||||||
Sometimes
|
|
||||||
"F5 VPN workspace"
|
|
||||||
xpfF5VPN
|
|
||||||
[Subfeature (IORoot_ dw tree) "f5vpn"]
|
|
||||||
where
|
|
||||||
tree = Only_ $ sysExe [Package AUR "f5vpn"] "f5vpn"
|
|
||||||
dw =
|
|
||||||
DynWorkspace
|
|
||||||
{ dwName = "F5Vpn"
|
|
||||||
, dwTag = f5Tag
|
|
||||||
, dwClass = c
|
|
||||||
, dwHook = [className =? c -?> appendShift f5Tag]
|
|
||||||
, dwKey = 'i'
|
|
||||||
, dwCmd = Just skip
|
|
||||||
}
|
|
||||||
c = "F5 VPN"
|
|
||||||
|
|
||||||
allDWs' :: [Sometimes DynWorkspace]
|
allDWs' :: [Sometimes DynWorkspace]
|
||||||
allDWs' =
|
allDWs' =
|
||||||
[ xsaneDynamicWorkspace
|
[ xsaneDynamicWorkspace
|
||||||
, vmDynamicWorkspace
|
, vmDynamicWorkspace
|
||||||
, gimpDynamicWorkspace
|
, gimpDynamicWorkspace
|
||||||
, f5vpnDynamicWorkspace
|
|
||||||
]
|
]
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
@ -437,6 +436,10 @@ myLayouts tt =
|
||||||
mkToggle (single HIDE) $
|
mkToggle (single HIDE) $
|
||||||
tall ||| fulltab ||| full
|
tall ||| fulltab ||| full
|
||||||
where
|
where
|
||||||
|
addTopBar
|
||||||
|
:: (Eq a)
|
||||||
|
=> l a
|
||||||
|
-> ModifiedLayout (Decoration NoFrillsDecoration DefaultShrinker) l a
|
||||||
addTopBar = noFrillsDeco shrinkText tt
|
addTopBar = noFrillsDeco shrinkText tt
|
||||||
tall =
|
tall =
|
||||||
renamed [Replace "Tall"] $
|
renamed [Replace "Tall"] $
|
||||||
|
@ -619,20 +622,30 @@ manageApps dws =
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Eventhook configuration
|
-- Eventhook configuration
|
||||||
|
|
||||||
myEventHook :: (String -> X ()) -> Event -> X All
|
myEventHook
|
||||||
myEventHook handler = xMsgEventHook handler <+> handleEventHook def
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> (m () -> IO ())
|
||||||
|
-> (String -> X ())
|
||||||
|
-> Event
|
||||||
|
-> X All
|
||||||
|
myEventHook runIO handler = xMsgEventHook runIO handler <+> handleEventHook def
|
||||||
|
|
||||||
-- | React to ClientMessage events from concurrent threads
|
-- | React to ClientMessage events from concurrent threads
|
||||||
xMsgEventHook :: (String -> X ()) -> Event -> X All
|
xMsgEventHook
|
||||||
xMsgEventHook handler ClientMessageEvent {ev_message_type = t, ev_data = d}
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> (m () -> IO ())
|
||||||
|
-> (String -> X ())
|
||||||
|
-> Event
|
||||||
|
-> X All
|
||||||
|
xMsgEventHook runIO handler ClientMessageEvent {ev_message_type = t, ev_data = d}
|
||||||
| t == bITMAP = do
|
| t == bITMAP = do
|
||||||
let (xtype, tag) = splitXMsg d
|
let (xtype, tag) = splitXMsg d
|
||||||
case xtype of
|
case xtype of
|
||||||
Workspace -> removeDynamicWorkspace tag
|
Workspace -> removeDynamicWorkspace tag
|
||||||
ACPI -> handler tag
|
ACPI -> handler tag
|
||||||
Unknown -> io $ putStrLn "WARNING: unknown concurrent message"
|
Unknown -> liftIO $ runIO $ logWarn "unknown concurrent message"
|
||||||
return (All True)
|
return (All True)
|
||||||
xMsgEventHook _ _ = return (All True)
|
xMsgEventHook _ _ _ = return (All True)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Keymap configuration
|
-- Keymap configuration
|
||||||
|
@ -735,13 +748,13 @@ data KeyGroup a = KeyGroup
|
||||||
, kgBindings :: [KeyBinding a]
|
, kgBindings :: [KeyBinding a]
|
||||||
}
|
}
|
||||||
|
|
||||||
evalExternal :: [KeyGroup FeatureX] -> FIO [KeyGroup MaybeX]
|
evalExternal :: [KeyGroup FeatureX] -> XIO [KeyGroup MaybeX]
|
||||||
evalExternal = mapM go
|
evalExternal = mapM go
|
||||||
where
|
where
|
||||||
go k@KeyGroup {kgBindings = bs} =
|
go k@KeyGroup {kgBindings = bs} =
|
||||||
(\bs' -> k {kgBindings = bs'}) <$> mapM evalKeyBinding bs
|
(\bs' -> k {kgBindings = bs'}) <$> mapM evalKeyBinding bs
|
||||||
|
|
||||||
evalKeyBinding :: KeyBinding FeatureX -> FIO (KeyBinding MaybeX)
|
evalKeyBinding :: KeyBinding FeatureX -> XIO (KeyBinding MaybeX)
|
||||||
evalKeyBinding k@KeyBinding {kbMaybeAction = a} =
|
evalKeyBinding k@KeyBinding {kbMaybeAction = a} =
|
||||||
(\f -> k {kbMaybeAction = f}) <$> evalFeature a
|
(\f -> k {kbMaybeAction = f}) <$> evalFeature a
|
||||||
|
|
||||||
|
@ -756,52 +769,52 @@ filterExternal = fmap go
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
externalBindings :: ThreadState -> DBusState -> [KeyGroup FeatureX]
|
externalBindings :: (XIO () -> IO ()) -> X () -> DBusState -> [KeyGroup FeatureX]
|
||||||
externalBindings ts db =
|
externalBindings runIO cleanup db =
|
||||||
[ KeyGroup
|
[ KeyGroup
|
||||||
"Launchers"
|
"Launchers"
|
||||||
[ KeyBinding "<XF86Search>" "select/launch app" $ Left runAppMenu
|
[ KeyBinding "<XF86Search>" "select/launch app" $ Left $ toX runAppMenu
|
||||||
, KeyBinding "M-g" "launch clipboard manager" $ Left runClipMenu
|
, KeyBinding "M-g" "launch clipboard manager" $ Left $ toX runClipMenu
|
||||||
, KeyBinding "M-a" "launch network selector" $ Left $ runNetMenu sys
|
, KeyBinding "M-a" "launch network selector" $ Left $ toX $ runNetMenu sys
|
||||||
, KeyBinding "M-w" "launch window selector" $ Left runWinMenu
|
, KeyBinding "M-w" "launch window selector" $ Left $ toX runWinMenu
|
||||||
, KeyBinding "M-u" "launch device selector" $ Left runDevMenu
|
, KeyBinding "M-u" "launch device selector" $ Left $ toX runDevMenu
|
||||||
, KeyBinding "M-b" "launch bitwarden selector" $ Left $ runBwMenu ses
|
, KeyBinding "M-b" "launch bitwarden selector" $ Left $ toX $ runBwMenu ses
|
||||||
, KeyBinding "M-v" "launch ExpressVPN selector" $ Left runVPNMenu
|
, KeyBinding "M-v" "launch ExpressVPN selector" $ Left $ toX runVPNMenu
|
||||||
, KeyBinding "M-e" "launch bluetooth selector" $ Left runBTMenu
|
, KeyBinding "M-e" "launch bluetooth selector" $ Left $ toX runBTMenu
|
||||||
, KeyBinding "M-C-e" "launch editor" $ Left runEditor
|
, KeyBinding "M-C-e" "launch editor" $ Left $ toX runEditor
|
||||||
, KeyBinding "M-C-w" "launch browser" $ Left runBrowser
|
, KeyBinding "M-C-w" "launch browser" $ Left $ toX runBrowser
|
||||||
, KeyBinding "M-C-t" "launch terminal with tmux" $ Left runTMux
|
, KeyBinding "M-C-t" "launch terminal with tmux" $ Left $ toX runTMux
|
||||||
, KeyBinding "M-C-S-t" "launch terminal" $ Left runTerm
|
, KeyBinding "M-C-S-t" "launch terminal" $ Left $ toX runTerm
|
||||||
, KeyBinding "M-C-q" "launch calc" $ Left runCalc
|
, KeyBinding "M-C-q" "launch calc" $ Left $ toX runCalc
|
||||||
, KeyBinding "M-C-f" "launch file manager" $ Left runFileManager
|
, KeyBinding "M-C-f" "launch file manager" $ Left $ toX runFileManager
|
||||||
]
|
]
|
||||||
, KeyGroup
|
, KeyGroup
|
||||||
"Actions"
|
"Actions"
|
||||||
[ KeyBinding "M-q" "close window" $ ftrAlways "kill window function" kill1
|
[ KeyBinding "M-q" "close window" $ ftrAlways "kill window function" kill1
|
||||||
, KeyBinding "M-r" "run program" $ Left runCmdMenu
|
, KeyBinding "M-r" "run program" $ Left $ toX runCmdMenu
|
||||||
, KeyBinding "M-<Space>" "warp pointer" $ ftrAlways "warp function" $ warpToWindow 0.5 0.5
|
, KeyBinding "M-<Space>" "warp pointer" $ ftrAlways "warp function" $ warpToWindow 0.5 0.5
|
||||||
, KeyBinding "M-C-s" "capture area" $ Left $ runAreaCapture ses
|
, KeyBinding "M-C-s" "capture area" $ Left $ toX $ runAreaCapture ses
|
||||||
, KeyBinding "M-C-S-s" "capture screen" $ Left $ runScreenCapture ses
|
, KeyBinding "M-C-S-s" "capture screen" $ Left $ toX $ runScreenCapture ses
|
||||||
, KeyBinding "M-C-d" "capture desktop" $ Left $ runDesktopCapture ses
|
, KeyBinding "M-C-d" "capture desktop" $ Left $ toX $ runDesktopCapture ses
|
||||||
, KeyBinding "M-C-b" "browse captures" $ Left runCaptureBrowser
|
, KeyBinding "M-C-b" "browse captures" $ Left $ toX runCaptureBrowser
|
||||||
-- , ("M-C-S-s", "capture focused window", spawn myWindowCap)
|
-- , ("M-C-S-s", "capture focused window", spawn myWindowCap)
|
||||||
]
|
]
|
||||||
, KeyGroup
|
, KeyGroup
|
||||||
"Multimedia"
|
"Multimedia"
|
||||||
[ KeyBinding "<XF86AudioPlay>" "toggle play/pause" $ Left runTogglePlay
|
[ KeyBinding "<XF86AudioPlay>" "toggle play/pause" $ Left $ toX runTogglePlay
|
||||||
, KeyBinding "<XF86AudioPrev>" "previous track" $ Left runPrevTrack
|
, KeyBinding "<XF86AudioPrev>" "previous track" $ Left $ toX runPrevTrack
|
||||||
, KeyBinding "<XF86AudioNext>" "next track" $ Left runNextTrack
|
, KeyBinding "<XF86AudioNext>" "next track" $ Left $ toX runNextTrack
|
||||||
, KeyBinding "<XF86AudioStop>" "stop" $ Left runStopPlay
|
, KeyBinding "<XF86AudioStop>" "stop" $ Left $ toX runStopPlay
|
||||||
, KeyBinding "<XF86AudioLowerVolume>" "volume down" $ Left runVolumeDown
|
, KeyBinding "<XF86AudioLowerVolume>" "volume down" $ Left $ toX runVolumeDown
|
||||||
, KeyBinding "<XF86AudioRaiseVolume>" "volume up" $ Left runVolumeUp
|
, KeyBinding "<XF86AudioRaiseVolume>" "volume up" $ Left $ toX runVolumeUp
|
||||||
, KeyBinding "<XF86AudioMute>" "volume mute" $ Left runVolumeMute
|
, KeyBinding "<XF86AudioMute>" "volume mute" $ Left $ toX runVolumeMute
|
||||||
]
|
]
|
||||||
, KeyGroup
|
, KeyGroup
|
||||||
"Dunst"
|
"Dunst"
|
||||||
[ KeyBinding "M-`" "dunst history" $ Left $ runNotificationHistory ses
|
[ KeyBinding "M-`" "dunst history" $ Left $ toX $ runNotificationHistory ses
|
||||||
, KeyBinding "M-S-`" "dunst close" $ Left $ runNotificationClose ses
|
, KeyBinding "M-S-`" "dunst close" $ Left $ toX $ runNotificationClose ses
|
||||||
, KeyBinding "M-M1-`" "dunst context menu" $ Left $ runNotificationContext ses
|
, KeyBinding "M-M1-`" "dunst context menu" $ Left $ toX $ runNotificationContext ses
|
||||||
, KeyBinding "M-C-`" "dunst close all" $ Left $ runNotificationCloseAll ses
|
, KeyBinding "M-C-`" "dunst close all" $ Left $ toX $ runNotificationCloseAll ses
|
||||||
]
|
]
|
||||||
, KeyGroup
|
, KeyGroup
|
||||||
"System"
|
"System"
|
||||||
|
@ -819,22 +832,25 @@ externalBindings ts db =
|
||||||
, -- M-<F1> reserved for showing the keymap
|
, -- M-<F1> reserved for showing the keymap
|
||||||
KeyBinding "M-<F2>" "restart xmonad" restartf
|
KeyBinding "M-<F2>" "restart xmonad" restartf
|
||||||
, KeyBinding "M-<F3>" "recompile xmonad" recompilef
|
, KeyBinding "M-<F3>" "recompile xmonad" recompilef
|
||||||
, KeyBinding "M-<F8>" "select autorandr profile" $ Left runAutorandrMenu
|
, KeyBinding "M-<F7>" "select autorandr profile" $ Left $ toX runAutorandrMenu
|
||||||
, KeyBinding "M-<F9>" "toggle ethernet" $ Left runToggleEthernet
|
, KeyBinding "M-<F8>" "toggle wifi" $ Left $ toX runToggleWifi
|
||||||
, KeyBinding "M-<F10>" "toggle bluetooth" $ Left $ runToggleBluetooth sys
|
, KeyBinding "M-<F9>" "toggle network" $ Left $ toX runToggleNetworking
|
||||||
, KeyBinding "M-<F11>" "toggle screensaver" $ Left $ callToggle ses
|
, KeyBinding "M-<F10>" "toggle bluetooth" $ Left $ toX $ runToggleBluetooth sys
|
||||||
|
, KeyBinding "M-<F11>" "toggle screensaver" $ Left $ toX $ callToggle ses
|
||||||
, KeyBinding "M-<F12>" "switch gpu" $ Left runOptimusPrompt
|
, KeyBinding "M-<F12>" "switch gpu" $ Left runOptimusPrompt
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
ses = dbSesClient db
|
ses = dbSesClient db
|
||||||
sys = dbSysClient db
|
sys = dbSysClient db
|
||||||
brightessControls ctl getter = (getter . ctl) ses
|
brightessControls ctl getter = (toX . getter . ctl) ses
|
||||||
ib = Left . brightessControls intelBacklightControls
|
ib = Left . brightessControls intelBacklightControls
|
||||||
ck = Left . brightessControls clevoKeyboardControls
|
ck = Left . brightessControls clevoKeyboardControls
|
||||||
ftrAlways n = Right . Always n . Always_ . FallbackAlone
|
ftrAlways n = Right . Always n . Always_ . FallbackAlone
|
||||||
restartf = ftrAlways "restart function" (runCleanup ts db >> runRestart)
|
restartf = ftrAlways "restart function" (cleanup >> runRestart)
|
||||||
recompilef = ftrAlways "recompile function" runRecompile
|
recompilef = ftrAlways "recompile function" runRecompile
|
||||||
|
toX_ = liftIO . runIO
|
||||||
|
toX = fmap toX_
|
||||||
|
|
||||||
type MaybeX = Maybe (X ())
|
type MaybeX = Maybe (X ())
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,17 @@ module Data.Internal.DBus
|
||||||
( SafeClient (..)
|
( SafeClient (..)
|
||||||
, SysClient (..)
|
, SysClient (..)
|
||||||
, SesClient (..)
|
, SesClient (..)
|
||||||
|
, NamedConnection (..)
|
||||||
|
, NamedSesConnection
|
||||||
|
, NamedSysConnection
|
||||||
|
, DBusEnv (..)
|
||||||
|
, DIO
|
||||||
|
, HasClient (..)
|
||||||
|
, releaseBusName
|
||||||
|
, withDIO
|
||||||
, addMatchCallback
|
, addMatchCallback
|
||||||
|
, addMatchCallbackSignal
|
||||||
|
, matchSignalFull
|
||||||
, matchProperty
|
, matchProperty
|
||||||
, matchPropertyFull
|
, matchPropertyFull
|
||||||
, matchPropertyChanged
|
, matchPropertyChanged
|
||||||
|
@ -25,77 +35,218 @@ module Data.Internal.DBus
|
||||||
, addInterfaceRemovedListener
|
, addInterfaceRemovedListener
|
||||||
, fromSingletonVariant
|
, fromSingletonVariant
|
||||||
, bodyToMaybe
|
, bodyToMaybe
|
||||||
|
, exportPair
|
||||||
|
, displayBusName
|
||||||
|
, displayObjectPath
|
||||||
|
, displayMemberName
|
||||||
|
, displayInterfaceName
|
||||||
|
, displayWrapQuote
|
||||||
|
, busNameT
|
||||||
|
, interfaceNameT
|
||||||
|
, memberNameT
|
||||||
|
, objectPathT
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import DBus
|
import DBus
|
||||||
import DBus.Client
|
import DBus.Client
|
||||||
|
import qualified Data.ByteString.Char8 as BC
|
||||||
import RIO
|
import RIO
|
||||||
|
import RIO.List
|
||||||
import qualified RIO.Map as M
|
import qualified RIO.Map as M
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Type-safe client
|
-- Type-safe client
|
||||||
|
|
||||||
|
data NamedConnection c = NamedConnection
|
||||||
|
{ ncClient :: !Client
|
||||||
|
, ncHumanName :: !(Maybe BusName)
|
||||||
|
--, ncUniqueName :: !BusName
|
||||||
|
, ncType :: !c
|
||||||
|
}
|
||||||
|
|
||||||
|
type NamedSesConnection = NamedConnection SesClient
|
||||||
|
|
||||||
|
type NamedSysConnection = NamedConnection SysClient
|
||||||
|
|
||||||
class SafeClient c where
|
class SafeClient c where
|
||||||
toClient :: c -> Client
|
getDBusClient
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Maybe BusName
|
||||||
|
-> m (Maybe (NamedConnection c))
|
||||||
|
|
||||||
getDBusClient :: MonadUnliftIO m => m (Maybe c)
|
disconnectDBusClient
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> NamedConnection c
|
||||||
|
-> m ()
|
||||||
|
disconnectDBusClient c = do
|
||||||
|
releaseBusName c
|
||||||
|
liftIO $ disconnect $ ncClient c
|
||||||
|
|
||||||
disconnectDBusClient :: MonadUnliftIO m => c -> m ()
|
withDBusClient
|
||||||
disconnectDBusClient = liftIO . disconnect . toClient
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Maybe BusName
|
||||||
|
-> (NamedConnection c -> m a)
|
||||||
|
-> m (Maybe a)
|
||||||
|
withDBusClient n f =
|
||||||
|
bracket (getDBusClient n) (mapM (liftIO . disconnect . ncClient)) $ mapM f
|
||||||
|
|
||||||
withDBusClient :: MonadUnliftIO m => (c -> m a) -> m (Maybe a)
|
withDBusClient_
|
||||||
withDBusClient f =
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
bracket getDBusClient (mapM (liftIO . disconnect . toClient)) $ mapM f
|
=> Maybe BusName
|
||||||
|
-> (NamedConnection c -> m ())
|
||||||
|
-> m ()
|
||||||
|
withDBusClient_ n = void . withDBusClient n
|
||||||
|
|
||||||
withDBusClient_ :: MonadUnliftIO m => (c -> m ()) -> m ()
|
fromDBusClient
|
||||||
withDBusClient_ = void . withDBusClient
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Maybe BusName
|
||||||
|
-> (NamedConnection c -> a)
|
||||||
|
-> m (Maybe a)
|
||||||
|
fromDBusClient n f = withDBusClient n (return . f)
|
||||||
|
|
||||||
fromDBusClient :: MonadUnliftIO m => (c -> a) -> m (Maybe a)
|
data SysClient = SysClient
|
||||||
fromDBusClient f = withDBusClient (return . f)
|
|
||||||
|
|
||||||
newtype SysClient = SysClient Client
|
|
||||||
|
|
||||||
instance SafeClient SysClient where
|
instance SafeClient SysClient where
|
||||||
toClient (SysClient cl) = cl
|
getDBusClient = connectToDBusWithName True SysClient
|
||||||
|
|
||||||
getDBusClient = fmap SysClient <$> getDBusClient' True
|
data SesClient = SesClient
|
||||||
|
|
||||||
newtype SesClient = SesClient Client
|
|
||||||
|
|
||||||
instance SafeClient SesClient where
|
instance SafeClient SesClient where
|
||||||
toClient (SesClient cl) = cl
|
-- TODO wet
|
||||||
|
getDBusClient = connectToDBusWithName False SesClient
|
||||||
|
|
||||||
getDBusClient = fmap SesClient <$> getDBusClient' False
|
connectToDBusWithName
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Bool
|
||||||
|
-> c
|
||||||
|
-> Maybe BusName
|
||||||
|
-> m (Maybe (NamedConnection c))
|
||||||
|
connectToDBusWithName sys t n = do
|
||||||
|
clRes <- getDBusClient' sys
|
||||||
|
case clRes of
|
||||||
|
Nothing -> do
|
||||||
|
logError "could not get client"
|
||||||
|
return Nothing
|
||||||
|
Just cl -> do
|
||||||
|
--helloRes <- liftIO $ callHello cl
|
||||||
|
--case helloRes of
|
||||||
|
-- Nothing -> do
|
||||||
|
-- logError "count not get unique name"
|
||||||
|
-- return Nothing
|
||||||
|
-- Just unique -> do
|
||||||
|
n' <- maybe (return Nothing) (`requestBusName` cl) n
|
||||||
|
return $
|
||||||
|
Just $
|
||||||
|
NamedConnection
|
||||||
|
{ ncClient = cl
|
||||||
|
, ncHumanName = n'
|
||||||
|
-- , ncUniqueName = unique
|
||||||
|
, ncType = t
|
||||||
|
}
|
||||||
|
|
||||||
getDBusClient' :: MonadUnliftIO m => Bool -> m (Maybe Client)
|
releaseBusName
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> NamedConnection c
|
||||||
|
-> m ()
|
||||||
|
releaseBusName NamedConnection {ncClient, ncHumanName} = do
|
||||||
|
-- TODO this might error?
|
||||||
|
case ncHumanName of
|
||||||
|
Just n -> do
|
||||||
|
liftIO $ void $ releaseName ncClient n
|
||||||
|
logInfo $ "released bus name: " <> displayBusName n
|
||||||
|
Nothing -> return ()
|
||||||
|
|
||||||
|
requestBusName
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> BusName
|
||||||
|
-> Client
|
||||||
|
-> m (Maybe BusName)
|
||||||
|
requestBusName n cl = do
|
||||||
|
res <- try $ liftIO $ requestName cl n []
|
||||||
|
case res of
|
||||||
|
Left e -> do
|
||||||
|
logError $ displayBytesUtf8 $ BC.pack $ clientErrorMessage e
|
||||||
|
return Nothing
|
||||||
|
Right r -> do
|
||||||
|
let msg
|
||||||
|
| r == NamePrimaryOwner = "registering name"
|
||||||
|
| r == NameAlreadyOwner = "this process already owns name"
|
||||||
|
| r == NameInQueue
|
||||||
|
|| r == NameExists =
|
||||||
|
"another process owns name"
|
||||||
|
-- this should never happen
|
||||||
|
| otherwise = "unknown error when requesting name"
|
||||||
|
logInfo $ msg <> ": " <> displayBusName n
|
||||||
|
case r of
|
||||||
|
NamePrimaryOwner -> return $ Just n
|
||||||
|
_ -> return Nothing
|
||||||
|
|
||||||
|
getDBusClient'
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Bool
|
||||||
|
-> m (Maybe Client)
|
||||||
getDBusClient' sys = do
|
getDBusClient' sys = do
|
||||||
res <- try $ liftIO $ if sys then connectSystem else connectSession
|
res <- try $ liftIO $ if sys then connectSystem else connectSession
|
||||||
case res of
|
case res of
|
||||||
Left e -> liftIO $ putStrLn (clientErrorMessage e) >> return Nothing
|
Left e -> do
|
||||||
|
logInfo $ displayBytesUtf8 $ BC.pack $ clientErrorMessage e
|
||||||
|
return Nothing
|
||||||
Right c -> return $ Just c
|
Right c -> return $ Just c
|
||||||
|
|
||||||
|
--callHello :: Client -> IO (Maybe BusName)
|
||||||
|
--callHello cl = do
|
||||||
|
-- reply <- call_ cl $ methodCallBus dbusName dbusPath dbusInterface "Hello"
|
||||||
|
-- case methodReturnBody reply of
|
||||||
|
-- [name] | Just nameStr <- fromVariant name -> do
|
||||||
|
-- busName <- parseBusName nameStr
|
||||||
|
-- return $ Just busName
|
||||||
|
-- _ -> return Nothing
|
||||||
|
--
|
||||||
|
data DBusEnv env c = DBusEnv {dClient :: !(NamedConnection c), dEnv :: !env}
|
||||||
|
|
||||||
|
type DIO env c = RIO (DBusEnv env c)
|
||||||
|
|
||||||
|
instance HasClient (DBusEnv SimpleApp) where
|
||||||
|
clientL = lens dClient (\x y -> x {dClient = y})
|
||||||
|
|
||||||
|
instance HasLogFunc (DBusEnv SimpleApp c) where
|
||||||
|
logFuncL = lens dEnv (\x y -> x {dEnv = y}) . logFuncL
|
||||||
|
|
||||||
|
withDIO
|
||||||
|
:: (MonadUnliftIO m, MonadReader env m)
|
||||||
|
=> NamedConnection c
|
||||||
|
-> DIO env c a
|
||||||
|
-> m a
|
||||||
|
withDIO cl x = do
|
||||||
|
env <- ask
|
||||||
|
runRIO (DBusEnv cl env) x
|
||||||
|
|
||||||
|
class HasClient env where
|
||||||
|
clientL :: SafeClient c => Lens' (env c) (NamedConnection c)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Methods
|
-- Methods
|
||||||
|
|
||||||
type MethodBody = Either T.Text [Variant]
|
type MethodBody = Either T.Text [Variant]
|
||||||
|
|
||||||
callMethod' :: (MonadUnliftIO m, SafeClient c) => c -> MethodCall -> m MethodBody
|
callMethod'
|
||||||
callMethod' cl =
|
:: (SafeClient c, MonadUnliftIO m, MonadReader (env c) m, HasClient env)
|
||||||
liftIO
|
=> MethodCall
|
||||||
. fmap (bimap (T.pack . methodErrorMessage) methodReturnBody)
|
-> m MethodBody
|
||||||
. call (toClient cl)
|
callMethod' mc = do
|
||||||
|
cl <- ncClient <$> view clientL
|
||||||
|
liftIO $ bimap (T.pack . methodErrorMessage) methodReturnBody <$> call cl mc
|
||||||
|
|
||||||
callMethod
|
callMethod
|
||||||
:: (MonadUnliftIO m, SafeClient c)
|
:: (SafeClient c, MonadUnliftIO m, MonadReader (env c) m, HasClient env)
|
||||||
=> c
|
=> BusName
|
||||||
-> BusName
|
|
||||||
-> ObjectPath
|
-> ObjectPath
|
||||||
-> InterfaceName
|
-> InterfaceName
|
||||||
-> MemberName
|
-> MemberName
|
||||||
-> m MethodBody
|
-> m MethodBody
|
||||||
callMethod client bus path iface = callMethod' client . methodCallBus bus path iface
|
callMethod bus path iface = callMethod' . methodCallBus bus path iface
|
||||||
|
|
||||||
methodCallBus :: BusName -> ObjectPath -> InterfaceName -> MemberName -> MethodCall
|
methodCallBus :: BusName -> ObjectPath -> InterfaceName -> MemberName -> MethodCall
|
||||||
methodCallBus b p i m =
|
methodCallBus b p i m =
|
||||||
|
@ -109,8 +260,22 @@ methodCallBus b p i m =
|
||||||
dbusInterface :: InterfaceName
|
dbusInterface :: InterfaceName
|
||||||
dbusInterface = interfaceName_ "org.freedesktop.DBus"
|
dbusInterface = interfaceName_ "org.freedesktop.DBus"
|
||||||
|
|
||||||
callGetNameOwner :: (MonadUnliftIO m, SafeClient c) => c -> BusName -> m (Maybe BusName)
|
callGetNameOwner
|
||||||
callGetNameOwner cl name = bodyToMaybe <$> callMethod' cl mc
|
:: ( SafeClient c
|
||||||
|
, MonadUnliftIO m
|
||||||
|
, MonadReader (env c) m
|
||||||
|
, HasClient env
|
||||||
|
, HasLogFunc (env c)
|
||||||
|
)
|
||||||
|
=> BusName
|
||||||
|
-> m (Maybe BusName)
|
||||||
|
callGetNameOwner name = do
|
||||||
|
res <- callMethod' mc
|
||||||
|
case res of
|
||||||
|
Left err -> do
|
||||||
|
logError $ Utf8Builder $ encodeUtf8Builder err
|
||||||
|
return Nothing
|
||||||
|
Right body -> return $ fromSingletonVariant body
|
||||||
where
|
where
|
||||||
mc =
|
mc =
|
||||||
(methodCallBus dbusName dbusPath dbusInterface mem)
|
(methodCallBus dbusName dbusPath dbusInterface mem)
|
||||||
|
@ -121,6 +286,7 @@ callGetNameOwner cl name = bodyToMaybe <$> callMethod' cl mc
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Variant parsing
|
-- Variant parsing
|
||||||
|
|
||||||
|
-- TODO log failures here?
|
||||||
fromSingletonVariant :: IsVariant a => [Variant] -> Maybe a
|
fromSingletonVariant :: IsVariant a => [Variant] -> Maybe a
|
||||||
fromSingletonVariant = fromVariant <=< listToMaybe
|
fromSingletonVariant = fromVariant <=< listToMaybe
|
||||||
|
|
||||||
|
@ -132,14 +298,29 @@ bodyToMaybe = either (const Nothing) fromSingletonVariant
|
||||||
|
|
||||||
type SignalCallback m = [Variant] -> m ()
|
type SignalCallback m = [Variant] -> m ()
|
||||||
|
|
||||||
|
addMatchCallbackSignal
|
||||||
|
:: ( MonadReader (env c) m
|
||||||
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, HasClient env
|
||||||
|
)
|
||||||
|
=> MatchRule
|
||||||
|
-> (Signal -> m ())
|
||||||
|
-> m SignalHandler
|
||||||
|
addMatchCallbackSignal rule cb = do
|
||||||
|
cl <- ncClient <$> view clientL
|
||||||
|
withRunInIO $ \run -> addMatch cl rule $ run . cb
|
||||||
|
|
||||||
addMatchCallback
|
addMatchCallback
|
||||||
:: (MonadUnliftIO m, SafeClient c)
|
:: ( MonadReader (env c) m
|
||||||
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, HasClient env
|
||||||
|
)
|
||||||
=> MatchRule
|
=> MatchRule
|
||||||
-> SignalCallback m
|
-> SignalCallback m
|
||||||
-> c
|
|
||||||
-> m SignalHandler
|
-> m SignalHandler
|
||||||
addMatchCallback rule cb cl = withRunInIO $ \run -> do
|
addMatchCallback rule cb = addMatchCallbackSignal rule (cb . signalBody)
|
||||||
addMatch (toClient cl) rule $ run . cb . signalBody
|
|
||||||
|
|
||||||
matchSignal
|
matchSignal
|
||||||
:: Maybe BusName
|
:: Maybe BusName
|
||||||
|
@ -156,15 +337,35 @@ matchSignal b p i m =
|
||||||
}
|
}
|
||||||
|
|
||||||
matchSignalFull
|
matchSignalFull
|
||||||
:: (MonadUnliftIO m, SafeClient c)
|
:: ( MonadReader (env c) m
|
||||||
=> c
|
, HasLogFunc (env c)
|
||||||
-> BusName
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, HasClient env
|
||||||
|
)
|
||||||
|
=> BusName
|
||||||
-> Maybe ObjectPath
|
-> Maybe ObjectPath
|
||||||
-> Maybe InterfaceName
|
-> Maybe InterfaceName
|
||||||
-> Maybe MemberName
|
-> Maybe MemberName
|
||||||
-> m (Maybe MatchRule)
|
-> m (Maybe MatchRule)
|
||||||
matchSignalFull client b p i m =
|
matchSignalFull b p i m = do
|
||||||
fmap (\o -> matchSignal (Just o) p i m) <$> callGetNameOwner client b
|
res <- callGetNameOwner b
|
||||||
|
case res of
|
||||||
|
Just o -> return $ Just $ matchSignal (Just o) p i m
|
||||||
|
Nothing -> do
|
||||||
|
logError msg
|
||||||
|
return Nothing
|
||||||
|
where
|
||||||
|
bus_ = displayWrapQuote $ displayBusName b
|
||||||
|
iface_ = displayWrapQuote . displayInterfaceName <$> i
|
||||||
|
path_ = displayWrapQuote . displayObjectPath <$> p
|
||||||
|
mem_ = displayWrapQuote . displayMemberName <$> m
|
||||||
|
match =
|
||||||
|
intersperse ", " $
|
||||||
|
mapMaybe (\(k, v) -> fmap ((k <> "=") <>) v) $
|
||||||
|
zip ["interface", "path", "member"] [iface_, path_, mem_]
|
||||||
|
stem = "could not get match rule for bus " <> bus_
|
||||||
|
msg = if null match then stem else stem <> " where " <> mconcat match
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Properties
|
-- Properties
|
||||||
|
@ -176,31 +377,42 @@ propertySignal :: MemberName
|
||||||
propertySignal = memberName_ "PropertiesChanged"
|
propertySignal = memberName_ "PropertiesChanged"
|
||||||
|
|
||||||
callPropertyGet
|
callPropertyGet
|
||||||
:: (MonadUnliftIO m, SafeClient c)
|
:: ( HasClient env
|
||||||
|
, MonadReader (env c) m
|
||||||
|
, HasLogFunc (env c)
|
||||||
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
)
|
||||||
=> BusName
|
=> BusName
|
||||||
-> ObjectPath
|
-> ObjectPath
|
||||||
-> InterfaceName
|
-> InterfaceName
|
||||||
-> MemberName
|
-> MemberName
|
||||||
-> c
|
|
||||||
-> m [Variant]
|
-> m [Variant]
|
||||||
callPropertyGet bus path iface property cl =
|
callPropertyGet bus path iface property = do
|
||||||
liftIO $
|
cl <- ncClient <$> view clientL
|
||||||
fmap (either (const []) (: [])) $
|
res <- liftIO $ getProperty cl $ methodCallBus bus path iface property
|
||||||
getProperty (toClient cl) $
|
case res of
|
||||||
methodCallBus bus path iface property
|
Left err -> do
|
||||||
|
logError $ displayBytesUtf8 $ BC.pack $ methodErrorMessage err
|
||||||
|
return []
|
||||||
|
Right v -> return [v]
|
||||||
|
|
||||||
matchProperty :: Maybe BusName -> Maybe ObjectPath -> MatchRule
|
matchProperty :: Maybe BusName -> Maybe ObjectPath -> MatchRule
|
||||||
matchProperty b p =
|
matchProperty b p =
|
||||||
matchSignal b p (Just propertyInterface) (Just propertySignal)
|
matchSignal b p (Just propertyInterface) (Just propertySignal)
|
||||||
|
|
||||||
matchPropertyFull
|
matchPropertyFull
|
||||||
:: (MonadUnliftIO m, SafeClient c)
|
:: ( MonadReader (env c) m
|
||||||
=> c
|
, HasLogFunc (env c)
|
||||||
-> BusName
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, HasClient env
|
||||||
|
)
|
||||||
|
=> BusName
|
||||||
-> Maybe ObjectPath
|
-> Maybe ObjectPath
|
||||||
-> m (Maybe MatchRule)
|
-> m (Maybe MatchRule)
|
||||||
matchPropertyFull cl b p =
|
matchPropertyFull b p =
|
||||||
matchSignalFull cl b p (Just propertyInterface) (Just propertySignal)
|
matchSignalFull b p (Just propertyInterface) (Just propertySignal)
|
||||||
|
|
||||||
data SignalMatch a = Match a | NoMatch | Failure deriving (Eq, Show)
|
data SignalMatch a = Match a | NoMatch | Failure deriving (Eq, Show)
|
||||||
|
|
||||||
|
@ -212,26 +424,26 @@ withSignalMatch _ NoMatch = return ()
|
||||||
matchPropertyChanged
|
matchPropertyChanged
|
||||||
:: IsVariant a
|
:: IsVariant a
|
||||||
=> InterfaceName
|
=> InterfaceName
|
||||||
-> T.Text
|
-> MemberName
|
||||||
-> [Variant]
|
-> [Variant]
|
||||||
-> SignalMatch a
|
-> SignalMatch a
|
||||||
matchPropertyChanged iface property [i, body, _] =
|
matchPropertyChanged iface property [sigIface, sigValues, _] =
|
||||||
let i' = (fromVariant i :: Maybe T.Text)
|
let i = fromVariant sigIface :: Maybe T.Text
|
||||||
b = toMap body
|
v = fromVariant sigValues :: Maybe (M.Map T.Text Variant)
|
||||||
in case (i', b) of
|
in case (i, v) of
|
||||||
(Just i'', Just b') ->
|
(Just i', Just v') ->
|
||||||
if i'' == T.pack (formatInterfaceName iface)
|
if i' == interfaceNameT iface
|
||||||
then maybe NoMatch Match $ fromVariant =<< M.lookup property b'
|
then
|
||||||
|
maybe NoMatch Match $
|
||||||
|
fromVariant =<< M.lookup (memberNameT property) v'
|
||||||
else NoMatch
|
else NoMatch
|
||||||
_ -> Failure
|
_ -> Failure
|
||||||
where
|
|
||||||
toMap v = fromVariant v :: Maybe (M.Map T.Text Variant)
|
|
||||||
matchPropertyChanged _ _ _ = Failure
|
matchPropertyChanged _ _ _ = Failure
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Object Manager
|
-- Object Manager
|
||||||
|
|
||||||
type ObjectTree = M.Map ObjectPath (M.Map T.Text (M.Map T.Text Variant))
|
type ObjectTree = M.Map ObjectPath (M.Map InterfaceName (M.Map T.Text Variant))
|
||||||
|
|
||||||
omInterface :: InterfaceName
|
omInterface :: InterfaceName
|
||||||
omInterface = interfaceName_ "org.freedesktop.DBus.ObjectManager"
|
omInterface = interfaceName_ "org.freedesktop.DBus.ObjectManager"
|
||||||
|
@ -246,43 +458,132 @@ omInterfacesRemoved :: MemberName
|
||||||
omInterfacesRemoved = memberName_ "InterfacesRemoved"
|
omInterfacesRemoved = memberName_ "InterfacesRemoved"
|
||||||
|
|
||||||
callGetManagedObjects
|
callGetManagedObjects
|
||||||
:: (MonadUnliftIO m, SafeClient c)
|
:: ( MonadReader (env c) m
|
||||||
=> c
|
, HasLogFunc (env c)
|
||||||
-> BusName
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, HasClient env
|
||||||
|
)
|
||||||
|
=> BusName
|
||||||
-> ObjectPath
|
-> ObjectPath
|
||||||
-> m ObjectTree
|
-> m ObjectTree
|
||||||
callGetManagedObjects cl bus path =
|
callGetManagedObjects bus path = do
|
||||||
either (const M.empty) (fromMaybe M.empty . fromSingletonVariant)
|
res <- callMethod bus path omInterface getManagedObjects
|
||||||
<$> callMethod cl bus path omInterface getManagedObjects
|
case res of
|
||||||
|
Left err -> do
|
||||||
|
logError $ Utf8Builder $ encodeUtf8Builder err
|
||||||
|
return M.empty
|
||||||
|
Right v ->
|
||||||
|
return $
|
||||||
|
fmap (M.mapKeys interfaceName_) $
|
||||||
|
fromMaybe M.empty $
|
||||||
|
fromSingletonVariant v
|
||||||
|
|
||||||
addInterfaceChangedListener
|
addInterfaceChangedListener
|
||||||
:: (MonadUnliftIO m, SafeClient c)
|
:: ( MonadReader (env c) m
|
||||||
|
, HasLogFunc (env c)
|
||||||
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, HasClient env
|
||||||
|
)
|
||||||
=> BusName
|
=> BusName
|
||||||
-> MemberName
|
-> MemberName
|
||||||
-> ObjectPath
|
-> ObjectPath
|
||||||
-> SignalCallback m
|
-> SignalCallback m
|
||||||
-> c
|
|
||||||
-> m (Maybe SignalHandler)
|
-> m (Maybe SignalHandler)
|
||||||
addInterfaceChangedListener bus prop path sc cl = do
|
addInterfaceChangedListener bus prop path sc = do
|
||||||
rule <- matchSignalFull cl bus (Just path) (Just omInterface) (Just prop)
|
res <- matchSignalFull bus (Just path) (Just omInterface) (Just prop)
|
||||||
forM rule $ \r -> addMatchCallback r sc cl
|
case res of
|
||||||
|
Nothing -> do
|
||||||
|
logError $
|
||||||
|
"could not add listener for property"
|
||||||
|
<> prop_
|
||||||
|
<> " at path "
|
||||||
|
<> path_
|
||||||
|
<> " on bus "
|
||||||
|
<> bus_
|
||||||
|
return Nothing
|
||||||
|
Just rule -> Just <$> addMatchCallback rule sc
|
||||||
|
where
|
||||||
|
bus_ = "'" <> displayBusName bus <> "'"
|
||||||
|
path_ = "'" <> displayObjectPath path <> "'"
|
||||||
|
prop_ = "'" <> displayMemberName prop <> "'"
|
||||||
|
|
||||||
addInterfaceAddedListener
|
addInterfaceAddedListener
|
||||||
:: (MonadUnliftIO m, SafeClient c)
|
:: ( MonadReader (env c) m
|
||||||
|
, HasLogFunc (env c)
|
||||||
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, HasClient env
|
||||||
|
)
|
||||||
=> BusName
|
=> BusName
|
||||||
-> ObjectPath
|
-> ObjectPath
|
||||||
-> SignalCallback m
|
-> SignalCallback m
|
||||||
-> c
|
|
||||||
-> m (Maybe SignalHandler)
|
-> m (Maybe SignalHandler)
|
||||||
addInterfaceAddedListener bus =
|
addInterfaceAddedListener bus =
|
||||||
addInterfaceChangedListener bus omInterfacesAdded
|
addInterfaceChangedListener bus omInterfacesAdded
|
||||||
|
|
||||||
addInterfaceRemovedListener
|
addInterfaceRemovedListener
|
||||||
:: (MonadUnliftIO m, SafeClient c)
|
:: ( MonadReader (env c) m
|
||||||
|
, HasLogFunc (env c)
|
||||||
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, HasClient env
|
||||||
|
)
|
||||||
=> BusName
|
=> BusName
|
||||||
-> ObjectPath
|
-> ObjectPath
|
||||||
-> SignalCallback m
|
-> SignalCallback m
|
||||||
-> c
|
|
||||||
-> m (Maybe SignalHandler)
|
-> m (Maybe SignalHandler)
|
||||||
addInterfaceRemovedListener bus =
|
addInterfaceRemovedListener bus =
|
||||||
addInterfaceChangedListener bus omInterfacesRemoved
|
addInterfaceChangedListener bus omInterfacesRemoved
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Interface export/unexport
|
||||||
|
|
||||||
|
exportPair
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> ObjectPath
|
||||||
|
-> (Client -> m Interface)
|
||||||
|
-> NamedConnection c
|
||||||
|
-> (m (), m ())
|
||||||
|
exportPair path toIface cl = (up, down)
|
||||||
|
where
|
||||||
|
cl_ = ncClient cl
|
||||||
|
up = do
|
||||||
|
logInfo $ "adding interface: " <> path_
|
||||||
|
i <- toIface cl_
|
||||||
|
liftIO $ export cl_ path i
|
||||||
|
down = do
|
||||||
|
logInfo $ "removing interface: " <> path_
|
||||||
|
liftIO $ unexport cl_ path
|
||||||
|
path_ = displayObjectPath path
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- logging helpers
|
||||||
|
|
||||||
|
busNameT :: BusName -> T.Text
|
||||||
|
busNameT = T.pack . formatBusName
|
||||||
|
|
||||||
|
objectPathT :: ObjectPath -> T.Text
|
||||||
|
objectPathT = T.pack . formatObjectPath
|
||||||
|
|
||||||
|
interfaceNameT :: InterfaceName -> T.Text
|
||||||
|
interfaceNameT = T.pack . formatInterfaceName
|
||||||
|
|
||||||
|
memberNameT :: MemberName -> T.Text
|
||||||
|
memberNameT = T.pack . formatMemberName
|
||||||
|
|
||||||
|
displayBusName :: BusName -> Utf8Builder
|
||||||
|
displayBusName = displayBytesUtf8 . BC.pack . formatBusName
|
||||||
|
|
||||||
|
displayObjectPath :: ObjectPath -> Utf8Builder
|
||||||
|
displayObjectPath = displayBytesUtf8 . BC.pack . formatObjectPath
|
||||||
|
|
||||||
|
displayMemberName :: MemberName -> Utf8Builder
|
||||||
|
displayMemberName = displayBytesUtf8 . BC.pack . formatMemberName
|
||||||
|
|
||||||
|
displayInterfaceName :: InterfaceName -> Utf8Builder
|
||||||
|
displayInterfaceName = displayBytesUtf8 . BC.pack . formatInterfaceName
|
||||||
|
|
||||||
|
displayWrapQuote :: Utf8Builder -> Utf8Builder
|
||||||
|
displayWrapQuote x = "'" <> x <> "'"
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
{-# LANGUAGE DeriveFunctor #-}
|
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
{-# LANGUAGE GADTs #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE RankNTypes #-}
|
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Functions for handling dependencies
|
-- Functions for handling dependencies
|
||||||
|
|
||||||
module Data.Internal.Dependency
|
module Data.Internal.XIO
|
||||||
-- feature types
|
-- feature types
|
||||||
( Feature
|
( Feature
|
||||||
, Always (..)
|
, Always (..)
|
||||||
|
@ -26,6 +19,7 @@ module Data.Internal.Dependency
|
||||||
, SubfeatureRoot
|
, SubfeatureRoot
|
||||||
, Msg (..)
|
, Msg (..)
|
||||||
-- configuration
|
-- configuration
|
||||||
|
, XEnv (..)
|
||||||
, XParams (..)
|
, XParams (..)
|
||||||
, XPFeatures (..)
|
, XPFeatures (..)
|
||||||
, XPQuery
|
, XPQuery
|
||||||
|
@ -53,8 +47,9 @@ module Data.Internal.Dependency
|
||||||
, dumpSometimes
|
, dumpSometimes
|
||||||
, showFulfillment
|
, showFulfillment
|
||||||
-- testing
|
-- testing
|
||||||
, FIO
|
, XIO
|
||||||
, withCache
|
, runXIO
|
||||||
|
, hRunXIO
|
||||||
, evalFeature
|
, evalFeature
|
||||||
, executeSometimes
|
, executeSometimes
|
||||||
, executeAlways
|
, executeAlways
|
||||||
|
@ -96,6 +91,7 @@ module Data.Internal.Dependency
|
||||||
, process
|
, process
|
||||||
-- misc
|
-- misc
|
||||||
, shellTest
|
, shellTest
|
||||||
|
, withLogFile
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
@ -104,6 +100,7 @@ import qualified DBus.Introspection as I
|
||||||
import Data.Aeson hiding (Error, Result)
|
import Data.Aeson hiding (Error, Result)
|
||||||
import Data.Aeson.Key
|
import Data.Aeson.Key
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
|
import qualified Data.Text.IO as TI
|
||||||
import Data.Yaml
|
import Data.Yaml
|
||||||
import GHC.IO.Exception (ioe_description)
|
import GHC.IO.Exception (ioe_description)
|
||||||
import RIO hiding (bracket, fromString)
|
import RIO hiding (bracket, fromString)
|
||||||
|
@ -115,7 +112,7 @@ import qualified RIO.Text as T
|
||||||
import System.Posix.Files
|
import System.Posix.Files
|
||||||
import System.Process.Typed (nullStream)
|
import System.Process.Typed (nullStream)
|
||||||
import UnliftIO.Environment
|
import UnliftIO.Environment
|
||||||
import XMonad.Core (X, io)
|
import XMonad.Core (X, dataDir, getDirectories, io)
|
||||||
import XMonad.Internal.IO
|
import XMonad.Internal.IO
|
||||||
import XMonad.Internal.Shell hiding (proc, runProcess)
|
import XMonad.Internal.Shell hiding (proc, runProcess)
|
||||||
import XMonad.Internal.Theme
|
import XMonad.Internal.Theme
|
||||||
|
@ -128,30 +125,47 @@ import XMonad.Internal.Theme
|
||||||
|
|
||||||
-- | Run feature evaluation(s) with the cache
|
-- | Run feature evaluation(s) with the cache
|
||||||
-- Currently there is no easy way to not use this (oh well)
|
-- Currently there is no easy way to not use this (oh well)
|
||||||
withCache :: FIO a -> IO a
|
runXIO :: FilePath -> XIO a -> IO a
|
||||||
withCache x = do
|
runXIO logfile x = withLogFile logfile $ \h -> hRunXIO True h x
|
||||||
logOpts <- logOptionsHandle stderr False
|
|
||||||
|
-- TODO use dhall to encode config file and log here to control the loglevel
|
||||||
|
withLogFile :: MonadUnliftIO m => FilePath -> (Handle -> m a) -> m a
|
||||||
|
withLogFile logfile f = do
|
||||||
|
p <- (</> logfile) . dataDir <$> liftIO getDirectories
|
||||||
|
catchIO (withBinaryFile p AppendMode f) $ \e -> do
|
||||||
|
liftIO $ TI.putStrLn $ T.pack $ show e
|
||||||
|
liftIO $ TI.putStrLn "could not open log file, falling back to stderr"
|
||||||
|
f stderr
|
||||||
|
|
||||||
|
hRunXIO :: Bool -> Handle -> XIO a -> IO a
|
||||||
|
hRunXIO verbose h x = do
|
||||||
|
hSetBuffering h LineBuffering
|
||||||
|
logOpts <- logOptionsHandle_ verbose h
|
||||||
pc <- mkDefaultProcessContext
|
pc <- mkDefaultProcessContext
|
||||||
withLogFunc logOpts $ \f -> do
|
withLogFunc logOpts $ \f -> do
|
||||||
p <- getParams
|
p <- getParams
|
||||||
let s = DepStage f pc p
|
let s = XEnv f pc p
|
||||||
runRIO s x
|
runRIO s x
|
||||||
|
|
||||||
|
logOptionsHandle_ :: MonadUnliftIO m => Bool -> Handle -> m LogOptions
|
||||||
|
logOptionsHandle_ v h =
|
||||||
|
setLogVerboseFormat v . setLogUseTime v <$> logOptionsHandle h False
|
||||||
|
|
||||||
-- | Execute an Always immediately
|
-- | Execute an Always immediately
|
||||||
executeAlways :: Always (IO a) -> FIO a
|
executeAlways :: Always (IO a) -> XIO a
|
||||||
executeAlways = io <=< evalAlways
|
executeAlways = io <=< evalAlways
|
||||||
|
|
||||||
-- | Execute a Sometimes immediately (or do nothing if failure)
|
-- | Execute a Sometimes immediately (or do nothing if failure)
|
||||||
executeSometimes :: Sometimes (FIO a) -> FIO (Maybe a)
|
executeSometimes :: Sometimes (XIO a) -> XIO (Maybe a)
|
||||||
executeSometimes a = maybe (return Nothing) (fmap Just) =<< evalSometimes a
|
executeSometimes a = maybe (return Nothing) (fmap Just) =<< evalSometimes a
|
||||||
|
|
||||||
-- | Possibly return the action of an Always/Sometimes
|
-- | Possibly return the action of an Always/Sometimes
|
||||||
evalFeature :: Feature a -> FIO (Maybe a)
|
evalFeature :: Feature a -> XIO (Maybe a)
|
||||||
evalFeature (Right a) = Just <$> evalAlways a
|
evalFeature (Right a) = Just <$> evalAlways a
|
||||||
evalFeature (Left s) = evalSometimes s
|
evalFeature (Left s) = evalSometimes s
|
||||||
|
|
||||||
-- | Possibly return the action of a Sometimes
|
-- | Possibly return the action of a Sometimes
|
||||||
evalSometimes :: Sometimes a -> FIO (Maybe a)
|
evalSometimes :: Sometimes a -> XIO (Maybe a)
|
||||||
evalSometimes x = either goFail goPass =<< evalSometimesMsg x
|
evalSometimes x = either goFail goPass =<< evalSometimesMsg x
|
||||||
where
|
where
|
||||||
goPass (a, ws) = putErrors ws >> return (Just a)
|
goPass (a, ws) = putErrors ws >> return (Just a)
|
||||||
|
@ -159,13 +173,13 @@ evalSometimes x = either goFail goPass =<< evalSometimesMsg x
|
||||||
putErrors = mapM_ logMsg
|
putErrors = mapM_ logMsg
|
||||||
|
|
||||||
-- | Return the action of an Always
|
-- | Return the action of an Always
|
||||||
evalAlways :: Always a -> FIO a
|
evalAlways :: Always a -> XIO a
|
||||||
evalAlways a = do
|
evalAlways a = do
|
||||||
(x, ws) <- evalAlwaysMsg a
|
(x, ws) <- evalAlwaysMsg a
|
||||||
mapM_ logMsg ws
|
mapM_ logMsg ws
|
||||||
return x
|
return x
|
||||||
|
|
||||||
logMsg :: FMsg -> FIO ()
|
logMsg :: FMsg -> XIO ()
|
||||||
logMsg (FMsg fn n (Msg ll m)) = do
|
logMsg (FMsg fn n (Msg ll m)) = do
|
||||||
p <- io getProgName
|
p <- io getProgName
|
||||||
f $ Utf8Builder $ encodeUtf8Builder $ T.unwords $ fmt s (T.pack p)
|
f $ Utf8Builder $ encodeUtf8Builder $ T.unwords $ fmt s (T.pack p)
|
||||||
|
@ -186,8 +200,9 @@ logMsg (FMsg fn n (Msg ll m)) = do
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Package status
|
-- Package status
|
||||||
|
|
||||||
showFulfillment :: Fulfillment -> T.Text
|
showFulfillment :: Fulfillment -> Utf8Builder
|
||||||
showFulfillment (Package t n) = T.concat [T.pack $ show t, "\t", n]
|
showFulfillment (Package t n) =
|
||||||
|
displayShow t <> "\t" <> Utf8Builder (encodeUtf8Builder n)
|
||||||
|
|
||||||
dumpFeature :: Feature a -> [Fulfillment]
|
dumpFeature :: Feature a -> [Fulfillment]
|
||||||
dumpFeature = either dumpSometimes dumpAlways
|
dumpFeature = either dumpSometimes dumpAlways
|
||||||
|
@ -209,7 +224,7 @@ type AlwaysIO = Always (IO ())
|
||||||
|
|
||||||
type SometimesX = Sometimes (X ())
|
type SometimesX = Sometimes (X ())
|
||||||
|
|
||||||
type SometimesIO = Sometimes (FIO ())
|
type SometimesIO = Sometimes (XIO ())
|
||||||
|
|
||||||
type Feature a = Either (Sometimes a) (Always a)
|
type Feature a = Either (Sometimes a) (Always a)
|
||||||
|
|
||||||
|
@ -219,12 +234,13 @@ type Feature a = Either (Sometimes a) (Always a)
|
||||||
-- | Feature that is guaranteed to work
|
-- | Feature that is guaranteed to work
|
||||||
-- This is composed of sub-features that are tested in order, and if all fail
|
-- This is composed of sub-features that are tested in order, and if all fail
|
||||||
-- the fallback is a monadic action (eg a plain haskell function)
|
-- the fallback is a monadic action (eg a plain haskell function)
|
||||||
data Always a = Always T.Text (Always_ a)
|
data Always a = Always T.Text (Always_ a) deriving (Functor)
|
||||||
|
|
||||||
-- | Feature that is guaranteed to work (inner data)
|
-- | Feature that is guaranteed to work (inner data)
|
||||||
data Always_ a
|
data Always_ a
|
||||||
= Option (SubfeatureRoot a) (Always_ a)
|
= Option (SubfeatureRoot a) (Always_ a)
|
||||||
| Always_ (FallbackRoot a)
|
| Always_ (FallbackRoot a)
|
||||||
|
deriving (Functor)
|
||||||
|
|
||||||
-- | Root of a fallback action for an always
|
-- | Root of a fallback action for an always
|
||||||
-- This may either be a lone action or a function that depends on the results
|
-- This may either be a lone action or a function that depends on the results
|
||||||
|
@ -233,15 +249,23 @@ data FallbackRoot a
|
||||||
= FallbackAlone a
|
= FallbackAlone a
|
||||||
| forall p. FallbackTree (p -> a) (FallbackStack p)
|
| forall p. FallbackTree (p -> a) (FallbackStack p)
|
||||||
|
|
||||||
|
instance Functor FallbackRoot where
|
||||||
|
fmap f (FallbackAlone a) = FallbackAlone (f a)
|
||||||
|
fmap f (FallbackTree g s) = FallbackTree (f . g) s
|
||||||
|
|
||||||
-- | Always features that are used as a payload for a fallback action
|
-- | Always features that are used as a payload for a fallback action
|
||||||
data FallbackStack p
|
data FallbackStack p
|
||||||
= FallbackBottom (Always p)
|
= FallbackBottom (Always p)
|
||||||
| forall x y. FallbackStack (x -> y -> p) (Always x) (FallbackStack y)
|
| forall x y. FallbackStack (x -> y -> p) (Always x) (FallbackStack y)
|
||||||
|
|
||||||
|
instance Functor FallbackStack where
|
||||||
|
fmap f (FallbackBottom a) = FallbackBottom $ fmap f a
|
||||||
|
fmap f (FallbackStack g a s) = FallbackStack (\x -> f . g x) a s
|
||||||
|
|
||||||
-- | Feature that might not be present
|
-- | Feature that might not be present
|
||||||
-- This is like an Always except it doesn't fall back on a guaranteed monadic
|
-- This is like an Always except it doesn't fall back on a guaranteed monadic
|
||||||
-- action
|
-- action
|
||||||
data Sometimes a = Sometimes T.Text XPQuery (Sometimes_ a)
|
data Sometimes a = Sometimes T.Text XPQuery (Sometimes_ a) deriving (Functor)
|
||||||
|
|
||||||
-- | Feature that might not be present (inner data)
|
-- | Feature that might not be present (inner data)
|
||||||
type Sometimes_ a = [SubfeatureRoot a]
|
type Sometimes_ a = [SubfeatureRoot a]
|
||||||
|
@ -254,6 +278,7 @@ data Subfeature f = Subfeature
|
||||||
{ sfData :: f
|
{ sfData :: f
|
||||||
, sfName :: T.Text
|
, sfName :: T.Text
|
||||||
}
|
}
|
||||||
|
deriving (Functor)
|
||||||
|
|
||||||
type SubfeatureRoot a = Subfeature (Root a)
|
type SubfeatureRoot a = Subfeature (Root a)
|
||||||
|
|
||||||
|
@ -263,8 +288,24 @@ type SubfeatureRoot a = Subfeature (Root a)
|
||||||
data Root a
|
data Root a
|
||||||
= forall p. IORoot (p -> a) (IOTree p)
|
= forall p. IORoot (p -> a) (IOTree p)
|
||||||
| IORoot_ a IOTree_
|
| IORoot_ a IOTree_
|
||||||
| forall c p. SafeClient c => DBusRoot (p -> c -> a) (DBusTree c p) (Maybe c)
|
| forall c p.
|
||||||
| forall c. SafeClient c => DBusRoot_ (c -> a) (DBusTree_ c) (Maybe c)
|
SafeClient c =>
|
||||||
|
DBusRoot
|
||||||
|
(p -> NamedConnection c -> a)
|
||||||
|
(DBusTree c p)
|
||||||
|
(Maybe (NamedConnection c))
|
||||||
|
| forall c.
|
||||||
|
SafeClient c =>
|
||||||
|
DBusRoot_
|
||||||
|
(NamedConnection c -> a)
|
||||||
|
(DBusTree_ c)
|
||||||
|
(Maybe (NamedConnection c))
|
||||||
|
|
||||||
|
instance Functor Root where
|
||||||
|
fmap f (IORoot a t) = IORoot (f . a) t
|
||||||
|
fmap f (IORoot_ a t) = IORoot_ (f a) t
|
||||||
|
fmap f (DBusRoot a t cl) = DBusRoot (\p c -> f $ a p c) t cl
|
||||||
|
fmap f (DBusRoot_ a t cl) = DBusRoot_ (f . a) t cl
|
||||||
|
|
||||||
-- | The dependency tree with rule to merge results when needed
|
-- | The dependency tree with rule to merge results when needed
|
||||||
data Tree d d_ p
|
data Tree d d_ p
|
||||||
|
@ -289,7 +330,7 @@ type DBusTree_ c = Tree_ (DBusDependency_ c)
|
||||||
-- | A dependency that only requires IO to evaluate (with payload)
|
-- | A dependency that only requires IO to evaluate (with payload)
|
||||||
data IODependency p
|
data IODependency p
|
||||||
= -- an IO action that yields a payload
|
= -- an IO action that yields a payload
|
||||||
IORead T.Text [Fulfillment] (FIO (Result p))
|
IORead T.Text [Fulfillment] (XIO (Result p))
|
||||||
| -- always yields a payload
|
| -- always yields a payload
|
||||||
IOConst p
|
IOConst p
|
||||||
| -- an always that yields a payload
|
| -- an always that yields a payload
|
||||||
|
@ -302,12 +343,11 @@ data DBusDependency_ c
|
||||||
= Bus [Fulfillment] BusName
|
= Bus [Fulfillment] BusName
|
||||||
| Endpoint [Fulfillment] BusName ObjectPath InterfaceName DBusMember
|
| Endpoint [Fulfillment] BusName ObjectPath InterfaceName DBusMember
|
||||||
| DBusIO IODependency_
|
| DBusIO IODependency_
|
||||||
deriving (Generic)
|
|
||||||
|
|
||||||
-- | A dependency that only requires IO to evaluate (no payload)
|
-- | A dependency that only requires IO to evaluate (no payload)
|
||||||
data IODependency_
|
data IODependency_
|
||||||
= IOSystem_ [Fulfillment] SystemDependency
|
= IOSystem_ [Fulfillment] SystemDependency
|
||||||
| IOTest_ T.Text [Fulfillment] (FIO (Maybe Msg))
|
| IOTest_ T.Text [Fulfillment] (XIO (Maybe Msg))
|
||||||
| forall a. IOSometimes_ (Sometimes a)
|
| forall a. IOSometimes_ (Sometimes a)
|
||||||
|
|
||||||
-- | A system component to an IODependency
|
-- | A system component to an IODependency
|
||||||
|
@ -317,23 +357,23 @@ data SystemDependency
|
||||||
| AccessiblePath FilePath Bool Bool
|
| AccessiblePath FilePath Bool Bool
|
||||||
| Systemd UnitType T.Text
|
| Systemd UnitType T.Text
|
||||||
| Process T.Text
|
| Process T.Text
|
||||||
deriving (Eq, Show, Generic)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
-- | The type of a systemd service
|
-- | The type of a systemd service
|
||||||
data UnitType = SystemUnit | UserUnit deriving (Eq, Show, Generic)
|
data UnitType = SystemUnit | UserUnit deriving (Eq, Show)
|
||||||
|
|
||||||
-- | Wrapper type to describe and endpoint
|
-- | Wrapper type to describe and endpoint
|
||||||
data DBusMember
|
data DBusMember
|
||||||
= Method_ MemberName
|
= Method_ MemberName
|
||||||
| Signal_ MemberName
|
| Signal_ MemberName
|
||||||
| Property_ T.Text
|
| Property_ T.Text
|
||||||
deriving (Eq, Show, Generic)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
-- | A means to fulfill a dependency
|
-- | A means to fulfill a dependency
|
||||||
-- For now this is just the name of an Arch Linux package (AUR or official)
|
-- For now this is just the name of an Arch Linux package (AUR or official)
|
||||||
data Fulfillment = Package ArchPkg T.Text deriving (Eq, Show, Ord)
|
data Fulfillment = Package ArchPkg T.Text deriving (Eq, Show, Ord)
|
||||||
|
|
||||||
data ArchPkg = Official | AUR | Custom deriving (Eq, Show, Generic, Ord)
|
data ArchPkg = Official | AUR | Custom deriving (Eq, Show, Ord)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Tested dependency tree
|
-- Tested dependency tree
|
||||||
|
@ -376,19 +416,25 @@ data PostFail = PostFail [Msg] | PostMissing Msg
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Configuration
|
-- Configuration
|
||||||
|
|
||||||
type FIO a = RIO DepStage a
|
type XIO a = RIO XEnv a
|
||||||
|
|
||||||
data DepStage = DepStage
|
data XEnv = XEnv
|
||||||
{ dsLogFun :: !LogFunc
|
{ xLogFun :: !LogFunc
|
||||||
, dsProcCxt :: !ProcessContext
|
, xProcCxt :: !ProcessContext
|
||||||
, dsParams :: !XParams
|
, xParams :: !XParams
|
||||||
}
|
}
|
||||||
|
|
||||||
instance HasLogFunc DepStage where
|
instance HasLogFunc XEnv where
|
||||||
logFuncL = lens dsLogFun (\x y -> x {dsLogFun = y})
|
logFuncL = lens xLogFun (\x y -> x {xLogFun = y})
|
||||||
|
|
||||||
instance HasProcessContext DepStage where
|
instance HasLogFunc (DBusEnv XEnv c) where
|
||||||
processContextL = lens dsProcCxt (\x y -> x {dsProcCxt = y})
|
logFuncL = lens dEnv (\x y -> x {dEnv = y}) . logFuncL
|
||||||
|
|
||||||
|
instance HasProcessContext XEnv where
|
||||||
|
processContextL = lens xProcCxt (\x y -> x {xProcCxt = y})
|
||||||
|
|
||||||
|
instance HasClient (DBusEnv XEnv) where
|
||||||
|
clientL = lens dClient (\x y -> x {dClient = y})
|
||||||
|
|
||||||
data XParams = XParams
|
data XParams = XParams
|
||||||
{ xpLogLevel :: LogLevel
|
{ xpLogLevel :: LogLevel
|
||||||
|
@ -422,7 +468,8 @@ data XPFeatures = XPFeatures
|
||||||
, xpfIntelBacklight :: Bool
|
, xpfIntelBacklight :: Bool
|
||||||
, xpfClevoBacklight :: Bool
|
, xpfClevoBacklight :: Bool
|
||||||
, xpfBattery :: Bool
|
, xpfBattery :: Bool
|
||||||
, xpfF5VPN :: Bool
|
, xpfEthPrefix :: Maybe Text
|
||||||
|
, xpfWifiPrefix :: Maybe Text
|
||||||
}
|
}
|
||||||
|
|
||||||
instance FromJSON XPFeatures where
|
instance FromJSON XPFeatures where
|
||||||
|
@ -449,7 +496,9 @@ instance FromJSON XPFeatures where
|
||||||
<*> o
|
<*> o
|
||||||
.:+ "battery"
|
.:+ "battery"
|
||||||
<*> o
|
<*> o
|
||||||
.:+ "f5vpn"
|
.:? "ethPrefix"
|
||||||
|
<*> o
|
||||||
|
.:? "wifiPrefix"
|
||||||
|
|
||||||
defParams :: XParams
|
defParams :: XParams
|
||||||
defParams =
|
defParams =
|
||||||
|
@ -472,7 +521,8 @@ defXPFeatures =
|
||||||
, xpfIntelBacklight = False
|
, xpfIntelBacklight = False
|
||||||
, xpfClevoBacklight = False
|
, xpfClevoBacklight = False
|
||||||
, xpfBattery = False
|
, xpfBattery = False
|
||||||
, xpfF5VPN = False
|
, xpfEthPrefix = Nothing
|
||||||
|
, xpfWifiPrefix = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
type XPQuery = XPFeatures -> Bool
|
type XPQuery = XPFeatures -> Bool
|
||||||
|
@ -483,7 +533,7 @@ getParams = do
|
||||||
maybe (return defParams) (liftIO . decodeYaml) p
|
maybe (return defParams) (liftIO . decodeYaml) p
|
||||||
where
|
where
|
||||||
decodeYaml p =
|
decodeYaml p =
|
||||||
either (\e -> print e >> return defParams) return
|
either (\e -> TI.putStrLn (T.pack $ show e) >> return defParams) return
|
||||||
=<< decodeFileEither p
|
=<< decodeFileEither p
|
||||||
|
|
||||||
getParamFile :: MonadIO m => m (Maybe FilePath)
|
getParamFile :: MonadIO m => m (Maybe FilePath)
|
||||||
|
@ -507,9 +557,9 @@ infix 9 .:+
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Testing pipeline
|
-- Testing pipeline
|
||||||
|
|
||||||
evalSometimesMsg :: Sometimes a -> FIO (Either [FMsg] (a, [FMsg]))
|
evalSometimesMsg :: Sometimes a -> XIO (Either [FMsg] (a, [FMsg]))
|
||||||
evalSometimesMsg (Sometimes n u xs) = do
|
evalSometimesMsg (Sometimes n u xs) = do
|
||||||
r <- asks (u . xpFeatures . dsParams)
|
r <- asks (u . xpFeatures . xParams)
|
||||||
if not r
|
if not r
|
||||||
then return $ Left [dis n]
|
then return $ Left [dis n]
|
||||||
else do
|
else do
|
||||||
|
@ -521,7 +571,7 @@ evalSometimesMsg (Sometimes n u xs) = do
|
||||||
where
|
where
|
||||||
dis name = FMsg name Nothing (Msg LevelDebug "feature disabled")
|
dis name = FMsg name Nothing (Msg LevelDebug "feature disabled")
|
||||||
|
|
||||||
evalAlwaysMsg :: Always a -> FIO (a, [FMsg])
|
evalAlwaysMsg :: Always a -> XIO (a, [FMsg])
|
||||||
evalAlwaysMsg (Always n x) = do
|
evalAlwaysMsg (Always n x) = do
|
||||||
r <- testAlways x
|
r <- testAlways x
|
||||||
return $ case r of
|
return $ case r of
|
||||||
|
@ -541,7 +591,7 @@ failedMsg fn Subfeature {sfData = d, sfName = n} = case d of
|
||||||
where
|
where
|
||||||
f = fmap (FMsg fn (Just n))
|
f = fmap (FMsg fn (Just n))
|
||||||
|
|
||||||
testAlways :: Always_ a -> FIO (PostAlways a)
|
testAlways :: Always_ a -> XIO (PostAlways a)
|
||||||
testAlways = go []
|
testAlways = go []
|
||||||
where
|
where
|
||||||
go failed (Option fd next) = do
|
go failed (Option fd next) = do
|
||||||
|
@ -551,18 +601,18 @@ testAlways = go []
|
||||||
(Right pass) -> return $ Primary pass failed next
|
(Right pass) -> return $ Primary pass failed next
|
||||||
go failed (Always_ ar) = (`Fallback` failed) <$> evalFallbackRoot ar
|
go failed (Always_ ar) = (`Fallback` failed) <$> evalFallbackRoot ar
|
||||||
|
|
||||||
evalFallbackRoot :: FallbackRoot a -> FIO a
|
evalFallbackRoot :: FallbackRoot a -> XIO a
|
||||||
evalFallbackRoot (FallbackAlone a) = return a
|
evalFallbackRoot (FallbackAlone a) = return a
|
||||||
evalFallbackRoot (FallbackTree a s) = a <$> evalFallbackStack s
|
evalFallbackRoot (FallbackTree a s) = a <$> evalFallbackStack s
|
||||||
|
|
||||||
evalFallbackStack :: FallbackStack p -> FIO p
|
evalFallbackStack :: FallbackStack p -> XIO p
|
||||||
evalFallbackStack (FallbackBottom a) = evalAlways a
|
evalFallbackStack (FallbackBottom a) = evalAlways a
|
||||||
evalFallbackStack (FallbackStack f a as) = do
|
evalFallbackStack (FallbackStack f a as) = do
|
||||||
ps <- evalFallbackStack as
|
ps <- evalFallbackStack as
|
||||||
p <- evalAlways a
|
p <- evalAlways a
|
||||||
return $ f p ps
|
return $ f p ps
|
||||||
|
|
||||||
testSometimes :: Sometimes_ a -> FIO (PostSometimes a)
|
testSometimes :: Sometimes_ a -> XIO (PostSometimes a)
|
||||||
testSometimes = go (PostSometimes Nothing [])
|
testSometimes = go (PostSometimes Nothing [])
|
||||||
where
|
where
|
||||||
go ts [] = return ts
|
go ts [] = return ts
|
||||||
|
@ -572,13 +622,13 @@ testSometimes = go (PostSometimes Nothing [])
|
||||||
(Left l) -> go (ts {psFailed = l : psFailed ts}) xs
|
(Left l) -> go (ts {psFailed = l : psFailed ts}) xs
|
||||||
(Right pass) -> return $ ts {psSuccess = Just pass}
|
(Right pass) -> return $ ts {psSuccess = Just pass}
|
||||||
|
|
||||||
testSubfeature :: SubfeatureRoot a -> FIO (Either SubfeatureFail (SubfeaturePass a))
|
testSubfeature :: SubfeatureRoot a -> XIO (Either SubfeatureFail (SubfeaturePass a))
|
||||||
testSubfeature sf@Subfeature {sfData = t} = do
|
testSubfeature sf@Subfeature {sfData = t} = do
|
||||||
t' <- testRoot t
|
t' <- testRoot t
|
||||||
-- monomorphism restriction :(
|
-- monomorphism restriction :(
|
||||||
return $ bimap (\n -> sf {sfData = n}) (\n -> sf {sfData = n}) t'
|
return $ bimap (\n -> sf {sfData = n}) (\n -> sf {sfData = n}) t'
|
||||||
|
|
||||||
testRoot :: Root a -> FIO (Either PostFail (PostPass a))
|
testRoot :: Root a -> XIO (Either PostFail (PostPass a))
|
||||||
testRoot r = do
|
testRoot r = do
|
||||||
case r of
|
case r of
|
||||||
(IORoot a t) -> go a testIODep_ testIODep t
|
(IORoot a t) -> go a testIODep_ testIODep t
|
||||||
|
@ -592,7 +642,7 @@ testRoot r = do
|
||||||
Msg LevelError "client not available"
|
Msg LevelError "client not available"
|
||||||
where
|
where
|
||||||
-- rank N polymorphism is apparently undecidable...gross
|
-- rank N polymorphism is apparently undecidable...gross
|
||||||
go a f_ (f :: forall q. d q -> FIO (MResult q)) t =
|
go a f_ (f :: forall q. d q -> XIO (MResult q)) t =
|
||||||
bimap PostFail (fmap a) <$> testTree f_ f t
|
bimap PostFail (fmap a) <$> testTree f_ f t
|
||||||
go_ a f_ t = bimap PostFail (PostPass a) <$> testTree_ f_ t
|
go_ a f_ t = bimap PostFail (PostPass a) <$> testTree_ f_ t
|
||||||
|
|
||||||
|
@ -605,13 +655,13 @@ type MResult p = Memoized (Result p)
|
||||||
|
|
||||||
testTree
|
testTree
|
||||||
:: forall d d_ p
|
:: forall d d_ p
|
||||||
. (d_ -> FIO MResult_)
|
. (d_ -> XIO MResult_)
|
||||||
-> (forall q. d q -> FIO (MResult q))
|
-> (forall q. d q -> XIO (MResult q))
|
||||||
-> Tree d d_ p
|
-> Tree d d_ p
|
||||||
-> FIO (Either [Msg] (PostPass p))
|
-> XIO (Either [Msg] (PostPass p))
|
||||||
testTree test_ test = go
|
testTree test_ test = go
|
||||||
where
|
where
|
||||||
go :: forall q. Tree d d_ q -> FIO (Result q)
|
go :: forall q. Tree d d_ q -> XIO (Result q)
|
||||||
go (And12 f a b) = do
|
go (And12 f a b) = do
|
||||||
ra <- go a
|
ra <- go a
|
||||||
liftRight (\pa -> (and2nd f pa =<<) <$> go b) ra
|
liftRight (\pa -> (and2nd f pa =<<) <$> go b) ra
|
||||||
|
@ -628,7 +678,7 @@ testTree test_ test = go
|
||||||
and2nd f (PostPass pa wa) (PostPass pb wb) = Right $ PostPass (f pa pb) $ wa ++ wb
|
and2nd f (PostPass pa wa) (PostPass pb wb) = Right $ PostPass (f pa pb) $ wa ++ wb
|
||||||
liftRight = either (return . Left)
|
liftRight = either (return . Left)
|
||||||
|
|
||||||
testIODep :: IODependency p -> FIO (MResult p)
|
testIODep :: IODependency p -> XIO (MResult p)
|
||||||
testIODep d = memoizeMVar $ case d of
|
testIODep d = memoizeMVar $ case d of
|
||||||
IORead _ _ t -> t
|
IORead _ _ t -> t
|
||||||
IOConst c -> return $ Right $ PostPass c []
|
IOConst c -> return $ Right $ PostPass c []
|
||||||
|
@ -656,7 +706,7 @@ type Result_ = Either [Msg] [Msg]
|
||||||
|
|
||||||
type MResult_ = Memoized Result_
|
type MResult_ = Memoized Result_
|
||||||
|
|
||||||
testTree_ :: (d -> FIO MResult_) -> Tree_ d -> FIO Result_
|
testTree_ :: (d -> XIO MResult_) -> Tree_ d -> XIO Result_
|
||||||
testTree_ test = go
|
testTree_ test = go
|
||||||
where
|
where
|
||||||
go (And_ a b) = either (return . Left) (`test2nd` b) =<< go a
|
go (And_ a b) = either (return . Left) (`test2nd` b) =<< go a
|
||||||
|
@ -664,10 +714,10 @@ testTree_ test = go
|
||||||
go (Only_ a) = runMemoized =<< test a
|
go (Only_ a) = runMemoized =<< test a
|
||||||
test2nd ws = fmap ((Right . (ws ++)) =<<) . go
|
test2nd ws = fmap ((Right . (ws ++)) =<<) . go
|
||||||
|
|
||||||
testIODep_ :: IODependency_ -> FIO MResult_
|
testIODep_ :: IODependency_ -> XIO MResult_
|
||||||
testIODep_ d = memoizeMVar $ testIODepNoCache_ d
|
testIODep_ d = memoizeMVar $ testIODepNoCache_ d
|
||||||
|
|
||||||
testIODepNoCache_ :: IODependency_ -> FIO Result_
|
testIODepNoCache_ :: IODependency_ -> XIO Result_
|
||||||
testIODepNoCache_ (IOSystem_ _ s) = readResult_ <$> testSysDependency s
|
testIODepNoCache_ (IOSystem_ _ s) = readResult_ <$> testSysDependency s
|
||||||
testIODepNoCache_ (IOTest_ _ _ t) = readResult_ <$> t
|
testIODepNoCache_ (IOTest_ _ _ t) = readResult_ <$> t
|
||||||
testIODepNoCache_ (IOSometimes_ x) =
|
testIODepNoCache_ (IOSometimes_ x) =
|
||||||
|
@ -762,7 +812,7 @@ fontDependency_ fam ful = IOTest_ (fontTestName fam) ful $ voidRead <$> testFont
|
||||||
fontTestName :: T.Text -> T.Text
|
fontTestName :: T.Text -> T.Text
|
||||||
fontTestName fam = T.unwords ["test if font", singleQuote fam, "exists"]
|
fontTestName fam = T.unwords ["test if font", singleQuote fam, "exists"]
|
||||||
|
|
||||||
-- testFont :: T.Text -> FIO (Result FontBuilder)
|
-- testFont :: T.Text -> XIO (Result FontBuilder)
|
||||||
-- testFont = liftIO . testFont'
|
-- testFont = liftIO . testFont'
|
||||||
|
|
||||||
testFont
|
testFont
|
||||||
|
@ -783,16 +833,16 @@ testFont fam = maybe pass (Left . (: [])) <$> shellTest "fc-list" args msg
|
||||||
-- start with "en" and wireless interfaces always start with "wl"
|
-- start with "en" and wireless interfaces always start with "wl"
|
||||||
|
|
||||||
readEthernet :: IODependency T.Text
|
readEthernet :: IODependency T.Text
|
||||||
readEthernet = readInterface "get ethernet interface" isEthernet
|
readEthernet = readInterface "get ethernet interface" (fromMaybe "en" . xpfEthPrefix)
|
||||||
|
|
||||||
readWireless :: IODependency T.Text
|
readWireless :: IODependency T.Text
|
||||||
readWireless = readInterface "get wireless interface" isWireless
|
readWireless = readInterface "get wireless interface" (fromMaybe "wl" . xpfWifiPrefix)
|
||||||
|
|
||||||
isWireless :: T.Text -> Bool
|
-- isWireless :: T.Text -> Bool
|
||||||
isWireless = T.isPrefixOf "wl"
|
-- isWireless = T.isPrefixOf "wl"
|
||||||
|
|
||||||
isEthernet :: T.Text -> Bool
|
-- isEthernet :: T.Text -> Bool
|
||||||
isEthernet = T.isPrefixOf "en"
|
-- isEthernet = T.isPrefixOf "en"
|
||||||
|
|
||||||
listInterfaces :: MonadUnliftIO m => m [T.Text]
|
listInterfaces :: MonadUnliftIO m => m [T.Text]
|
||||||
listInterfaces =
|
listInterfaces =
|
||||||
|
@ -804,11 +854,12 @@ sysfsNet = "/sys/class/net"
|
||||||
|
|
||||||
-- ASSUME there are no (non-base) packages required to make these interfaces
|
-- ASSUME there are no (non-base) packages required to make these interfaces
|
||||||
-- work (all at the kernel level)
|
-- work (all at the kernel level)
|
||||||
readInterface :: T.Text -> (T.Text -> Bool) -> IODependency T.Text
|
readInterface :: T.Text -> (XPFeatures -> Text) -> IODependency T.Text
|
||||||
readInterface n f = IORead n [] go
|
readInterface n f = IORead n [] go
|
||||||
where
|
where
|
||||||
go = io $ do
|
go = do
|
||||||
ns <- filter f <$> listInterfaces
|
p <- asks (f . xpFeatures . xParams)
|
||||||
|
ns <- filter (T.isPrefixOf p) <$> listInterfaces
|
||||||
case ns of
|
case ns of
|
||||||
[] -> return $ Left [Msg LevelError "no interfaces found"]
|
[] -> return $ Left [Msg LevelError "no interfaces found"]
|
||||||
(x : xs) -> do
|
(x : xs) -> do
|
||||||
|
@ -820,7 +871,7 @@ readInterface n f = IORead n [] go
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Misc testers
|
-- Misc testers
|
||||||
|
|
||||||
socketExists :: T.Text -> [Fulfillment] -> FIO FilePath -> IODependency_
|
socketExists :: T.Text -> [Fulfillment] -> XIO FilePath -> IODependency_
|
||||||
socketExists n ful =
|
socketExists n ful =
|
||||||
IOTest_ (T.unwords ["test if", n, "socket exists"]) ful . socketExists'
|
IOTest_ (T.unwords ["test if", n, "socket exists"]) ful . socketExists'
|
||||||
|
|
||||||
|
@ -844,12 +895,12 @@ introspectInterface = interfaceName_ "org.freedesktop.DBus.Introspectable"
|
||||||
introspectMethod :: MemberName
|
introspectMethod :: MemberName
|
||||||
introspectMethod = memberName_ "Introspect"
|
introspectMethod = memberName_ "Introspect"
|
||||||
|
|
||||||
testDBusDep_ :: SafeClient c => c -> DBusDependency_ c -> FIO MResult_
|
testDBusDep_ :: SafeClient c => NamedConnection c -> DBusDependency_ c -> XIO MResult_
|
||||||
testDBusDep_ c d = memoizeMVar $ testDBusDepNoCache_ c d
|
testDBusDep_ c d = memoizeMVar $ testDBusDepNoCache_ c d
|
||||||
|
|
||||||
testDBusDepNoCache_ :: SafeClient c => c -> DBusDependency_ c -> FIO Result_
|
testDBusDepNoCache_ :: SafeClient c => NamedConnection c -> DBusDependency_ c -> XIO Result_
|
||||||
testDBusDepNoCache_ cl (Bus _ bus) = io $ do
|
testDBusDepNoCache_ cl (Bus _ bus) = do
|
||||||
ret <- callMethod cl queryBus queryPath queryIface queryMem
|
ret <- withDIO cl $ callMethod queryBus queryPath queryIface queryMem
|
||||||
return $ case ret of
|
return $ case ret of
|
||||||
Left e -> Left [Msg LevelError e]
|
Left e -> Left [Msg LevelError e]
|
||||||
Right b ->
|
Right b ->
|
||||||
|
@ -868,8 +919,10 @@ testDBusDepNoCache_ cl (Bus _ bus) = io $ do
|
||||||
queryMem = memberName_ "ListNames"
|
queryMem = memberName_ "ListNames"
|
||||||
bodyGetNames [v] = fromMaybe [] $ fromVariant v :: [T.Text]
|
bodyGetNames [v] = fromMaybe [] $ fromVariant v :: [T.Text]
|
||||||
bodyGetNames _ = []
|
bodyGetNames _ = []
|
||||||
testDBusDepNoCache_ cl (Endpoint _ busname objpath iface mem) = io $ do
|
testDBusDepNoCache_ cl (Endpoint _ busname objpath iface mem) = do
|
||||||
ret <- callMethod cl busname objpath introspectInterface introspectMethod
|
ret <-
|
||||||
|
withDIO cl $
|
||||||
|
callMethod busname objpath introspectInterface introspectMethod
|
||||||
return $ case ret of
|
return $ case ret of
|
||||||
Left e -> Left [Msg LevelError e]
|
Left e -> Left [Msg LevelError e]
|
||||||
Right body -> procBody body
|
Right body -> procBody body
|
||||||
|
@ -925,12 +978,6 @@ testDBusDepNoCache_ _ (DBusIO i) = testIODepNoCache_ i
|
||||||
-- ioSubfeature :: MonadIO m => SubfeatureRoot (IO a) -> SubfeatureRoot (m a)
|
-- ioSubfeature :: MonadIO m => SubfeatureRoot (IO a) -> SubfeatureRoot (m a)
|
||||||
-- ioSubfeature sf = sf {sfData = ioRoot $ sfData sf}
|
-- ioSubfeature sf = sf {sfData = ioRoot $ sfData sf}
|
||||||
|
|
||||||
-- ioRoot :: MonadIO m => Root (IO a) -> Root (m a)
|
|
||||||
-- ioRoot (IORoot a t) = IORoot (io . a) t
|
|
||||||
-- ioRoot (IORoot_ a t) = IORoot_ (io a) t
|
|
||||||
-- ioRoot (DBusRoot a t cl) = DBusRoot (\p c -> io $ a p c) t cl
|
|
||||||
-- ioRoot (DBusRoot_ a t cl) = DBusRoot_ (io . a) t cl
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Feature constructors
|
-- Feature constructors
|
||||||
|
|
||||||
|
@ -982,16 +1029,17 @@ sometimesExeArgs fn n ful sys path args =
|
||||||
|
|
||||||
sometimesDBus
|
sometimesDBus
|
||||||
:: SafeClient c
|
:: SafeClient c
|
||||||
=> Maybe c
|
=> Maybe (NamedConnection c)
|
||||||
-> T.Text
|
-> T.Text
|
||||||
-> T.Text
|
-> T.Text
|
||||||
-> Tree_ (DBusDependency_ c)
|
-> Tree_ (DBusDependency_ c)
|
||||||
-> (c -> a)
|
-> (NamedConnection c -> a)
|
||||||
-> Sometimes a
|
-> Sometimes a
|
||||||
sometimesDBus c fn n t x = sometimes1 fn n $ DBusRoot_ x t c
|
sometimesDBus c fn n t x = sometimes1 fn n $ DBusRoot_ x t c
|
||||||
|
|
||||||
|
-- TODO do I need to hardcode XEnv?
|
||||||
sometimesEndpoint
|
sometimesEndpoint
|
||||||
:: (SafeClient c, MonadIO m)
|
:: (HasClient (DBusEnv env), SafeClient c, MonadReader env m, MonadUnliftIO m)
|
||||||
=> T.Text
|
=> T.Text
|
||||||
-> T.Text
|
-> T.Text
|
||||||
-> [Fulfillment]
|
-> [Fulfillment]
|
||||||
|
@ -999,13 +1047,13 @@ sometimesEndpoint
|
||||||
-> ObjectPath
|
-> ObjectPath
|
||||||
-> InterfaceName
|
-> InterfaceName
|
||||||
-> MemberName
|
-> MemberName
|
||||||
-> Maybe c
|
-> Maybe (NamedConnection c)
|
||||||
-> Sometimes (m ())
|
-> Sometimes (m ())
|
||||||
sometimesEndpoint fn name ful busname path iface mem cl =
|
sometimesEndpoint fn name ful busname path iface mem cl =
|
||||||
sometimesDBus cl fn name deps cmd
|
sometimesDBus cl fn name deps cmd
|
||||||
where
|
where
|
||||||
deps = Only_ $ Endpoint ful busname path iface $ Method_ mem
|
deps = Only_ $ Endpoint ful busname path iface $ Method_ mem
|
||||||
cmd c = io $ void $ callMethod c busname path iface mem
|
cmd c = void $ withDIO c $ callMethod busname path iface mem
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Dependency Tree Constructors
|
-- Dependency Tree Constructors
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Dmenu (Rofi) Commands
|
-- Dmenu (Rofi) Commands
|
||||||
|
|
||||||
|
@ -19,15 +17,18 @@ module XMonad.Internal.Command.DMenu
|
||||||
where
|
where
|
||||||
|
|
||||||
import DBus
|
import DBus
|
||||||
|
import qualified Data.ByteString.Char8 as BC
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import Graphics.X11.Types
|
import Graphics.X11.Types
|
||||||
|
import RIO
|
||||||
|
import qualified RIO.ByteString as B
|
||||||
import RIO.Directory
|
import RIO.Directory
|
||||||
( XdgDirectory (..)
|
( XdgDirectory (..)
|
||||||
, getXdgDirectory
|
, getXdgDirectory
|
||||||
)
|
)
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import System.IO
|
-- import System.IO
|
||||||
import XMonad.Core hiding (spawn)
|
import XMonad.Core hiding (spawn)
|
||||||
import XMonad.Internal.Command.Desktop
|
import XMonad.Internal.Command.Desktop
|
||||||
import XMonad.Internal.DBus.Common
|
import XMonad.Internal.DBus.Common
|
||||||
|
@ -74,7 +75,7 @@ clipboardPkgs = [Package AUR "rofi-greenclip"]
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Other internal functions
|
-- Other internal functions
|
||||||
|
|
||||||
spawnDmenuCmd :: T.Text -> [T.Text] -> SometimesX
|
spawnDmenuCmd :: MonadUnliftIO m => T.Text -> [T.Text] -> Sometimes (m ())
|
||||||
spawnDmenuCmd n =
|
spawnDmenuCmd n =
|
||||||
sometimesExeArgs n "rofi preset" dmenuPkgs True myDmenuCmd
|
sometimesExeArgs n "rofi preset" dmenuPkgs True myDmenuCmd
|
||||||
|
|
||||||
|
@ -97,7 +98,7 @@ dmenuDep = sysExe dmenuPkgs myDmenuCmd
|
||||||
-- Exported Commands
|
-- Exported Commands
|
||||||
|
|
||||||
-- TODO test that veracrypt and friends are installed
|
-- TODO test that veracrypt and friends are installed
|
||||||
runDevMenu :: SometimesX
|
runDevMenu :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runDevMenu = sometimesIO_ "device manager" "rofi devices" t x
|
runDevMenu = sometimesIO_ "device manager" "rofi devices" t x
|
||||||
where
|
where
|
||||||
t = dmenuTree $ Only_ (localExe [] myDmenuDevices)
|
t = dmenuTree $ Only_ (localExe [] myDmenuDevices)
|
||||||
|
@ -110,7 +111,7 @@ runDevMenu = sometimesIO_ "device manager" "rofi devices" t x
|
||||||
++ myDmenuMatchingArgs
|
++ myDmenuMatchingArgs
|
||||||
|
|
||||||
-- TODO test that bluetooth interface exists
|
-- TODO test that bluetooth interface exists
|
||||||
runBTMenu :: SometimesX
|
runBTMenu :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runBTMenu =
|
runBTMenu =
|
||||||
Sometimes
|
Sometimes
|
||||||
"bluetooth selector"
|
"bluetooth selector"
|
||||||
|
@ -120,7 +121,7 @@ runBTMenu =
|
||||||
cmd = spawnCmd myDmenuBluetooth $ "-c" : themeArgs "#0044bb"
|
cmd = spawnCmd myDmenuBluetooth $ "-c" : themeArgs "#0044bb"
|
||||||
tree = dmenuTree $ Only_ $ sysExe [] myDmenuBluetooth
|
tree = dmenuTree $ Only_ $ sysExe [] myDmenuBluetooth
|
||||||
|
|
||||||
runVPNMenu :: SometimesX
|
runVPNMenu :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runVPNMenu =
|
runVPNMenu =
|
||||||
Sometimes
|
Sometimes
|
||||||
"VPN selector"
|
"VPN selector"
|
||||||
|
@ -136,16 +137,16 @@ runVPNMenu =
|
||||||
socketExists "expressVPN" [] $
|
socketExists "expressVPN" [] $
|
||||||
return "/var/lib/expressvpn/expressvpnd.socket"
|
return "/var/lib/expressvpn/expressvpnd.socket"
|
||||||
|
|
||||||
runCmdMenu :: SometimesX
|
runCmdMenu :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runCmdMenu = spawnDmenuCmd "command menu" ["-show", "run"]
|
runCmdMenu = spawnDmenuCmd "command menu" ["-show", "run"]
|
||||||
|
|
||||||
runAppMenu :: SometimesX
|
runAppMenu :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runAppMenu = spawnDmenuCmd "app launcher" ["-show", "drun"]
|
runAppMenu = spawnDmenuCmd "app launcher" ["-show", "drun"]
|
||||||
|
|
||||||
runWinMenu :: SometimesX
|
runWinMenu :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runWinMenu = spawnDmenuCmd "window switcher" ["-show", "window"]
|
runWinMenu = spawnDmenuCmd "window switcher" ["-show", "window"]
|
||||||
|
|
||||||
runNetMenu :: Maybe SysClient -> SometimesX
|
runNetMenu :: MonadUnliftIO m => Maybe NamedSysConnection -> Sometimes (m ())
|
||||||
runNetMenu cl =
|
runNetMenu cl =
|
||||||
Sometimes
|
Sometimes
|
||||||
"network control menu"
|
"network control menu"
|
||||||
|
@ -161,7 +162,7 @@ runNetMenu cl =
|
||||||
DBusIO $
|
DBusIO $
|
||||||
sysExe [Package AUR "networkmanager-dmenu-git"] myDmenuNetworks
|
sysExe [Package AUR "networkmanager-dmenu-git"] myDmenuNetworks
|
||||||
|
|
||||||
runAutorandrMenu :: SometimesX
|
runAutorandrMenu :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runAutorandrMenu = sometimesIO_ "autorandr menu" "rofi autorandr" tree cmd
|
runAutorandrMenu = sometimesIO_ "autorandr menu" "rofi autorandr" tree cmd
|
||||||
where
|
where
|
||||||
cmd = spawnCmd myDmenuMonitors $ themeArgs "#ff0066"
|
cmd = spawnCmd myDmenuMonitors $ themeArgs "#ff0066"
|
||||||
|
@ -170,7 +171,7 @@ runAutorandrMenu = sometimesIO_ "autorandr menu" "rofi autorandr" tree cmd
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Password manager
|
-- Password manager
|
||||||
|
|
||||||
runBwMenu :: Maybe SesClient -> SometimesX
|
runBwMenu :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ())
|
||||||
runBwMenu cl = sometimesDBus cl "password manager" "rofi bitwarden" tree cmd
|
runBwMenu cl = sometimesDBus cl "password manager" "rofi bitwarden" tree cmd
|
||||||
where
|
where
|
||||||
cmd _ =
|
cmd _ =
|
||||||
|
@ -183,7 +184,7 @@ runBwMenu cl = sometimesDBus cl "password manager" "rofi bitwarden" tree cmd
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Clipboard
|
-- Clipboard
|
||||||
|
|
||||||
runClipMenu :: SometimesX
|
runClipMenu :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runClipMenu = sometimesIO_ "clipboard manager" "rofi greenclip" tree act
|
runClipMenu = sometimesIO_ "clipboard manager" "rofi greenclip" tree act
|
||||||
where
|
where
|
||||||
act = spawnCmd myDmenuCmd args
|
act = spawnCmd myDmenuCmd args
|
||||||
|
@ -206,7 +207,9 @@ runClipMenu = sometimesIO_ "clipboard manager" "rofi greenclip" tree act
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Shortcut menu
|
-- Shortcut menu
|
||||||
|
|
||||||
runShowKeys :: Always ([((KeyMask, KeySym), NamedAction)] -> X ())
|
runShowKeys
|
||||||
|
:: (MonadReader env m, MonadUnliftIO m)
|
||||||
|
=> Always ([((KeyMask, KeySym), NamedAction)] -> m ())
|
||||||
runShowKeys =
|
runShowKeys =
|
||||||
Always "keyboard menu" $
|
Always "keyboard menu" $
|
||||||
Option showKeysDMenu $
|
Option showKeysDMenu $
|
||||||
|
@ -219,18 +222,23 @@ runShowKeys =
|
||||||
spawnNotify $
|
spawnNotify $
|
||||||
defNoteError {body = Just $ Text "could not display keymap"}
|
defNoteError {body = Just $ Text "could not display keymap"}
|
||||||
|
|
||||||
showKeysDMenu :: SubfeatureRoot ([((KeyMask, KeySym), NamedAction)] -> X ())
|
showKeysDMenu
|
||||||
|
:: (MonadReader env m, MonadUnliftIO m)
|
||||||
|
=> SubfeatureRoot ([((KeyMask, KeySym), NamedAction)] -> m ())
|
||||||
showKeysDMenu =
|
showKeysDMenu =
|
||||||
Subfeature
|
Subfeature
|
||||||
{ sfName = "keyboard shortcut menu"
|
{ sfName = "keyboard shortcut menu"
|
||||||
, sfData = IORoot_ showKeys $ Only_ dmenuDep
|
, sfData = IORoot_ showKeys $ Only_ dmenuDep
|
||||||
}
|
}
|
||||||
|
|
||||||
showKeys :: [((KeyMask, KeySym), NamedAction)] -> X ()
|
showKeys
|
||||||
|
:: (MonadReader env m, MonadUnliftIO m)
|
||||||
|
=> [((KeyMask, KeySym), NamedAction)]
|
||||||
|
-> m ()
|
||||||
showKeys kbs = do
|
showKeys kbs = do
|
||||||
h <- spawnPipe cmd
|
h <- spawnPipe cmd
|
||||||
io $ hPutStr h $ unlines $ showKm kbs
|
B.hPut h $ BC.unlines $ BC.pack <$> showKm kbs
|
||||||
io $ hClose h
|
hClose h
|
||||||
where
|
where
|
||||||
cmd =
|
cmd =
|
||||||
fmtCmd myDmenuCmd $
|
fmtCmd myDmenuCmd $
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- General commands
|
-- General commands
|
||||||
|
|
||||||
|
@ -21,7 +19,8 @@ module XMonad.Internal.Command.Desktop
|
||||||
, runVolumeUp
|
, runVolumeUp
|
||||||
, runVolumeMute
|
, runVolumeMute
|
||||||
, runToggleBluetooth
|
, runToggleBluetooth
|
||||||
, runToggleEthernet
|
, runToggleNetworking
|
||||||
|
, runToggleWifi
|
||||||
, runRestart
|
, runRestart
|
||||||
, runRecompile
|
, runRecompile
|
||||||
, runAreaCapture
|
, runAreaCapture
|
||||||
|
@ -41,7 +40,7 @@ where
|
||||||
|
|
||||||
import DBus
|
import DBus
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import RIO
|
import RIO
|
||||||
import RIO.Directory
|
import RIO.Directory
|
||||||
import RIO.FilePath
|
import RIO.FilePath
|
||||||
|
@ -60,13 +59,13 @@ import XMonad.Operations
|
||||||
-- My Executables
|
-- My Executables
|
||||||
|
|
||||||
myTerm :: FilePath
|
myTerm :: FilePath
|
||||||
myTerm = "urxvt"
|
myTerm = "alacritty"
|
||||||
|
|
||||||
myCalc :: FilePath
|
myCalc :: FilePath
|
||||||
myCalc = "bc"
|
myCalc = "bc"
|
||||||
|
|
||||||
myBrowser :: FilePath
|
myBrowser :: FilePath
|
||||||
myBrowser = "brave"
|
myBrowser = "firefox"
|
||||||
|
|
||||||
myEditor :: FilePath
|
myEditor :: FilePath
|
||||||
myEditor = "emacsclient"
|
myEditor = "emacsclient"
|
||||||
|
@ -94,8 +93,7 @@ myNotificationCtrl = "dunstctl"
|
||||||
|
|
||||||
myTermPkgs :: [Fulfillment]
|
myTermPkgs :: [Fulfillment]
|
||||||
myTermPkgs =
|
myTermPkgs =
|
||||||
[ Package Official "rxvt-unicode"
|
[ Package Official "alacritty"
|
||||||
, Package Official "urxvt-perls"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
myEditorPkgs :: [Fulfillment]
|
myEditorPkgs :: [Fulfillment]
|
||||||
|
@ -110,6 +108,9 @@ bluetoothPkgs = [Package Official "bluez-utils"]
|
||||||
networkManagerPkgs :: [Fulfillment]
|
networkManagerPkgs :: [Fulfillment]
|
||||||
networkManagerPkgs = [Package Official "networkmanager"]
|
networkManagerPkgs = [Package Official "networkmanager"]
|
||||||
|
|
||||||
|
nmcli :: IODependency_
|
||||||
|
nmcli = sysExe networkManagerPkgs "nmcli"
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Misc constants
|
-- Misc constants
|
||||||
|
|
||||||
|
@ -119,10 +120,10 @@ volumeChangeSound = "smb_fireball.wav"
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Some nice apps
|
-- Some nice apps
|
||||||
|
|
||||||
runTerm :: SometimesX
|
runTerm :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runTerm = sometimesExe "terminal" "urxvt" myTermPkgs True myTerm
|
runTerm = sometimesExe "terminal" "alacritty" myTermPkgs True myTerm
|
||||||
|
|
||||||
runTMux :: SometimesX
|
runTMux :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runTMux = sometimesIO_ "terminal multiplexer" "tmux" deps act
|
runTMux = sometimesIO_ "terminal multiplexer" "tmux" deps act
|
||||||
where
|
where
|
||||||
deps =
|
deps =
|
||||||
|
@ -140,13 +141,13 @@ runTMux = sometimesIO_ "terminal multiplexer" "tmux" deps act
|
||||||
t <- getTemporaryDirectory
|
t <- getTemporaryDirectory
|
||||||
return $ t </> "tmux-" ++ show u </> "default"
|
return $ t </> "tmux-" ++ show u </> "default"
|
||||||
|
|
||||||
runCalc :: SometimesX
|
runCalc :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runCalc = sometimesIO_ "calculator" "bc" deps act
|
runCalc = sometimesIO_ "calculator" "bc" deps act
|
||||||
where
|
where
|
||||||
deps = toAnd_ (sysExe myTermPkgs myTerm) (sysExe [Package Official "bc"] myCalc)
|
deps = toAnd_ (sysExe myTermPkgs myTerm) (sysExe [Package Official "bc"] myCalc)
|
||||||
act = spawnCmd myTerm ["-e", T.pack myCalc, "-l"]
|
act = spawnCmd myTerm ["-e", T.pack myCalc, "-l"]
|
||||||
|
|
||||||
runBrowser :: SometimesX
|
runBrowser :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runBrowser =
|
runBrowser =
|
||||||
sometimesExe
|
sometimesExe
|
||||||
"web browser"
|
"web browser"
|
||||||
|
@ -155,7 +156,7 @@ runBrowser =
|
||||||
False
|
False
|
||||||
myBrowser
|
myBrowser
|
||||||
|
|
||||||
runEditor :: SometimesX
|
runEditor :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runEditor = sometimesIO_ "text editor" "emacs" tree cmd
|
runEditor = sometimesIO_ "text editor" "emacs" tree cmd
|
||||||
where
|
where
|
||||||
cmd =
|
cmd =
|
||||||
|
@ -166,7 +167,7 @@ runEditor = sometimesIO_ "text editor" "emacs" tree cmd
|
||||||
-- before xmonad starts, so just check to see if the process has started
|
-- before xmonad starts, so just check to see if the process has started
|
||||||
tree = toAnd_ (sysExe myEditorPkgs myEditor) $ process [] $ T.pack myEditorServer
|
tree = toAnd_ (sysExe myEditorPkgs myEditor) $ process [] $ T.pack myEditorServer
|
||||||
|
|
||||||
runFileManager :: SometimesX
|
runFileManager :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runFileManager =
|
runFileManager =
|
||||||
sometimesExe
|
sometimesExe
|
||||||
"file browser"
|
"file browser"
|
||||||
|
@ -178,7 +179,11 @@ runFileManager =
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Multimedia Commands
|
-- Multimedia Commands
|
||||||
|
|
||||||
runMultimediaIfInstalled :: T.Text -> T.Text -> SometimesX
|
runMultimediaIfInstalled
|
||||||
|
:: MonadUnliftIO m
|
||||||
|
=> T.Text
|
||||||
|
-> T.Text
|
||||||
|
-> Sometimes (m ())
|
||||||
runMultimediaIfInstalled n cmd =
|
runMultimediaIfInstalled n cmd =
|
||||||
sometimesExeArgs
|
sometimesExeArgs
|
||||||
(T.append n " multimedia control")
|
(T.append n " multimedia control")
|
||||||
|
@ -188,23 +193,23 @@ runMultimediaIfInstalled n cmd =
|
||||||
myMultimediaCtl
|
myMultimediaCtl
|
||||||
[cmd]
|
[cmd]
|
||||||
|
|
||||||
runTogglePlay :: SometimesX
|
runTogglePlay :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runTogglePlay = runMultimediaIfInstalled "play/pause" "play-pause"
|
runTogglePlay = runMultimediaIfInstalled "play/pause" "play-pause"
|
||||||
|
|
||||||
runPrevTrack :: SometimesX
|
runPrevTrack :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runPrevTrack = runMultimediaIfInstalled "previous track" "previous"
|
runPrevTrack = runMultimediaIfInstalled "previous track" "previous"
|
||||||
|
|
||||||
runNextTrack :: SometimesX
|
runNextTrack :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runNextTrack = runMultimediaIfInstalled "next track" "next"
|
runNextTrack = runMultimediaIfInstalled "next track" "next"
|
||||||
|
|
||||||
runStopPlay :: SometimesX
|
runStopPlay :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runStopPlay = runMultimediaIfInstalled "stop playback" "stop"
|
runStopPlay = runMultimediaIfInstalled "stop playback" "stop"
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Volume Commands
|
-- Volume Commands
|
||||||
|
|
||||||
soundDir :: FilePath
|
soundDir :: FilePath
|
||||||
soundDir = "sound"
|
soundDir = "assets" </> "sound"
|
||||||
|
|
||||||
playSound :: MonadIO m => FilePath -> m ()
|
playSound :: MonadIO m => FilePath -> m ()
|
||||||
playSound file = do
|
playSound file = do
|
||||||
|
@ -213,7 +218,13 @@ playSound file = do
|
||||||
-- paplay seems to have less latency than aplay
|
-- paplay seems to have less latency than aplay
|
||||||
spawnCmd "paplay" [T.pack p]
|
spawnCmd "paplay" [T.pack p]
|
||||||
|
|
||||||
featureSound :: T.Text -> FilePath -> X () -> X () -> SometimesX
|
featureSound
|
||||||
|
:: MonadUnliftIO m
|
||||||
|
=> T.Text
|
||||||
|
-> FilePath
|
||||||
|
-> m ()
|
||||||
|
-> m ()
|
||||||
|
-> Sometimes (m ())
|
||||||
featureSound n file pre post =
|
featureSound n file pre post =
|
||||||
sometimesIO_ (T.unwords ["volume", n, " control"]) "paplay" tree $
|
sometimesIO_ (T.unwords ["volume", n, " control"]) "paplay" tree $
|
||||||
pre >> playSound file >> post
|
pre >> playSound file >> post
|
||||||
|
@ -222,19 +233,24 @@ featureSound n file pre post =
|
||||||
-- to play sound (duh) but libpulse is the package with the paplay binary
|
-- to play sound (duh) but libpulse is the package with the paplay binary
|
||||||
tree = Only_ $ sysExe [Package Official "pulseaudio"] "paplay"
|
tree = Only_ $ sysExe [Package Official "pulseaudio"] "paplay"
|
||||||
|
|
||||||
runVolumeDown :: SometimesX
|
runVolumeDown :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runVolumeDown = featureSound "up" volumeChangeSound (return ()) $ void (lowerVolume 2)
|
runVolumeDown = featureSound "up" volumeChangeSound (return ()) $ void (lowerVolume 2)
|
||||||
|
|
||||||
runVolumeUp :: SometimesX
|
runVolumeUp :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runVolumeUp = featureSound "down" volumeChangeSound (return ()) $ void (raiseVolume 2)
|
runVolumeUp = featureSound "down" volumeChangeSound (return ()) $ void (raiseVolume 2)
|
||||||
|
|
||||||
runVolumeMute :: SometimesX
|
runVolumeMute :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runVolumeMute = featureSound "mute" volumeChangeSound (void toggleMute) $ return ()
|
runVolumeMute = featureSound "mute" volumeChangeSound (void toggleMute) $ return ()
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Notification control
|
-- Notification control
|
||||||
|
|
||||||
runNotificationCmd :: T.Text -> T.Text -> Maybe SesClient -> SometimesX
|
runNotificationCmd
|
||||||
|
:: MonadUnliftIO m
|
||||||
|
=> T.Text
|
||||||
|
-> T.Text
|
||||||
|
-> Maybe NamedSesConnection
|
||||||
|
-> Sometimes (m ())
|
||||||
runNotificationCmd n arg cl =
|
runNotificationCmd n arg cl =
|
||||||
sometimesDBus cl (T.unwords [n, "control"]) "dunstctl" tree cmd
|
sometimesDBus cl (T.unwords [n, "control"]) "dunstctl" tree cmd
|
||||||
where
|
where
|
||||||
|
@ -245,37 +261,37 @@ runNotificationCmd n arg cl =
|
||||||
Method_ $
|
Method_ $
|
||||||
memberName_ "NotificationAction"
|
memberName_ "NotificationAction"
|
||||||
|
|
||||||
runNotificationClose :: Maybe SesClient -> SometimesX
|
runNotificationClose :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ())
|
||||||
runNotificationClose = runNotificationCmd "close notification" "close"
|
runNotificationClose = runNotificationCmd "close notification" "close"
|
||||||
|
|
||||||
runNotificationCloseAll :: Maybe SesClient -> SometimesX
|
runNotificationCloseAll :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ())
|
||||||
runNotificationCloseAll =
|
runNotificationCloseAll =
|
||||||
runNotificationCmd "close all notifications" "close-all"
|
runNotificationCmd "close all notifications" "close-all"
|
||||||
|
|
||||||
runNotificationHistory :: Maybe SesClient -> SometimesX
|
runNotificationHistory :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ())
|
||||||
runNotificationHistory =
|
runNotificationHistory =
|
||||||
runNotificationCmd "see notification history" "history-pop"
|
runNotificationCmd "see notification history" "history-pop"
|
||||||
|
|
||||||
runNotificationContext :: Maybe SesClient -> SometimesX
|
runNotificationContext :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ())
|
||||||
runNotificationContext =
|
runNotificationContext =
|
||||||
runNotificationCmd "open notification context" "context"
|
runNotificationCmd "open notification context" "context"
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- System commands
|
-- System commands
|
||||||
|
|
||||||
-- this is required for some vpn's to work properly with network-manager
|
-- needed to lookup/prompt for passwords/keys for wifi connections and some VPNs
|
||||||
runNetAppDaemon :: Maybe SysClient -> Sometimes (FIO (P.Process () () ()))
|
runNetAppDaemon :: Maybe NamedSysConnection -> Sometimes (XIO (P.Process () () ()))
|
||||||
runNetAppDaemon cl =
|
runNetAppDaemon cl =
|
||||||
Sometimes
|
Sometimes
|
||||||
"network applet"
|
"network applet"
|
||||||
xpfVPN
|
(\x -> xpfVPN x || xpfWireless x)
|
||||||
[Subfeature (DBusRoot_ cmd tree cl) "NM-applet"]
|
[Subfeature (DBusRoot_ cmd tree cl) "NM-applet"]
|
||||||
where
|
where
|
||||||
tree = toAnd_ app $ Bus networkManagerPkgs networkManagerBus
|
tree = toAnd_ app $ Bus networkManagerPkgs networkManagerBus
|
||||||
app = DBusIO $ sysExe [Package Official "network-manager-applet"] "nm-applet"
|
app = DBusIO $ sysExe [Package Official "network-manager-applet"] "nm-applet"
|
||||||
cmd _ = P.proc "nm-applet" [] (P.startProcess . P.setCreateGroup True)
|
cmd _ = P.proc "nm-applet" [] (P.startProcess . P.setCreateGroup True)
|
||||||
|
|
||||||
runToggleBluetooth :: Maybe SysClient -> SometimesX
|
runToggleBluetooth :: MonadUnliftIO m => Maybe NamedSysConnection -> Sometimes (m ())
|
||||||
runToggleBluetooth cl =
|
runToggleBluetooth cl =
|
||||||
Sometimes
|
Sometimes
|
||||||
"bluetooth toggle"
|
"bluetooth toggle"
|
||||||
|
@ -292,27 +308,35 @@ runToggleBluetooth cl =
|
||||||
#!>> fmtCmd myBluetooth ["power", "$a", ">", "/dev/null"]
|
#!>> fmtCmd myBluetooth ["power", "$a", ">", "/dev/null"]
|
||||||
#!&& fmtNotifyCmd defNoteInfo {body = Just $ Text "bluetooth powered $a"}
|
#!&& fmtNotifyCmd defNoteInfo {body = Just $ Text "bluetooth powered $a"}
|
||||||
|
|
||||||
runToggleEthernet :: SometimesX
|
runToggleNetworking :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runToggleEthernet =
|
runToggleNetworking =
|
||||||
Sometimes
|
Sometimes
|
||||||
"ethernet toggle"
|
"network toggle"
|
||||||
xpfEthernet
|
(\x -> xpfEthernet x || xpfWireless x)
|
||||||
[Subfeature root "nmcli"]
|
[Subfeature root "nmcli"]
|
||||||
where
|
where
|
||||||
root =
|
root = IORoot_ cmd $ Only_ nmcli
|
||||||
IORoot cmd $
|
cmd =
|
||||||
And1 (Only readEthernet) $
|
|
||||||
Only_ $
|
|
||||||
sysExe networkManagerPkgs "nmcli"
|
|
||||||
-- TODO make this less noisy
|
|
||||||
cmd iface =
|
|
||||||
S.spawn $
|
S.spawn $
|
||||||
fmtCmd "nmcli" ["-g", "GENERAL.STATE", "device", "show", iface]
|
fmtCmd "nmcli" ["networking"]
|
||||||
#!| "grep -q disconnected"
|
#!| "grep -q enabled"
|
||||||
#!&& "a=connect"
|
#!&& "a=off"
|
||||||
#!|| "a=disconnect"
|
#!|| "a=on"
|
||||||
#!>> fmtCmd "nmcli" ["device", "$a", iface]
|
#!>> fmtCmd "nmcli" ["networking", "$a"]
|
||||||
#!&& fmtNotifyCmd defNoteInfo {body = Just $ Text "ethernet \"$a\"ed"}
|
#!&& fmtNotifyCmd defNoteInfo {body = Just $ Text "networking switched $a"}
|
||||||
|
|
||||||
|
runToggleWifi :: MonadUnliftIO m => Sometimes (m ())
|
||||||
|
runToggleWifi = Sometimes "wifi toggle" xpfWireless [Subfeature root "nmcli"]
|
||||||
|
where
|
||||||
|
root = IORoot_ cmd $ Only_ nmcli
|
||||||
|
cmd =
|
||||||
|
S.spawn $
|
||||||
|
fmtCmd "nmcli" ["radio", "wifi"]
|
||||||
|
#!| "grep -q enabled"
|
||||||
|
#!&& "a=off"
|
||||||
|
#!|| "a=on"
|
||||||
|
#!>> fmtCmd "nmcli" ["radio", "wifi", "$a"]
|
||||||
|
#!&& fmtNotifyCmd defNoteInfo {body = Just $ Text "wifi switched $a"}
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Configuration commands
|
-- Configuration commands
|
||||||
|
@ -320,6 +344,7 @@ runToggleEthernet =
|
||||||
runRestart :: X ()
|
runRestart :: X ()
|
||||||
runRestart = restart "xmonad" True
|
runRestart = restart "xmonad" True
|
||||||
|
|
||||||
|
-- TODO use rio in here so I don't have to fill my xinit log with stack poop
|
||||||
-- TODO only recompile the VM binary if we have virtualbox enabled
|
-- TODO only recompile the VM binary if we have virtualbox enabled
|
||||||
runRecompile :: X ()
|
runRecompile :: X ()
|
||||||
runRecompile = do
|
runRecompile = do
|
||||||
|
@ -348,7 +373,12 @@ getCaptureDir = do
|
||||||
where
|
where
|
||||||
fallback = (</> ".local/share") <$> getHomeDirectory
|
fallback = (</> ".local/share") <$> getHomeDirectory
|
||||||
|
|
||||||
runFlameshot :: T.Text -> T.Text -> Maybe SesClient -> SometimesX
|
runFlameshot
|
||||||
|
:: MonadUnliftIO m
|
||||||
|
=> T.Text
|
||||||
|
-> T.Text
|
||||||
|
-> Maybe NamedSesConnection
|
||||||
|
-> Sometimes (m ())
|
||||||
runFlameshot n mode cl = sometimesDBus cl n (T.pack myCapture) tree cmd
|
runFlameshot n mode cl = sometimesDBus cl n (T.pack myCapture) tree cmd
|
||||||
where
|
where
|
||||||
cmd _ = spawnCmd myCapture [mode]
|
cmd _ = spawnCmd myCapture [mode]
|
||||||
|
@ -359,18 +389,18 @@ runFlameshot n mode cl = sometimesDBus cl n (T.pack myCapture) tree cmd
|
||||||
|
|
||||||
-- TODO this will steal focus from the current window (and puts it
|
-- TODO this will steal focus from the current window (and puts it
|
||||||
-- in the root window?) ...need to fix
|
-- in the root window?) ...need to fix
|
||||||
runAreaCapture :: Maybe SesClient -> SometimesX
|
runAreaCapture :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ())
|
||||||
runAreaCapture = runFlameshot "screen area capture" "gui"
|
runAreaCapture = runFlameshot "screen area capture" "gui"
|
||||||
|
|
||||||
-- myWindowCap = "screencap -w" --external script
|
-- myWindowCap = "screencap -w" --external script
|
||||||
|
|
||||||
runDesktopCapture :: Maybe SesClient -> SometimesX
|
runDesktopCapture :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ())
|
||||||
runDesktopCapture = runFlameshot "fullscreen capture" "full"
|
runDesktopCapture = runFlameshot "fullscreen capture" "full"
|
||||||
|
|
||||||
runScreenCapture :: Maybe SesClient -> SometimesX
|
runScreenCapture :: MonadUnliftIO m => Maybe NamedSesConnection -> Sometimes (m ())
|
||||||
runScreenCapture = runFlameshot "screen capture" "screen"
|
runScreenCapture = runFlameshot "screen capture" "screen"
|
||||||
|
|
||||||
runCaptureBrowser :: SometimesX
|
runCaptureBrowser :: MonadUnliftIO m => Sometimes (m ())
|
||||||
runCaptureBrowser = sometimesIO_
|
runCaptureBrowser = sometimesIO_
|
||||||
"screen capture browser"
|
"screen capture browser"
|
||||||
"feh"
|
"feh"
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Commands for controlling power
|
-- Commands for controlling power
|
||||||
|
|
||||||
|
@ -26,7 +24,7 @@ module XMonad.Internal.Command.Power
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import Graphics.X11.Types
|
import Graphics.X11.Types
|
||||||
import RIO
|
import RIO
|
||||||
import RIO.Directory
|
import RIO.Directory
|
||||||
|
@ -69,29 +67,30 @@ runScreenLock =
|
||||||
False
|
False
|
||||||
myScreenlock
|
myScreenlock
|
||||||
|
|
||||||
runPowerOff :: X ()
|
runPowerOff :: MonadUnliftIO m => m ()
|
||||||
runPowerOff = spawn "systemctl poweroff"
|
runPowerOff = spawn "systemctl poweroff"
|
||||||
|
|
||||||
runSuspend :: X ()
|
runSuspend :: MonadUnliftIO m => m ()
|
||||||
runSuspend = spawn "systemctl suspend"
|
runSuspend = spawn "systemctl suspend"
|
||||||
|
|
||||||
runHibernate :: X ()
|
runHibernate :: MonadUnliftIO m => m ()
|
||||||
runHibernate = spawn "systemctl hibernate"
|
runHibernate = spawn "systemctl hibernate"
|
||||||
|
|
||||||
runReboot :: X ()
|
runReboot :: MonadUnliftIO m => m ()
|
||||||
runReboot = spawn "systemctl reboot"
|
runReboot = spawn "systemctl reboot"
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Autolock
|
-- Autolock
|
||||||
|
|
||||||
runAutolock :: Sometimes (FIO (P.Process () () ()))
|
runAutolock :: Sometimes (XIO (P.Process () () ()))
|
||||||
runAutolock = sometimesIO_ "automatic screen lock" "xss-lock" tree cmd
|
runAutolock = sometimesIO_ "automatic screen lock" "xss-lock" tree cmd
|
||||||
where
|
where
|
||||||
tree =
|
tree =
|
||||||
And_ (Only_ $ sysExe [Package Official "xss-lock"] "xss-lock") $
|
And_ (Only_ $ sysExe [Package Official "xss-lock"] "xss-lock") $
|
||||||
Only_ $
|
Only_ $
|
||||||
IOSometimes_ runScreenLock
|
IOSometimes_ runScreenLock
|
||||||
cmd = P.proc "xss-lock" ["--ignore-sleep", "screenlock"] (P.startProcess . P.setCreateGroup True)
|
cmd = P.proc "xss-lock" args (P.startProcess . P.setCreateGroup True)
|
||||||
|
args = ["--ignore-sleep", "--", "screenlock", "true"]
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Confirmation prompts
|
-- Confirmation prompts
|
||||||
|
@ -106,7 +105,7 @@ confirmPrompt' :: T.Text -> X () -> XT.FontBuilder -> X ()
|
||||||
confirmPrompt' s x fb = confirmPrompt (XT.promptTheme fb) (T.unpack s) x
|
confirmPrompt' s x fb = confirmPrompt (XT.promptTheme fb) (T.unpack s) x
|
||||||
|
|
||||||
suspendPrompt :: XT.FontBuilder -> X ()
|
suspendPrompt :: XT.FontBuilder -> X ()
|
||||||
suspendPrompt = confirmPrompt' "suspend?" runSuspend
|
suspendPrompt = confirmPrompt' "suspend?" $ liftIO runSuspend
|
||||||
|
|
||||||
quitPrompt :: XT.FontBuilder -> X ()
|
quitPrompt :: XT.FontBuilder -> X ()
|
||||||
quitPrompt = confirmPrompt' "quit?" $ io exitSuccess
|
quitPrompt = confirmPrompt' "quit?" $ io exitSuccess
|
||||||
|
@ -180,17 +179,20 @@ data PowerMaybeAction
|
||||||
| Reboot
|
| Reboot
|
||||||
deriving (Eq)
|
deriving (Eq)
|
||||||
|
|
||||||
instance Enum PowerMaybeAction where
|
fromPMA :: PowerMaybeAction -> Int
|
||||||
toEnum 0 = Poweroff
|
fromPMA a = case a of
|
||||||
toEnum 1 = Shutdown
|
Poweroff -> 0
|
||||||
toEnum 2 = Hibernate
|
Shutdown -> 1
|
||||||
toEnum 3 = Reboot
|
Hibernate -> 2
|
||||||
toEnum _ = errorWithoutStackTrace "Main.Enum.PowerMaybeAction.toEnum: bad argument"
|
Reboot -> 3
|
||||||
|
|
||||||
fromEnum Poweroff = 0
|
toPMA :: Int -> Maybe PowerMaybeAction
|
||||||
fromEnum Shutdown = 1
|
toPMA x = case x of
|
||||||
fromEnum Hibernate = 2
|
0 -> Just Poweroff
|
||||||
fromEnum Reboot = 3
|
1 -> Just Shutdown
|
||||||
|
2 -> Just Hibernate
|
||||||
|
3 -> Just Reboot
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
data PowerPrompt = PowerPrompt
|
data PowerPrompt = PowerPrompt
|
||||||
|
|
||||||
|
@ -222,9 +224,11 @@ powerPrompt lock fb = mkXPrompt PowerPrompt theme comp executeMaybeAction
|
||||||
, (xK_Return, quit)
|
, (xK_Return, quit)
|
||||||
, (xK_Escape, quit)
|
, (xK_Escape, quit)
|
||||||
]
|
]
|
||||||
sendMaybeAction a = setInput (show $ fromEnum a) >> setSuccess True >> setDone True
|
sendMaybeAction a = setInput (show $ fromPMA a) >> setSuccess True >> setDone True
|
||||||
executeMaybeAction a = case toEnum $ read a of
|
executeMaybeAction a = case toPMA =<< readMaybe a of
|
||||||
Poweroff -> runPowerOff
|
Just Poweroff -> liftIO runPowerOff
|
||||||
Shutdown -> lock >> runSuspend
|
Just Shutdown -> lock >> liftIO runSuspend
|
||||||
Hibernate -> lock >> runHibernate
|
Just Hibernate -> lock >> liftIO runHibernate
|
||||||
Reboot -> runReboot
|
Just Reboot -> liftIO runReboot
|
||||||
|
-- TODO log an error here since this should never happen
|
||||||
|
Nothing -> skip
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
{-# LANGUAGE LambdaCase #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Concurrent module to handle events from acpid
|
-- Concurrent module to handle events from acpid
|
||||||
|
|
||||||
|
@ -10,7 +7,7 @@ module XMonad.Internal.Concurrent.ACPIEvent
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import Network.Socket
|
import Network.Socket
|
||||||
import Network.Socket.ByteString
|
import Network.Socket.ByteString
|
||||||
import RIO
|
import RIO
|
||||||
|
@ -33,15 +30,18 @@ data ACPIEvent
|
||||||
| LidClose
|
| LidClose
|
||||||
deriving (Eq)
|
deriving (Eq)
|
||||||
|
|
||||||
instance Enum ACPIEvent where
|
fromACPIEvent :: ACPIEvent -> Int
|
||||||
toEnum 0 = Power
|
fromACPIEvent x = case x of
|
||||||
toEnum 1 = Sleep
|
Power -> 0
|
||||||
toEnum 2 = LidClose
|
Sleep -> 1
|
||||||
toEnum _ = errorWithoutStackTrace "ACPI.Enum.ACPIEvent.toEnum: bad argument"
|
LidClose -> 2
|
||||||
|
|
||||||
fromEnum Power = 0
|
toACPIEvent :: Int -> Maybe ACPIEvent
|
||||||
fromEnum Sleep = 1
|
toACPIEvent x = case x of
|
||||||
fromEnum LidClose = 2
|
0 -> Just Power
|
||||||
|
1 -> Just Sleep
|
||||||
|
2 -> Just LidClose
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Internal functions
|
-- Internal functions
|
||||||
|
@ -64,7 +64,7 @@ parseLine line =
|
||||||
|
|
||||||
-- | Send an ACPIEvent to the X server as a ClientMessage
|
-- | Send an ACPIEvent to the X server as a ClientMessage
|
||||||
sendACPIEvent :: ACPIEvent -> IO ()
|
sendACPIEvent :: ACPIEvent -> IO ()
|
||||||
sendACPIEvent = sendXMsg ACPI . show . fromEnum
|
sendACPIEvent = sendXMsg ACPI . show . fromACPIEvent
|
||||||
|
|
||||||
isDischarging :: IO (Maybe Bool)
|
isDischarging :: IO (Maybe Bool)
|
||||||
isDischarging = do
|
isDischarging = do
|
||||||
|
@ -91,14 +91,14 @@ socketDep = Only_ $ pathR acpiPath [Package Official "acpid"]
|
||||||
-- Xmonad's event hook)
|
-- Xmonad's event hook)
|
||||||
handleACPI :: FontBuilder -> X () -> String -> X ()
|
handleACPI :: FontBuilder -> X () -> String -> X ()
|
||||||
handleACPI fb lock tag = do
|
handleACPI fb lock tag = do
|
||||||
let acpiTag = toEnum <$> readMaybe tag :: Maybe ACPIEvent
|
let acpiTag = toACPIEvent =<< readMaybe tag :: Maybe ACPIEvent
|
||||||
forM_ acpiTag $ \case
|
forM_ acpiTag $ \case
|
||||||
Power -> powerPrompt lock fb
|
Power -> powerPrompt lock fb
|
||||||
Sleep -> suspendPrompt fb
|
Sleep -> suspendPrompt fb
|
||||||
LidClose -> do
|
LidClose -> do
|
||||||
status <- io isDischarging
|
status <- io isDischarging
|
||||||
-- only run suspend if battery exists and is discharging
|
-- only run suspend if battery exists and is discharging
|
||||||
forM_ status $ flip when runSuspend
|
forM_ status $ flip when $ liftIO runSuspend
|
||||||
lock
|
lock
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
|
@ -28,6 +28,7 @@ import Graphics.X11.Xlib.Atom
|
||||||
import Graphics.X11.Xlib.Display
|
import Graphics.X11.Xlib.Display
|
||||||
import Graphics.X11.Xlib.Event
|
import Graphics.X11.Xlib.Event
|
||||||
import Graphics.X11.Xlib.Extras
|
import Graphics.X11.Xlib.Extras
|
||||||
|
import RIO
|
||||||
import XMonad.Internal.IO
|
import XMonad.Internal.IO
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
@ -42,14 +43,18 @@ data XMsgType
|
||||||
| Unknown
|
| Unknown
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
instance Enum XMsgType where
|
fromXMsgType :: XMsgType -> Int
|
||||||
toEnum 0 = ACPI
|
fromXMsgType x = case x of
|
||||||
toEnum 1 = Workspace
|
ACPI -> 0
|
||||||
toEnum _ = errorWithoutStackTrace "ACPI.Enum.ACPIEvent.toEnum: bad argument"
|
Workspace -> 1
|
||||||
|
Unknown -> 2
|
||||||
|
|
||||||
fromEnum ACPI = 0
|
toXMsgType :: Int -> Maybe XMsgType
|
||||||
fromEnum Workspace = 1
|
toXMsgType x = case x of
|
||||||
fromEnum Unknown = 2
|
0 -> Just ACPI
|
||||||
|
1 -> Just Workspace
|
||||||
|
2 -> Just Unknown
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Exported API
|
-- Exported API
|
||||||
|
@ -58,9 +63,9 @@ instance Enum XMsgType where
|
||||||
-- type and payload
|
-- type and payload
|
||||||
splitXMsg :: (Integral a) => [a] -> (XMsgType, String)
|
splitXMsg :: (Integral a) => [a] -> (XMsgType, String)
|
||||||
splitXMsg [] = (Unknown, "")
|
splitXMsg [] = (Unknown, "")
|
||||||
splitXMsg (x : xs) = (xtype, tag)
|
splitXMsg (x : xs) = (fromMaybe Unknown xtype, tag)
|
||||||
where
|
where
|
||||||
xtype = toEnum $ fromIntegral x
|
xtype = toXMsgType $ fromIntegral x
|
||||||
tag = chr . fromIntegral <$> takeWhile (/= 0) xs
|
tag = chr . fromIntegral <$> takeWhile (/= 0) xs
|
||||||
|
|
||||||
-- | Emit a ClientMessage event to the X server with the given type and payloud
|
-- | Emit a ClientMessage event to the X server with the given type and payloud
|
||||||
|
@ -86,5 +91,5 @@ sendXMsg xtype tag = withOpenDisplay $ \dpy -> do
|
||||||
setClientMessageEvent' e root bITMAP 8 (x : t)
|
setClientMessageEvent' e root bITMAP 8 (x : t)
|
||||||
sendEvent dpy root False substructureNotifyMask e
|
sendEvent dpy root False substructureNotifyMask e
|
||||||
where
|
where
|
||||||
x = fromIntegral $ fromEnum xtype
|
x = fromIntegral $ fromXMsgType xtype
|
||||||
t = fmap (fromIntegral . fromEnum) tag
|
t = fmap (fromIntegral . fromEnum) tag
|
||||||
|
|
|
@ -34,6 +34,8 @@ module XMonad.Internal.Concurrent.DynamicWorkspaces
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import qualified Data.ByteString.Char8 as BC
|
||||||
|
import Data.Internal.XIO
|
||||||
import Graphics.X11.Types
|
import Graphics.X11.Types
|
||||||
import Graphics.X11.Xlib.Atom
|
import Graphics.X11.Xlib.Atom
|
||||||
import Graphics.X11.Xlib.Display
|
import Graphics.X11.Xlib.Display
|
||||||
|
@ -88,38 +90,49 @@ data DynWorkspace = DynWorkspace
|
||||||
-- the same as that in XMonad itself (eg with Query types)
|
-- the same as that in XMonad itself (eg with Query types)
|
||||||
-- type MatchTags = M.Map String String
|
-- type MatchTags = M.Map String String
|
||||||
|
|
||||||
data WConf = WConf
|
data WEnv = WEnv
|
||||||
{ display :: Display
|
{ wDisplay :: !Display
|
||||||
, dynWorkspaces :: [DynWorkspace]
|
, wDynWorkspaces :: ![DynWorkspace]
|
||||||
, curPIDs :: MVar (S.Set Pid)
|
, wCurPIDs :: !(MVar (S.Set Pid))
|
||||||
|
, wXEnv :: !XEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
type W a = RIO WConf ()
|
instance HasLogFunc WEnv where
|
||||||
|
logFuncL = lens wXEnv (\x y -> x {wXEnv = y}) . logFuncL
|
||||||
|
|
||||||
runWorkspaceMon :: [DynWorkspace] -> IO ()
|
type WIO a = RIO WEnv a
|
||||||
|
|
||||||
|
runWorkspaceMon :: [DynWorkspace] -> XIO ()
|
||||||
runWorkspaceMon dws = withOpenDisplay $ \dpy -> do
|
runWorkspaceMon dws = withOpenDisplay $ \dpy -> do
|
||||||
root <- rootWindow dpy $ defaultScreen dpy
|
root <- liftIO $ rootWindow dpy $ defaultScreen dpy
|
||||||
-- listen only for substructure change events (which includes MapNotify)
|
-- listen only for substructure change events (which includes MapNotify)
|
||||||
allocaSetWindowAttributes $ \a -> do
|
liftIO $ allocaSetWindowAttributes $ \a -> do
|
||||||
set_event_mask a substructureNotifyMask
|
set_event_mask a substructureNotifyMask
|
||||||
changeWindowAttributes dpy root cWEventMask a
|
changeWindowAttributes dpy root cWEventMask a
|
||||||
void $ allocaXEvent $ withEvents dpy
|
withRunInIO $ \runIO -> do
|
||||||
|
void $ allocaXEvent $ runIO . withEvents dpy
|
||||||
where
|
where
|
||||||
|
wrapEnv dpy ps x =
|
||||||
|
WEnv
|
||||||
|
{ wDisplay = dpy
|
||||||
|
, wDynWorkspaces = dws
|
||||||
|
, wCurPIDs = ps
|
||||||
|
, wXEnv = x
|
||||||
|
}
|
||||||
withEvents dpy e = do
|
withEvents dpy e = do
|
||||||
ps <- newMVar S.empty
|
ps <- newMVar S.empty
|
||||||
let c = WConf {display = dpy, dynWorkspaces = dws, curPIDs = ps}
|
mapRIO (wrapEnv dpy ps) $ do
|
||||||
runRIO c $
|
|
||||||
forever $
|
forever $
|
||||||
handleEvent =<< io (nextEvent dpy e >> getEvent e)
|
handleEvent =<< io (nextEvent dpy e >> getEvent e)
|
||||||
|
|
||||||
handleEvent :: Event -> W ()
|
handleEvent :: Event -> WIO ()
|
||||||
|
|
||||||
-- | assume this fires at least once when a new window is created (also could
|
-- | assume this fires at least once when a new window is created (also could
|
||||||
-- use CreateNotify but that is really noisy)
|
-- use CreateNotify but that is really noisy)
|
||||||
handleEvent MapNotifyEvent {ev_window = w} = do
|
handleEvent MapNotifyEvent {ev_window = w} = do
|
||||||
dpy <- asks display
|
dpy <- asks wDisplay
|
||||||
hint <- io $ getClassHint dpy w
|
hint <- io $ getClassHint dpy w
|
||||||
dws <- asks dynWorkspaces
|
dws <- asks wDynWorkspaces
|
||||||
let tag =
|
let tag =
|
||||||
M.lookup (resClass hint) $
|
M.lookup (resClass hint) $
|
||||||
M.fromList $
|
M.fromList $
|
||||||
|
@ -133,21 +146,28 @@ handleEvent MapNotifyEvent {ev_window = w} = do
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
handleEvent _ = return ()
|
handleEvent _ = return ()
|
||||||
|
|
||||||
withUniquePid :: Pid -> String -> W ()
|
withUniquePid :: Pid -> String -> WIO ()
|
||||||
withUniquePid pid tag = do
|
withUniquePid pid tag = do
|
||||||
ps <- asks curPIDs
|
ps <- asks wCurPIDs
|
||||||
pids <- readMVar ps
|
pids <- readMVar ps
|
||||||
io
|
unless (pid `elem` pids)
|
||||||
$ unless (pid `elem` pids)
|
|
||||||
$ bracket_
|
$ bracket_
|
||||||
(modifyMVar_ ps (return . S.insert pid))
|
(modifyMVar_ ps (return . S.insert pid))
|
||||||
(modifyMVar_ ps (return . S.delete pid))
|
(modifyMVar_ ps (return . S.delete pid))
|
||||||
$ waitUntilExit pid >> sendXMsg Workspace tag
|
$ do
|
||||||
|
logInfo $ "waiting for pid " <> pid_ <> " to exit on workspace " <> tag_
|
||||||
|
waitUntilExit pid
|
||||||
|
logInfo $ "pid " <> pid_ <> " exited on workspace " <> tag_
|
||||||
|
liftIO $ sendXMsg Workspace tag
|
||||||
|
where
|
||||||
|
pid_ = "'" <> displayShow pid <> "'"
|
||||||
|
tag_ = "'" <> displayBytesUtf8 (BC.pack tag) <> "'"
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Launching apps
|
-- Launching apps
|
||||||
-- When launching apps on dymamic workspaces, first check if they are running
|
-- When launching apps on dymamic workspaces, first check if they are running
|
||||||
-- and launch if not, then switch to their workspace
|
-- and launch if not, then switch to their workspace
|
||||||
|
|
||||||
wsOccupied :: Eq a1 => a1 -> W.StackSet a1 l a2 sid sd -> Bool
|
wsOccupied :: Eq a1 => a1 -> W.StackSet a1 l a2 sid sd -> Bool
|
||||||
wsOccupied tag ws =
|
wsOccupied tag ws =
|
||||||
elem tag $
|
elem tag $
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- VirtualBox-specific functions
|
-- VirtualBox-specific functions
|
||||||
|
|
||||||
|
@ -11,7 +8,7 @@ module XMonad.Internal.Concurrent.VirtualBox
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import RIO hiding (try)
|
import RIO hiding (try)
|
||||||
import RIO.Directory
|
import RIO.Directory
|
||||||
import RIO.FilePath
|
import RIO.FilePath
|
||||||
|
@ -36,7 +33,7 @@ vmInstanceConfig vmName = do
|
||||||
vmDirectory :: IO (Either String String)
|
vmDirectory :: IO (Either String String)
|
||||||
vmDirectory = do
|
vmDirectory = do
|
||||||
p <- vmConfig
|
p <- vmConfig
|
||||||
s <- tryIO $ readFile p
|
s <- tryIO $ readFileUtf8 p
|
||||||
return $ case s of
|
return $ case s of
|
||||||
(Left _) -> Left "could not read VirtualBox config file"
|
(Left _) -> Left "could not read VirtualBox config file"
|
||||||
(Right x) ->
|
(Right x) ->
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- DBus module for Clevo Keyboard control
|
-- DBus module for Clevo Keyboard control
|
||||||
|
|
||||||
|
@ -15,7 +13,7 @@ where
|
||||||
|
|
||||||
import DBus
|
import DBus
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import RIO
|
import RIO
|
||||||
import RIO.FilePath
|
import RIO.FilePath
|
||||||
import XMonad.Internal.DBus.Brightness.Common
|
import XMonad.Internal.DBus.Brightness.Common
|
||||||
|
@ -46,34 +44,34 @@ backlightDir = "/sys/devices/platform/tuxedo_keyboard"
|
||||||
stateFile :: FilePath
|
stateFile :: FilePath
|
||||||
stateFile = backlightDir </> "state"
|
stateFile = backlightDir </> "state"
|
||||||
|
|
||||||
stateChange :: Bool -> IO ()
|
stateChange :: MonadUnliftIO m => Bool -> m ()
|
||||||
stateChange = writeBool stateFile
|
stateChange = writeBool stateFile
|
||||||
|
|
||||||
stateOn :: IO ()
|
stateOn :: MonadUnliftIO m => m ()
|
||||||
stateOn = stateChange True
|
stateOn = stateChange True
|
||||||
|
|
||||||
stateOff :: IO ()
|
stateOff :: MonadUnliftIO m => m ()
|
||||||
stateOff = stateChange False
|
stateOff = stateChange False
|
||||||
|
|
||||||
brightnessFile :: FilePath
|
brightnessFile :: FilePath
|
||||||
brightnessFile = backlightDir </> "brightness"
|
brightnessFile = backlightDir </> "brightness"
|
||||||
|
|
||||||
getBrightness :: RawBounds -> IO Brightness
|
getBrightness :: MonadUnliftIO m => RawBounds -> m Brightness
|
||||||
getBrightness bounds = readPercent bounds brightnessFile
|
getBrightness bounds = readPercent bounds brightnessFile
|
||||||
|
|
||||||
minBrightness :: RawBounds -> IO Brightness
|
minBrightness :: MonadUnliftIO m => RawBounds -> m Brightness
|
||||||
minBrightness bounds = do
|
minBrightness bounds = do
|
||||||
b <- writePercentMin bounds brightnessFile
|
b <- writePercentMin bounds brightnessFile
|
||||||
stateOff
|
stateOff
|
||||||
return b
|
return b
|
||||||
|
|
||||||
maxBrightness :: RawBounds -> IO Brightness
|
maxBrightness :: MonadUnliftIO m => RawBounds -> m Brightness
|
||||||
maxBrightness bounds = stateOn >> writePercentMax bounds brightnessFile
|
maxBrightness bounds = stateOn >> writePercentMax bounds brightnessFile
|
||||||
|
|
||||||
incBrightness :: RawBounds -> IO Brightness
|
incBrightness :: MonadUnliftIO m => RawBounds -> m Brightness
|
||||||
incBrightness bounds = stateOn >> incPercent steps brightnessFile bounds
|
incBrightness bounds = stateOn >> incPercent steps brightnessFile bounds
|
||||||
|
|
||||||
decBrightness :: RawBounds -> IO Brightness
|
decBrightness :: MonadUnliftIO m => RawBounds -> m Brightness
|
||||||
decBrightness bounds = do
|
decBrightness bounds = do
|
||||||
b <- decPercent steps brightnessFile bounds
|
b <- decPercent steps brightnessFile bounds
|
||||||
when (b == 0) stateOff
|
when (b == 0) stateOff
|
||||||
|
@ -88,7 +86,7 @@ blPath = objectPath_ "/clevo_keyboard"
|
||||||
interface :: InterfaceName
|
interface :: InterfaceName
|
||||||
interface = interfaceName_ "org.xmonad.Brightness"
|
interface = interfaceName_ "org.xmonad.Brightness"
|
||||||
|
|
||||||
clevoKeyboardConfig :: BrightnessConfig RawBrightness Brightness
|
clevoKeyboardConfig :: MonadUnliftIO m => BrightnessConfig m RawBrightness Brightness
|
||||||
clevoKeyboardConfig =
|
clevoKeyboardConfig =
|
||||||
BrightnessConfig
|
BrightnessConfig
|
||||||
{ bcMin = minBrightness
|
{ bcMin = minBrightness
|
||||||
|
@ -113,9 +111,14 @@ brightnessFileDep :: IODependency_
|
||||||
brightnessFileDep = pathR brightnessFile [Package AUR "tuxedo-keyboard"]
|
brightnessFileDep = pathR brightnessFile [Package AUR "tuxedo-keyboard"]
|
||||||
|
|
||||||
clevoKeyboardSignalDep :: DBusDependency_ SesClient
|
clevoKeyboardSignalDep :: DBusDependency_ SesClient
|
||||||
clevoKeyboardSignalDep = signalDep clevoKeyboardConfig
|
clevoKeyboardSignalDep =
|
||||||
|
-- TODO do I need to get rid of the IO here?
|
||||||
|
signalDep (clevoKeyboardConfig :: BrightnessConfig IO RawBrightness Brightness)
|
||||||
|
|
||||||
exportClevoKeyboard :: Maybe SesClient -> SometimesIO
|
exportClevoKeyboard
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Maybe NamedSesConnection
|
||||||
|
-> Sometimes (m (), m ())
|
||||||
exportClevoKeyboard =
|
exportClevoKeyboard =
|
||||||
brightnessExporter
|
brightnessExporter
|
||||||
xpfClevoBacklight
|
xpfClevoBacklight
|
||||||
|
@ -123,15 +126,23 @@ exportClevoKeyboard =
|
||||||
[stateFileDep, brightnessFileDep]
|
[stateFileDep, brightnessFileDep]
|
||||||
clevoKeyboardConfig
|
clevoKeyboardConfig
|
||||||
|
|
||||||
clevoKeyboardControls :: Maybe SesClient -> BrightnessControls
|
clevoKeyboardControls
|
||||||
|
:: (HasClient (DBusEnv env), MonadReader env m, MonadUnliftIO m)
|
||||||
|
=> Maybe NamedSesConnection
|
||||||
|
-> BrightnessControls m
|
||||||
clevoKeyboardControls = brightnessControls xpfClevoBacklight clevoKeyboardConfig
|
clevoKeyboardControls = brightnessControls xpfClevoBacklight clevoKeyboardConfig
|
||||||
|
|
||||||
callGetBrightnessCK :: MonadUnliftIO m => SesClient -> m (Maybe Brightness)
|
callGetBrightnessCK
|
||||||
|
:: (SafeClient c, HasClient env, MonadReader (env c) m, MonadUnliftIO m)
|
||||||
|
=> m (Maybe Brightness)
|
||||||
callGetBrightnessCK = callGetBrightness clevoKeyboardConfig
|
callGetBrightnessCK = callGetBrightness clevoKeyboardConfig
|
||||||
|
|
||||||
matchSignalCK
|
matchSignalCK
|
||||||
:: MonadUnliftIO m
|
:: ( SafeClient c
|
||||||
|
, HasClient env
|
||||||
|
, MonadReader (env c) m
|
||||||
|
, MonadUnliftIO m
|
||||||
|
)
|
||||||
=> (Maybe Brightness -> m ())
|
=> (Maybe Brightness -> m ())
|
||||||
-> SesClient
|
|
||||||
-> m ()
|
-> m ()
|
||||||
matchSignalCK = matchSignal clevoKeyboardConfig
|
matchSignalCK = matchSignal clevoKeyboardConfig
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- DBus module for DBus brightness controls
|
-- DBus module for DBus brightness controls
|
||||||
|
|
||||||
|
@ -18,10 +16,9 @@ import DBus
|
||||||
import DBus.Client
|
import DBus.Client
|
||||||
import qualified DBus.Introspection as I
|
import qualified DBus.Introspection as I
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import RIO
|
import RIO
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import XMonad.Core (io)
|
|
||||||
import XMonad.Internal.DBus.Common
|
import XMonad.Internal.DBus.Common
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
@ -32,31 +29,32 @@ import XMonad.Internal.DBus.Common
|
||||||
-- integer and emit a signal with the same brightness value. Additionally, there
|
-- integer and emit a signal with the same brightness value. Additionally, there
|
||||||
-- is one method to get the current brightness.
|
-- is one method to get the current brightness.
|
||||||
|
|
||||||
data BrightnessConfig a b = BrightnessConfig
|
data BrightnessConfig m a b = BrightnessConfig
|
||||||
{ bcMin :: (a, a) -> IO b
|
{ bcMin :: (a, a) -> m b
|
||||||
, bcMax :: (a, a) -> IO b
|
, bcMax :: (a, a) -> m b
|
||||||
, bcDec :: (a, a) -> IO b
|
, bcDec :: (a, a) -> m b
|
||||||
, bcInc :: (a, a) -> IO b
|
, bcInc :: (a, a) -> m b
|
||||||
, bcGet :: (a, a) -> IO b
|
, bcGet :: (a, a) -> m b
|
||||||
, bcMinRaw :: a
|
, bcMinRaw :: a
|
||||||
, bcGetMax :: IO a
|
, bcGetMax :: m a
|
||||||
, bcPath :: ObjectPath
|
, bcPath :: ObjectPath
|
||||||
, bcInterface :: InterfaceName
|
, bcInterface :: InterfaceName
|
||||||
, bcName :: T.Text
|
, bcName :: T.Text
|
||||||
}
|
}
|
||||||
|
|
||||||
data BrightnessControls = BrightnessControls
|
data BrightnessControls m = BrightnessControls
|
||||||
{ bctlMax :: SometimesX
|
{ bctlMax :: Sometimes (m ())
|
||||||
, bctlMin :: SometimesX
|
, bctlMin :: Sometimes (m ())
|
||||||
, bctlInc :: SometimesX
|
, bctlInc :: Sometimes (m ())
|
||||||
, bctlDec :: SometimesX
|
, bctlDec :: Sometimes (m ())
|
||||||
}
|
}
|
||||||
|
|
||||||
brightnessControls
|
brightnessControls
|
||||||
:: XPQuery
|
:: (HasClient (DBusEnv env), MonadReader env m, MonadUnliftIO m)
|
||||||
-> BrightnessConfig a b
|
=> XPQuery
|
||||||
-> Maybe SesClient
|
-> BrightnessConfig m a b
|
||||||
-> BrightnessControls
|
-> Maybe NamedSesConnection
|
||||||
|
-> BrightnessControls m
|
||||||
brightnessControls q bc cl =
|
brightnessControls q bc cl =
|
||||||
BrightnessControls
|
BrightnessControls
|
||||||
{ bctlMax = cb "max brightness" memMax
|
{ bctlMax = cb "max brightness" memMax
|
||||||
|
@ -68,26 +66,34 @@ brightnessControls q bc cl =
|
||||||
cb = callBacklight q cl bc
|
cb = callBacklight q cl bc
|
||||||
|
|
||||||
callGetBrightness
|
callGetBrightness
|
||||||
:: (MonadUnliftIO m, SafeClient c, Num n)
|
:: ( HasClient env
|
||||||
=> BrightnessConfig a b
|
, MonadReader (env c) m
|
||||||
-> c
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, Num n
|
||||||
|
)
|
||||||
|
=> BrightnessConfig m a b
|
||||||
-> m (Maybe n)
|
-> m (Maybe n)
|
||||||
callGetBrightness BrightnessConfig {bcPath = p, bcInterface = i} client =
|
callGetBrightness BrightnessConfig {bcPath = p, bcInterface = i} =
|
||||||
either (const Nothing) bodyGetBrightness
|
either (const Nothing) bodyGetBrightness
|
||||||
<$> callMethod client xmonadBusName p i memGet
|
<$> callMethod xmonadSesBusName p i memGet
|
||||||
|
|
||||||
signalDep :: BrightnessConfig a b -> DBusDependency_ SesClient
|
signalDep :: BrightnessConfig m a b -> DBusDependency_ c
|
||||||
signalDep BrightnessConfig {bcPath = p, bcInterface = i} =
|
signalDep BrightnessConfig {bcPath = p, bcInterface = i} =
|
||||||
Endpoint [] xmonadBusName p i $ Signal_ memCur
|
Endpoint [] xmonadSesBusName p i $ Signal_ memCur
|
||||||
|
|
||||||
matchSignal
|
matchSignal
|
||||||
:: (MonadUnliftIO m, SafeClient c, Num n)
|
:: ( HasClient env
|
||||||
=> BrightnessConfig a b
|
, MonadReader (env c) m
|
||||||
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, Num n
|
||||||
|
)
|
||||||
|
=> BrightnessConfig m a b
|
||||||
-> (Maybe n -> m ())
|
-> (Maybe n -> m ())
|
||||||
-> c
|
|
||||||
-> m ()
|
-> m ()
|
||||||
matchSignal BrightnessConfig {bcPath = p, bcInterface = i} cb =
|
matchSignal BrightnessConfig {bcPath = p, bcInterface = i} cb =
|
||||||
void . addMatchCallback brMatcher (cb . bodyGetBrightness)
|
void $ addMatchCallback brMatcher (cb . bodyGetBrightness)
|
||||||
where
|
where
|
||||||
-- TODO add busname to this
|
-- TODO add busname to this
|
||||||
brMatcher =
|
brMatcher =
|
||||||
|
@ -101,33 +107,35 @@ matchSignal BrightnessConfig {bcPath = p, bcInterface = i} cb =
|
||||||
-- Internal DBus Crap
|
-- Internal DBus Crap
|
||||||
|
|
||||||
brightnessExporter
|
brightnessExporter
|
||||||
:: RealFrac b
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m, RealFrac b)
|
||||||
=> XPQuery
|
=> XPQuery
|
||||||
-> [Fulfillment]
|
-> [Fulfillment]
|
||||||
-> [IODependency_]
|
-> [IODependency_]
|
||||||
-> BrightnessConfig a b
|
-> BrightnessConfig m a b
|
||||||
-> Maybe SesClient
|
-> Maybe NamedSesConnection
|
||||||
-> SometimesIO
|
-> Sometimes (m (), m ())
|
||||||
brightnessExporter q ful deps bc@BrightnessConfig {bcName = n} cl =
|
brightnessExporter q ful deps bc@BrightnessConfig {bcName = n} cl =
|
||||||
Sometimes (T.append n " DBus Interface") q [Subfeature root "exporter"]
|
Sometimes (T.append n " DBus Interface") q [Subfeature root "exporter"]
|
||||||
where
|
where
|
||||||
root = DBusRoot_ (exportBrightnessControls' bc) tree cl
|
root = DBusRoot_ (exportBrightnessControlsInner bc) tree cl
|
||||||
tree = listToAnds (Bus ful xmonadBusName) $ fmap DBusIO deps
|
tree = listToAnds (Bus ful xmonadSesBusName) $ fmap DBusIO deps
|
||||||
|
|
||||||
exportBrightnessControls'
|
exportBrightnessControlsInner
|
||||||
:: (MonadUnliftIO m, RealFrac b)
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m, RealFrac b)
|
||||||
=> BrightnessConfig a b
|
=> BrightnessConfig m a b
|
||||||
-> SesClient
|
-> NamedSesConnection
|
||||||
-> m ()
|
-> (m (), m ())
|
||||||
exportBrightnessControls' bc cl = io $ do
|
exportBrightnessControlsInner bc = cmd
|
||||||
let ses = toClient cl
|
where
|
||||||
maxval <- bcGetMax bc -- assume the max value will never change
|
cmd = exportPair (bcPath bc) $ \cl_ -> do
|
||||||
let bounds = (bcMinRaw bc, maxval)
|
-- assume the max value will never change
|
||||||
let autoMethod' m f = autoMethod m $ emitBrightness bc ses =<< f bc bounds
|
bounds <- (bcMinRaw bc,) <$> bcGetMax bc
|
||||||
let funget = bcGet bc
|
runIO <- askRunInIO
|
||||||
export
|
let autoMethod' m f = autoMethod m $ runIO $ do
|
||||||
ses
|
val <- f bc bounds
|
||||||
(bcPath bc)
|
emitBrightness bc cl_ val
|
||||||
|
funget <- toIO $ bcGet bc bounds
|
||||||
|
return $
|
||||||
defaultInterface
|
defaultInterface
|
||||||
{ interfaceName = bcInterface bc
|
{ interfaceName = bcInterface bc
|
||||||
, interfaceMethods =
|
, interfaceMethods =
|
||||||
|
@ -135,11 +143,10 @@ exportBrightnessControls' bc cl = io $ do
|
||||||
, autoMethod' memMin bcMin
|
, autoMethod' memMin bcMin
|
||||||
, autoMethod' memInc bcInc
|
, autoMethod' memInc bcInc
|
||||||
, autoMethod' memDec bcDec
|
, autoMethod' memDec bcDec
|
||||||
, autoMethod memGet (round <$> funget bounds :: IO Int32)
|
, autoMethod memGet (round <$> funget :: IO Int32)
|
||||||
]
|
]
|
||||||
, interfaceSignals = [sig]
|
, interfaceSignals = [sig]
|
||||||
}
|
}
|
||||||
where
|
|
||||||
sig =
|
sig =
|
||||||
I.Signal
|
I.Signal
|
||||||
{ I.signalName = memCur
|
{ I.signalName = memCur
|
||||||
|
@ -153,7 +160,7 @@ exportBrightnessControls' bc cl = io $ do
|
||||||
|
|
||||||
emitBrightness
|
emitBrightness
|
||||||
:: (MonadUnliftIO m, RealFrac b)
|
:: (MonadUnliftIO m, RealFrac b)
|
||||||
=> BrightnessConfig a b
|
=> BrightnessConfig m a b
|
||||||
-> Client
|
-> Client
|
||||||
-> b
|
-> b
|
||||||
-> m ()
|
-> m ()
|
||||||
|
@ -163,26 +170,18 @@ emitBrightness BrightnessConfig {bcPath = p, bcInterface = i} client cur =
|
||||||
sig = signal p i memCur
|
sig = signal p i memCur
|
||||||
|
|
||||||
callBacklight
|
callBacklight
|
||||||
:: XPQuery
|
:: (MonadReader env m, HasClient (DBusEnv env), MonadUnliftIO m)
|
||||||
-> Maybe SesClient
|
=> XPQuery
|
||||||
-> BrightnessConfig a b
|
-> Maybe NamedSesConnection
|
||||||
|
-> BrightnessConfig m a b
|
||||||
-> T.Text
|
-> T.Text
|
||||||
-> MemberName
|
-> MemberName
|
||||||
-> SometimesX
|
-> Sometimes (m ())
|
||||||
callBacklight
|
callBacklight q cl BrightnessConfig {bcPath = p, bcInterface = i, bcName = n} controlName m =
|
||||||
q
|
|
||||||
cl
|
|
||||||
BrightnessConfig
|
|
||||||
{ bcPath = p
|
|
||||||
, bcInterface = i
|
|
||||||
, bcName = n
|
|
||||||
}
|
|
||||||
controlName
|
|
||||||
m =
|
|
||||||
Sometimes (T.unwords [n, controlName]) q [Subfeature root "method call"]
|
Sometimes (T.unwords [n, controlName]) q [Subfeature root "method call"]
|
||||||
where
|
where
|
||||||
root = DBusRoot_ cmd (Only_ $ Endpoint [] xmonadBusName p i $ Method_ m) cl
|
root = DBusRoot_ cmd (Only_ $ Endpoint [] xmonadSesBusName p i $ Method_ m) cl
|
||||||
cmd c = io $ void $ callMethod c xmonadBusName p i m
|
cmd c = void $ withDIO c $ callMethod xmonadSesBusName p i m
|
||||||
|
|
||||||
bodyGetBrightness :: Num a => [Variant] -> Maybe a
|
bodyGetBrightness :: Num a => [Variant] -> Maybe a
|
||||||
bodyGetBrightness [b] = fromIntegral <$> (fromVariant b :: Maybe Int32)
|
bodyGetBrightness [b] = fromIntegral <$> (fromVariant b :: Maybe Int32)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- DBus module for Intel Backlight control
|
-- DBus module for Intel Backlight control
|
||||||
|
|
||||||
|
@ -15,7 +13,7 @@ where
|
||||||
|
|
||||||
import DBus
|
import DBus
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import RIO
|
import RIO
|
||||||
import RIO.FilePath
|
import RIO.FilePath
|
||||||
import XMonad.Internal.DBus.Brightness.Common
|
import XMonad.Internal.DBus.Brightness.Common
|
||||||
|
@ -45,22 +43,22 @@ maxFile = backlightDir </> "max_brightness"
|
||||||
curFile :: FilePath
|
curFile :: FilePath
|
||||||
curFile = backlightDir </> "brightness"
|
curFile = backlightDir </> "brightness"
|
||||||
|
|
||||||
getMaxRawBrightness :: IO RawBrightness
|
getMaxRawBrightness :: MonadUnliftIO m => m RawBrightness
|
||||||
getMaxRawBrightness = readInt maxFile
|
getMaxRawBrightness = readInt maxFile
|
||||||
|
|
||||||
getBrightness :: RawBounds -> IO Brightness
|
getBrightness :: MonadUnliftIO m => RawBounds -> m Brightness
|
||||||
getBrightness bounds = readPercent bounds curFile
|
getBrightness bounds = readPercent bounds curFile
|
||||||
|
|
||||||
minBrightness :: RawBounds -> IO Brightness
|
minBrightness :: MonadUnliftIO m => RawBounds -> m Brightness
|
||||||
minBrightness bounds = writePercentMin bounds curFile
|
minBrightness bounds = writePercentMin bounds curFile
|
||||||
|
|
||||||
maxBrightness :: RawBounds -> IO Brightness
|
maxBrightness :: MonadUnliftIO m => RawBounds -> m Brightness
|
||||||
maxBrightness bounds = writePercentMax bounds curFile
|
maxBrightness bounds = writePercentMax bounds curFile
|
||||||
|
|
||||||
incBrightness :: RawBounds -> IO Brightness
|
incBrightness :: MonadUnliftIO m => RawBounds -> m Brightness
|
||||||
incBrightness = incPercent steps curFile
|
incBrightness = incPercent steps curFile
|
||||||
|
|
||||||
decBrightness :: RawBounds -> IO Brightness
|
decBrightness :: MonadUnliftIO m => RawBounds -> m Brightness
|
||||||
decBrightness = decPercent steps curFile
|
decBrightness = decPercent steps curFile
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
@ -72,7 +70,9 @@ blPath = objectPath_ "/intelbacklight"
|
||||||
interface :: InterfaceName
|
interface :: InterfaceName
|
||||||
interface = interfaceName_ "org.xmonad.Brightness"
|
interface = interfaceName_ "org.xmonad.Brightness"
|
||||||
|
|
||||||
intelBacklightConfig :: BrightnessConfig RawBrightness Brightness
|
intelBacklightConfig
|
||||||
|
:: MonadUnliftIO m
|
||||||
|
=> BrightnessConfig m RawBrightness Brightness
|
||||||
intelBacklightConfig =
|
intelBacklightConfig =
|
||||||
BrightnessConfig
|
BrightnessConfig
|
||||||
{ bcMin = minBrightness
|
{ bcMin = minBrightness
|
||||||
|
@ -97,9 +97,14 @@ maxFileDep :: IODependency_
|
||||||
maxFileDep = pathR maxFile []
|
maxFileDep = pathR maxFile []
|
||||||
|
|
||||||
intelBacklightSignalDep :: DBusDependency_ SesClient
|
intelBacklightSignalDep :: DBusDependency_ SesClient
|
||||||
intelBacklightSignalDep = signalDep intelBacklightConfig
|
intelBacklightSignalDep =
|
||||||
|
-- TODO do I need to get rid of the IO here?
|
||||||
|
signalDep (intelBacklightConfig :: BrightnessConfig IO RawBrightness Brightness)
|
||||||
|
|
||||||
exportIntelBacklight :: Maybe SesClient -> SometimesIO
|
exportIntelBacklight
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Maybe NamedSesConnection
|
||||||
|
-> Sometimes (m (), m ())
|
||||||
exportIntelBacklight =
|
exportIntelBacklight =
|
||||||
brightnessExporter
|
brightnessExporter
|
||||||
xpfIntelBacklight
|
xpfIntelBacklight
|
||||||
|
@ -107,15 +112,23 @@ exportIntelBacklight =
|
||||||
[curFileDep, maxFileDep]
|
[curFileDep, maxFileDep]
|
||||||
intelBacklightConfig
|
intelBacklightConfig
|
||||||
|
|
||||||
intelBacklightControls :: Maybe SesClient -> BrightnessControls
|
intelBacklightControls
|
||||||
|
:: (HasClient (DBusEnv env), MonadReader env m, MonadUnliftIO m)
|
||||||
|
=> Maybe NamedSesConnection
|
||||||
|
-> BrightnessControls m
|
||||||
intelBacklightControls = brightnessControls xpfIntelBacklight intelBacklightConfig
|
intelBacklightControls = brightnessControls xpfIntelBacklight intelBacklightConfig
|
||||||
|
|
||||||
callGetBrightnessIB :: MonadUnliftIO m => SesClient -> m (Maybe Brightness)
|
callGetBrightnessIB
|
||||||
|
:: (SafeClient c, HasClient env, MonadReader (env c) m, MonadUnliftIO m)
|
||||||
|
=> m (Maybe Brightness)
|
||||||
callGetBrightnessIB = callGetBrightness intelBacklightConfig
|
callGetBrightnessIB = callGetBrightness intelBacklightConfig
|
||||||
|
|
||||||
matchSignalIB
|
matchSignalIB
|
||||||
:: MonadUnliftIO m
|
:: ( SafeClient c
|
||||||
|
, HasClient env
|
||||||
|
, MonadReader (env c) m
|
||||||
|
, MonadUnliftIO m
|
||||||
|
)
|
||||||
=> (Maybe Brightness -> m ())
|
=> (Maybe Brightness -> m ())
|
||||||
-> SesClient
|
|
||||||
-> m ()
|
-> m ()
|
||||||
matchSignalIB = matchSignal intelBacklightConfig
|
matchSignalIB = matchSignal intelBacklightConfig
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
-- High-level interface for managing XMonad's DBus
|
-- High-level interface for managing XMonad's DBus
|
||||||
|
|
||||||
module XMonad.Internal.DBus.Common
|
module XMonad.Internal.DBus.Common
|
||||||
( xmonadBusName
|
( xmonadSesBusName
|
||||||
|
, xmonadSysBusName
|
||||||
, btBus
|
, btBus
|
||||||
, notifyBus
|
, notifyBus
|
||||||
, notifyPath
|
, notifyPath
|
||||||
|
@ -12,8 +13,11 @@ where
|
||||||
|
|
||||||
import DBus
|
import DBus
|
||||||
|
|
||||||
xmonadBusName :: BusName
|
xmonadSesBusName :: BusName
|
||||||
xmonadBusName = busName_ "org.xmonad"
|
xmonadSesBusName = busName_ "org.xmonad.session"
|
||||||
|
|
||||||
|
xmonadSysBusName :: BusName
|
||||||
|
xmonadSysBusName = busName_ "org.xmonad.system"
|
||||||
|
|
||||||
btBus :: BusName
|
btBus :: BusName
|
||||||
btBus = busName_ "org.bluez"
|
btBus = busName_ "org.bluez"
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- High-level interface for managing XMonad's DBus
|
-- High-level interface for managing XMonad's DBus
|
||||||
|
|
||||||
module XMonad.Internal.DBus.Control
|
module XMonad.Internal.DBus.Control
|
||||||
( Client
|
( Client
|
||||||
, DBusState (..)
|
, DBusState (..)
|
||||||
|
, withDBusInterfaces
|
||||||
|
, withDBusX
|
||||||
|
, withDBusX_
|
||||||
|
, withDBus
|
||||||
|
, withDBus_
|
||||||
, connectDBus
|
, connectDBus
|
||||||
, connectDBusX
|
|
||||||
, disconnectDBus
|
, disconnectDBus
|
||||||
, disconnectDBusX
|
-- , disconnectDBusX
|
||||||
, getDBusClient
|
, getDBusClient
|
||||||
, withDBusClient
|
, withDBusClient
|
||||||
, withDBusClient_
|
, withDBusClient_
|
||||||
|
@ -21,7 +23,7 @@ where
|
||||||
import DBus
|
import DBus
|
||||||
import DBus.Client
|
import DBus.Client
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import RIO
|
import RIO
|
||||||
import XMonad.Internal.DBus.Brightness.ClevoKeyboard
|
import XMonad.Internal.DBus.Brightness.ClevoKeyboard
|
||||||
import XMonad.Internal.DBus.Brightness.IntelBacklight
|
import XMonad.Internal.DBus.Brightness.IntelBacklight
|
||||||
|
@ -30,54 +32,157 @@ import XMonad.Internal.DBus.Screensaver
|
||||||
|
|
||||||
-- | Current connections to the DBus (session and system buses)
|
-- | Current connections to the DBus (session and system buses)
|
||||||
data DBusState = DBusState
|
data DBusState = DBusState
|
||||||
{ dbSesClient :: Maybe SesClient
|
{ dbSesClient :: Maybe NamedSesConnection
|
||||||
, dbSysClient :: Maybe SysClient
|
, dbSysClient :: Maybe NamedSysConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withDBusX_
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> (DBusState -> m a)
|
||||||
|
-> m ()
|
||||||
|
withDBusX_ = void . withDBusX
|
||||||
|
|
||||||
|
withDBusX
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> (DBusState -> m a)
|
||||||
|
-> m a
|
||||||
|
withDBusX = withDBus (Just xmonadSesBusName) Nothing
|
||||||
|
|
||||||
|
withDBus_
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Maybe BusName
|
||||||
|
-> Maybe BusName
|
||||||
|
-> (DBusState -> m a)
|
||||||
|
-> m ()
|
||||||
|
withDBus_ sesname sysname = void . withDBus sesname sysname
|
||||||
|
|
||||||
|
withDBus
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Maybe BusName
|
||||||
|
-> Maybe BusName
|
||||||
|
-> (DBusState -> m a)
|
||||||
|
-> m a
|
||||||
|
withDBus sesname sysname = bracket (connectDBus sesname sysname) disconnectDBus
|
||||||
|
|
||||||
-- | Connect to the DBus
|
-- | Connect to the DBus
|
||||||
connectDBus :: MonadUnliftIO m => m DBusState
|
connectDBus
|
||||||
connectDBus = do
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
ses <- getDBusClient
|
=> Maybe BusName
|
||||||
sys <- getDBusClient
|
-> Maybe BusName
|
||||||
|
-> m DBusState
|
||||||
|
connectDBus sesname sysname = do
|
||||||
|
ses <- getDBusClient sesname
|
||||||
|
sys <- getDBusClient sysname
|
||||||
return DBusState {dbSesClient = ses, dbSysClient = sys}
|
return DBusState {dbSesClient = ses, dbSysClient = sys}
|
||||||
|
|
||||||
-- | Disconnect from the DBus
|
-- | Disconnect from the DBus
|
||||||
disconnectDBus :: MonadUnliftIO m => DBusState -> m ()
|
disconnectDBus
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> DBusState
|
||||||
|
-> m ()
|
||||||
disconnectDBus db = disc dbSesClient >> disc dbSysClient
|
disconnectDBus db = disc dbSesClient >> disc dbSysClient
|
||||||
where
|
where
|
||||||
|
disc
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m, SafeClient c)
|
||||||
|
=> (DBusState -> Maybe (NamedConnection c))
|
||||||
|
-> m ()
|
||||||
disc f = maybe (return ()) disconnectDBusClient $ f db
|
disc f = maybe (return ()) disconnectDBusClient $ f db
|
||||||
|
|
||||||
-- | Connect to the DBus and request the XMonad name
|
-- -- | Connect to the DBus and request the XMonad name
|
||||||
connectDBusX :: MonadUnliftIO m => m DBusState
|
-- connectDBusX
|
||||||
connectDBusX = do
|
-- :: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
db <- connectDBus
|
-- => m DBusState
|
||||||
forM_ (dbSesClient db) requestXMonadName
|
-- connectDBusX = do
|
||||||
return db
|
-- db <- connectDBus
|
||||||
|
-- requestXMonadName2 db
|
||||||
|
-- return db
|
||||||
|
|
||||||
-- | Disconnect from DBus and release the XMonad name
|
-- -- | Disconnect from DBus and release the XMonad name
|
||||||
disconnectDBusX :: MonadUnliftIO m => DBusState -> m ()
|
-- disconnectDBusX
|
||||||
disconnectDBusX db = do
|
-- :: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
forM_ (dbSesClient db) releaseXMonadName
|
-- => DBusState
|
||||||
disconnectDBus db
|
-- -> m ()
|
||||||
|
-- disconnectDBusX db = do
|
||||||
|
-- forM_ (dbSesClient db) releaseBusName
|
||||||
|
-- forM_ (dbSysClient db) releaseBusName
|
||||||
|
-- disconnectDBus db
|
||||||
|
|
||||||
|
-- requestXMonadName2
|
||||||
|
-- :: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
-- => DBusState
|
||||||
|
-- -> m ()
|
||||||
|
-- requestXMonadName2 db = do
|
||||||
|
-- forM_ (dbSesClient db) requestXMonadName
|
||||||
|
-- forM_ (dbSysClient db) requestXMonadName
|
||||||
|
|
||||||
|
withDBusInterfaces
|
||||||
|
:: DBusState
|
||||||
|
-> [Maybe NamedSesConnection -> Sometimes (XIO (), XIO ())]
|
||||||
|
-> ([XIO ()] -> XIO a)
|
||||||
|
-> XIO a
|
||||||
|
withDBusInterfaces db interfaces = bracket up sequence
|
||||||
|
where
|
||||||
|
up = do
|
||||||
|
pairs <- catMaybes <$> mapM (\f -> evalSometimes $ f $ dbSesClient db) interfaces
|
||||||
|
mapM_ fst pairs
|
||||||
|
return $ snd <$> pairs
|
||||||
|
|
||||||
-- | All exporter features to be assigned to the DBus
|
-- | All exporter features to be assigned to the DBus
|
||||||
dbusExporters :: [Maybe SesClient -> SometimesIO]
|
dbusExporters
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> [Maybe NamedSesConnection -> Sometimes (m (), m ())]
|
||||||
dbusExporters = [exportScreensaver, exportIntelBacklight, exportClevoKeyboard]
|
dbusExporters = [exportScreensaver, exportIntelBacklight, exportClevoKeyboard]
|
||||||
|
|
||||||
releaseXMonadName :: MonadUnliftIO m => SesClient -> m ()
|
-- releaseXMonadName
|
||||||
releaseXMonadName ses = liftIO $ void $ releaseName (toClient ses) xmonadBusName
|
-- :: (SafeClient c, MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
-- => c
|
||||||
|
-- -> m ()
|
||||||
|
-- releaseXMonadName cl = do
|
||||||
|
-- -- TODO this might error?
|
||||||
|
-- liftIO $ void $ releaseName (toClient cl) xmonadBusName
|
||||||
|
-- logInfo "released xmonad name"
|
||||||
|
|
||||||
requestXMonadName :: MonadUnliftIO m => SesClient -> m ()
|
-- releaseBusName
|
||||||
requestXMonadName ses = do
|
-- :: (SafeClient c, MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
res <- liftIO $ requestName (toClient ses) xmonadBusName []
|
-- => BusName
|
||||||
-- TODO if the client is not released on shutdown the owner will be different
|
-- -> c
|
||||||
let msg
|
-- -> m ()
|
||||||
| res == NamePrimaryOwner = Nothing
|
-- releaseBusName n cl = do
|
||||||
| res == NameAlreadyOwner = Just $ "this process already owns " ++ xn
|
-- -- TODO this might error?
|
||||||
| res == NameInQueue
|
-- liftIO $ void $ releaseName (toClient cl) n
|
||||||
|| res == NameExists =
|
-- logInfo $ "released bus name: " <> displayBusName n
|
||||||
Just $ "another process owns " ++ xn
|
|
||||||
| otherwise = Just $ "unknown error when requesting " ++ xn
|
-- requestBusName
|
||||||
liftIO $ forM_ msg putStrLn
|
-- :: (SafeClient c, MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
where
|
-- => BusName
|
||||||
xn = "'" ++ formatBusName xmonadBusName ++ "'"
|
-- -> c
|
||||||
|
-- -> m ()
|
||||||
|
-- requestBusName n cl = do
|
||||||
|
-- res <- try $ liftIO $ requestName (toClient cl) n []
|
||||||
|
-- case res of
|
||||||
|
-- Left e -> logError $ displayBytesUtf8 $ BC.pack $ clientErrorMessage e
|
||||||
|
-- Right r -> do
|
||||||
|
-- let msg
|
||||||
|
-- | r == NamePrimaryOwner = "registering name"
|
||||||
|
-- | r == NameAlreadyOwner = "this process already owns name"
|
||||||
|
-- | r == NameInQueue
|
||||||
|
-- || r == NameExists =
|
||||||
|
-- "another process owns name"
|
||||||
|
-- -- this should never happen
|
||||||
|
-- | otherwise = "unknown error when requesting name"
|
||||||
|
-- logInfo $ msg <> ": " <> displayBusName n
|
||||||
|
|
||||||
|
-- requestXMonadName
|
||||||
|
-- :: (SafeClient c, MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
-- => c
|
||||||
|
-- -> m ()
|
||||||
|
-- requestXMonadName cl = do
|
||||||
|
-- res <- liftIO $ requestName (toClient cl) xmonadBusName []
|
||||||
|
-- let msg
|
||||||
|
-- | res == NamePrimaryOwner = "registering name"
|
||||||
|
-- | res == NameAlreadyOwner = "this process already owns name"
|
||||||
|
-- | res == NameInQueue
|
||||||
|
-- || res == NameExists =
|
||||||
|
-- "another process owns name"
|
||||||
|
-- | otherwise = "unknown error when requesting name"
|
||||||
|
-- logInfo $ msg <> ": " <> displayBusName xmonadBusName
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Module for monitoring removable drive events
|
-- Module for monitoring removable drive events
|
||||||
--
|
--
|
||||||
|
@ -11,7 +9,7 @@ module XMonad.Internal.DBus.Removable (runRemovableMon) where
|
||||||
import DBus
|
import DBus
|
||||||
import DBus.Client
|
import DBus.Client
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import RIO
|
import RIO
|
||||||
import qualified RIO.Map as M
|
import qualified RIO.Map as M
|
||||||
import XMonad.Core (io)
|
import XMonad.Core (io)
|
||||||
|
@ -73,25 +71,36 @@ removedHasDrive [_, a] =
|
||||||
(fromVariant a :: Maybe [String])
|
(fromVariant a :: Maybe [String])
|
||||||
removedHasDrive _ = False
|
removedHasDrive _ = False
|
||||||
|
|
||||||
playSoundMaybe :: FilePath -> Bool -> IO ()
|
playSoundMaybe :: MonadUnliftIO m => FilePath -> Bool -> m ()
|
||||||
playSoundMaybe p b = when b $ io $ playSound p
|
playSoundMaybe p b = when b $ io $ playSound p
|
||||||
|
|
||||||
-- NOTE: the udisks2 service should be already running for this module to work.
|
-- NOTE: the udisks2 service should be already running for this module to work.
|
||||||
-- If it not already, we won't see any signals from the dbus until it is
|
-- If it not already, we won't see any signals from the dbus until it is
|
||||||
-- started (it will work after it is started however). It seems safe to simply
|
-- started (it will work after it is started however). It seems safe to simply
|
||||||
-- enable the udisks2 service at boot; however this is not default behavior.
|
-- enable the udisks2 service at boot; however this is not default behavior.
|
||||||
listenDevices :: SysClient -> IO ()
|
listenDevices
|
||||||
|
:: ( HasClient (DBusEnv env)
|
||||||
|
, MonadReader env m
|
||||||
|
, MonadUnliftIO m
|
||||||
|
)
|
||||||
|
=> NamedSysConnection
|
||||||
|
-> m ()
|
||||||
listenDevices cl = do
|
listenDevices cl = do
|
||||||
addMatch' memAdded driveInsertedSound addedHasDrive
|
addMatch' memAdded driveInsertedSound addedHasDrive
|
||||||
addMatch' memRemoved driveRemovedSound removedHasDrive
|
addMatch' memRemoved driveRemovedSound removedHasDrive
|
||||||
where
|
where
|
||||||
addMatch' m p f =
|
addMatch' m p f = do
|
||||||
void $
|
let rule = ruleUdisks {matchMember = Just m}
|
||||||
addMatch (toClient cl) ruleUdisks {matchMember = Just m} $
|
void $ withDIO cl $ addMatchCallback rule (playSoundMaybe p . f)
|
||||||
playSoundMaybe p . f . signalBody
|
|
||||||
|
|
||||||
runRemovableMon :: Maybe SysClient -> SometimesIO
|
runRemovableMon
|
||||||
|
:: ( HasClient (DBusEnv env)
|
||||||
|
, MonadReader env m
|
||||||
|
, MonadUnliftIO m
|
||||||
|
)
|
||||||
|
=> Maybe NamedSysConnection
|
||||||
|
-> Sometimes (m ())
|
||||||
runRemovableMon cl =
|
runRemovableMon cl =
|
||||||
sometimesDBus cl "removeable device monitor" "dbus monitor" deps $ io . listenDevices
|
sometimesDBus cl "removeable device monitor" "dbus monitor" deps listenDevices
|
||||||
where
|
where
|
||||||
deps = toAnd_ addedDep removedDep
|
deps = toAnd_ addedDep removedDep
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- DBus module for X11 screensave/DPMS control
|
-- DBus module for X11 screensave/DPMS control
|
||||||
|
|
||||||
|
@ -16,7 +14,7 @@ import DBus
|
||||||
import DBus.Client
|
import DBus.Client
|
||||||
import qualified DBus.Introspection as I
|
import qualified DBus.Introspection as I
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import Graphics.X11.XScreenSaver
|
import Graphics.X11.XScreenSaver
|
||||||
import RIO
|
import RIO
|
||||||
import XMonad.Internal.DBus.Common
|
import XMonad.Internal.DBus.Common
|
||||||
|
@ -93,20 +91,20 @@ bodyGetCurrentState _ = Nothing
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Exported haskell API
|
-- Exported haskell API
|
||||||
|
|
||||||
exportScreensaver :: Maybe SesClient -> SometimesIO
|
exportScreensaver
|
||||||
|
:: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
=> Maybe NamedSesConnection
|
||||||
|
-> Sometimes (m (), m ())
|
||||||
exportScreensaver ses =
|
exportScreensaver ses =
|
||||||
sometimesDBus ses "screensaver toggle" "xset" (toAnd_ bus ssx) cmd
|
sometimesDBus ses "screensaver toggle" "xset" (toAnd_ bus ssx) cmd
|
||||||
where
|
where
|
||||||
cmd cl =
|
cmd = exportPair ssPath $ \cl_ -> do
|
||||||
let cl' = toClient cl
|
liftIO $ withRunInIO $ \run ->
|
||||||
in withRunInIO $ \run ->
|
return $
|
||||||
export
|
|
||||||
cl'
|
|
||||||
ssPath
|
|
||||||
defaultInterface
|
defaultInterface
|
||||||
{ interfaceName = interface
|
{ interfaceName = interface
|
||||||
, interfaceMethods =
|
, interfaceMethods =
|
||||||
[ autoMethod memToggle $ run $ emitState cl' =<< toggle
|
[ autoMethod memToggle $ run $ emitState cl_ =<< toggle
|
||||||
, autoMethod memQuery (run query)
|
, autoMethod memQuery (run query)
|
||||||
]
|
]
|
||||||
, interfaceSignals = [sig]
|
, interfaceSignals = [sig]
|
||||||
|
@ -121,32 +119,42 @@ exportScreensaver ses =
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
bus = Bus [] xmonadBusName
|
bus = Bus [] xmonadSesBusName
|
||||||
ssx = DBusIO $ sysExe [Package Official "xorg-xset"] ssExecutable
|
ssx = DBusIO $ sysExe [Package Official "xorg-xset"] ssExecutable
|
||||||
|
|
||||||
callToggle :: Maybe SesClient -> SometimesX
|
callToggle
|
||||||
|
:: (HasClient (DBusEnv env), MonadReader env m, MonadUnliftIO m)
|
||||||
|
=> Maybe NamedSesConnection
|
||||||
|
-> Sometimes (m ())
|
||||||
callToggle =
|
callToggle =
|
||||||
sometimesEndpoint
|
sometimesEndpoint
|
||||||
"screensaver toggle"
|
"screensaver toggle"
|
||||||
"dbus switch"
|
"dbus switch"
|
||||||
[]
|
[]
|
||||||
xmonadBusName
|
xmonadSesBusName
|
||||||
ssPath
|
ssPath
|
||||||
interface
|
interface
|
||||||
memToggle
|
memToggle
|
||||||
|
|
||||||
callQuery :: MonadUnliftIO m => SesClient -> m (Maybe SSState)
|
callQuery
|
||||||
callQuery ses = do
|
:: (HasClient env, MonadReader (env SesClient) m, MonadUnliftIO m)
|
||||||
reply <- callMethod ses xmonadBusName ssPath interface memQuery
|
=> m (Maybe SSState)
|
||||||
|
callQuery = do
|
||||||
|
reply <- callMethod xmonadSesBusName ssPath interface memQuery
|
||||||
return $ either (const Nothing) bodyGetCurrentState reply
|
return $ either (const Nothing) bodyGetCurrentState reply
|
||||||
|
|
||||||
matchSignal :: MonadUnliftIO m => (Maybe SSState -> m ()) -> SesClient -> m ()
|
matchSignal
|
||||||
matchSignal cb ses =
|
:: ( HasClient env
|
||||||
|
, MonadReader (env SesClient) m
|
||||||
|
, MonadUnliftIO m
|
||||||
|
)
|
||||||
|
=> (Maybe SSState -> m ())
|
||||||
|
-> m ()
|
||||||
|
matchSignal cb =
|
||||||
void $
|
void $
|
||||||
addMatchCallback
|
addMatchCallback
|
||||||
ruleCurrentState
|
ruleCurrentState
|
||||||
(cb . bodyGetCurrentState)
|
(cb . bodyGetCurrentState)
|
||||||
ses
|
|
||||||
|
|
||||||
ssSignalDep :: DBusDependency_ SesClient
|
ssSignalDep :: DBusDependency_ SesClient
|
||||||
ssSignalDep = Endpoint [] xmonadBusName ssPath interface $ Signal_ memState
|
ssSignalDep = Endpoint [] xmonadSesBusName ssPath interface $ Signal_ memState
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE ViewPatterns #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Random IO-ish functions used throughtout xmonad
|
-- Random IO-ish functions used throughtout xmonad
|
||||||
--
|
--
|
||||||
|
@ -41,7 +39,7 @@ import System.Process
|
||||||
-- read
|
-- read
|
||||||
|
|
||||||
readInt :: MonadIO m => (Read a, Integral a) => FilePath -> m a
|
readInt :: MonadIO m => (Read a, Integral a) => FilePath -> m a
|
||||||
readInt = fmap (read . takeWhile isDigit . T.unpack) . readFileUtf8
|
readInt = fmap (fromMaybe 0 . readMaybe . takeWhile isDigit . T.unpack) . readFileUtf8
|
||||||
|
|
||||||
readBool :: MonadIO m => FilePath -> m Bool
|
readBool :: MonadIO m => FilePath -> m Bool
|
||||||
readBool = fmap (== (1 :: Int)) . readInt
|
readBool = fmap (== (1 :: Int)) . readInt
|
||||||
|
@ -49,7 +47,7 @@ readBool = fmap (== (1 :: Int)) . readInt
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- write
|
-- write
|
||||||
|
|
||||||
writeInt :: MonadIO m => (Show a, Integral a) => FilePath -> a -> m ()
|
writeInt :: (MonadIO m, Show a) => FilePath -> a -> m ()
|
||||||
writeInt f = writeFileUtf8 f . T.pack . show
|
writeInt f = writeFileUtf8 f . T.pack . show
|
||||||
|
|
||||||
writeBool :: MonadIO m => FilePath -> Bool -> m ()
|
writeBool :: MonadIO m => FilePath -> Bool -> m ()
|
||||||
|
@ -62,7 +60,7 @@ writeBool f b = writeInt f ((if b then 1 else 0) :: Int)
|
||||||
-- value. Assume that the file being read has a min of 0 and an unchanging max
|
-- value. Assume that the file being read has a min of 0 and an unchanging max
|
||||||
-- given by a runtime argument, which is scaled linearly to the range 0-100
|
-- given by a runtime argument, which is scaled linearly to the range 0-100
|
||||||
-- (percent).
|
-- (percent).
|
||||||
rawToPercent :: (Integral a, Integral b, Read b, RealFrac c) => (a, a) -> b -> c
|
rawToPercent :: (Integral a, Integral b, RealFrac c) => (a, a) -> b -> c
|
||||||
rawToPercent (lower, upper) raw =
|
rawToPercent (lower, upper) raw =
|
||||||
100 * (fromIntegral raw - fromIntegral lower) / fromIntegral (upper - lower)
|
100 * (fromIntegral raw - fromIntegral lower) / fromIntegral (upper - lower)
|
||||||
|
|
||||||
|
@ -166,7 +164,7 @@ getPermissionsSafe f = do
|
||||||
-- | Block until a PID has exited.
|
-- | Block until a PID has exited.
|
||||||
-- Use this to control flow based on a process that was not explicitly started
|
-- Use this to control flow based on a process that was not explicitly started
|
||||||
-- by the Haskell runtime itself, and thus has no data structures to query.
|
-- by the Haskell runtime itself, and thus has no data structures to query.
|
||||||
waitUntilExit :: (MonadIO m) => Pid -> m ()
|
waitUntilExit :: (MonadUnliftIO m) => Pid -> m ()
|
||||||
waitUntilExit pid = do
|
waitUntilExit pid = do
|
||||||
res <- doesDirectoryExist $ "/proc" </> show pid
|
res <- doesDirectoryExist $ "/proc" </> show pid
|
||||||
when res $ do
|
when res $ do
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Functions for formatting and sending notifications
|
-- Functions for formatting and sending notifications
|
||||||
--
|
--
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
-- Functions for formatting and spawning shell commands
|
-- Functions for formatting and spawning shell commands
|
||||||
|
|
||||||
module XMonad.Internal.Shell
|
module XMonad.Internal.Shell
|
||||||
|
@ -85,8 +83,31 @@ spawn :: MonadIO m => T.Text -> m ()
|
||||||
spawn = X.spawn . T.unpack
|
spawn = X.spawn . T.unpack
|
||||||
|
|
||||||
-- | Run 'XMonad.Run.Utils.spawnPipe' with 'Text' input.
|
-- | Run 'XMonad.Run.Utils.spawnPipe' with 'Text' input.
|
||||||
spawnPipe :: MonadIO m => T.Text -> m Handle
|
spawnPipe :: MonadUnliftIO m => T.Text -> m Handle
|
||||||
spawnPipe = XR.spawnPipe . T.unpack
|
spawnPipe = liftIO . XR.spawnPipe . T.unpack
|
||||||
|
|
||||||
|
-- spawnPipeRW
|
||||||
|
-- :: (MonadReader env m, HasLogFunc env, MonadUnliftIO m)
|
||||||
|
-- => T.Text
|
||||||
|
-- -> m Handle
|
||||||
|
-- spawnPipeRW x = do
|
||||||
|
-- (r, h) <- liftIO mkPipe
|
||||||
|
-- child r
|
||||||
|
-- liftIO $ closeFd r
|
||||||
|
-- return h
|
||||||
|
-- where
|
||||||
|
-- mkPipe = do
|
||||||
|
-- (r, w) <- createPipe
|
||||||
|
-- setFdOption w CloseOnExec True
|
||||||
|
-- h <- fdToHandle w
|
||||||
|
-- -- ASSUME we are using utf8 everywhere
|
||||||
|
-- hSetEncoding h utf8
|
||||||
|
-- hSetBuffering h LineBuffering
|
||||||
|
-- return (r, h)
|
||||||
|
-- child r = void $ withRunInIO $ \runIO -> do
|
||||||
|
-- X.xfork $ runIO $ do
|
||||||
|
-- void $ liftIO $ dupTo r stdInput
|
||||||
|
-- liftIO $ executeFile "/bin/sh" False ["-c", T.unpack x] Nothing
|
||||||
|
|
||||||
-- | Run 'XMonad.Core.spawn' with a command and arguments
|
-- | Run 'XMonad.Core.spawn' with a command and arguments
|
||||||
spawnCmd :: MonadIO m => FilePath -> [T.Text] -> m ()
|
spawnCmd :: MonadIO m => FilePath -> [T.Text] -> m ()
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Theme for XMonad and Xmobar
|
-- Theme for XMonad and Xmobar
|
||||||
|
|
||||||
|
@ -33,6 +31,7 @@ where
|
||||||
|
|
||||||
import Data.Colour
|
import Data.Colour
|
||||||
import Data.Colour.SRGB
|
import Data.Colour.SRGB
|
||||||
|
import RIO
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import qualified XMonad.Layout.Decoration as D
|
import qualified XMonad.Layout.Decoration as D
|
||||||
import qualified XMonad.Prompt as P
|
import qualified XMonad.Prompt as P
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- NetworkManager Connection plugin
|
||||||
|
--
|
||||||
|
-- Show active connections of varying types.
|
||||||
|
--
|
||||||
|
-- This plugin exclusively monitors the */ActiveConnection/* paths in the
|
||||||
|
-- NetworkManager DBus path for state changes. It does not pin these to any
|
||||||
|
-- particular interface but instead looks at all connections equally and filters
|
||||||
|
-- based on their Type (ethernet, wifi, VPN, etc). For many use cases this will
|
||||||
|
-- track well enough with either one or a collection of similar interfaces (ie
|
||||||
|
-- all ethernet or all wifi).
|
||||||
|
|
||||||
|
module Xmobar.Plugins.ActiveConnection
|
||||||
|
( ActiveConnection (..)
|
||||||
|
, devDep
|
||||||
|
, connAlias
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import DBus
|
||||||
|
import Data.Internal.DBus
|
||||||
|
import Data.Internal.XIO
|
||||||
|
import RIO
|
||||||
|
import qualified RIO.Map as M
|
||||||
|
import qualified RIO.NonEmpty as NE
|
||||||
|
import qualified RIO.Text as T
|
||||||
|
import XMonad.Internal.Command.Desktop
|
||||||
|
import XMonad.Internal.DBus.Common
|
||||||
|
import Xmobar
|
||||||
|
import Xmobar.Plugins.Common
|
||||||
|
|
||||||
|
newtype ActiveConnection
|
||||||
|
= ActiveConnection (NE.NonEmpty T.Text, T.Text, Colors)
|
||||||
|
deriving (Read, Show)
|
||||||
|
|
||||||
|
connAlias :: NE.NonEmpty T.Text -> T.Text
|
||||||
|
connAlias = T.intercalate "_" . NE.toList
|
||||||
|
|
||||||
|
instance Exec ActiveConnection where
|
||||||
|
alias (ActiveConnection (contypes, _, _)) = T.unpack $ connAlias contypes
|
||||||
|
start (ActiveConnection (contypes, text, colors)) cb =
|
||||||
|
withDBusClientConnection cb Nothing (Just "ethernet.log") $ \c -> do
|
||||||
|
let dpy cb' = displayMaybe cb' formatter . Just =<< readState
|
||||||
|
i <- withDIO c $ initialState contypes
|
||||||
|
s <- newMVar i
|
||||||
|
let mapEnv c' = mapRIO (PluginEnv c' s dpy cb)
|
||||||
|
mapEnv c $ addListener mapEnv >> pluginDisplay
|
||||||
|
where
|
||||||
|
formatter names = return $ case names of
|
||||||
|
[] -> colorText colors False text
|
||||||
|
xs -> T.unwords [colorText colors True text, T.intercalate "|" xs]
|
||||||
|
addListener mapEnv = do
|
||||||
|
res <- matchSignalFull nmBus Nothing (Just nmActiveInterface) (Just stateChanged)
|
||||||
|
case res of
|
||||||
|
Nothing -> logError "could not start listener"
|
||||||
|
Just rule ->
|
||||||
|
-- Start a new connection and RIO process since the parent thread
|
||||||
|
-- will have died before these callbacks fire, therefore the logging
|
||||||
|
-- file descriptor will be closed. This makes a new one
|
||||||
|
-- TODO can I recycle the client?
|
||||||
|
void $
|
||||||
|
addMatchCallbackSignal rule $ \sig ->
|
||||||
|
withDBusClientConnection cb Nothing (Just "ethernet-cb.log") $ \c' ->
|
||||||
|
mapEnv c' $
|
||||||
|
testActiveType contypes sig
|
||||||
|
|
||||||
|
nmBus :: BusName
|
||||||
|
nmBus = "org.freedesktop.NetworkManager"
|
||||||
|
|
||||||
|
nmPath :: ObjectPath
|
||||||
|
nmPath = "/org/freedesktop/NetworkManager"
|
||||||
|
|
||||||
|
nmInterface :: InterfaceName
|
||||||
|
nmInterface = "org.freedesktop.NetworkManager"
|
||||||
|
|
||||||
|
nmObjectTreePath :: ObjectPath
|
||||||
|
nmObjectTreePath = "/org/freedesktop"
|
||||||
|
|
||||||
|
nmActiveInterface :: InterfaceName
|
||||||
|
nmActiveInterface = "org.freedesktop.NetworkManager.Connection.Active"
|
||||||
|
|
||||||
|
stateChanged :: MemberName
|
||||||
|
stateChanged = "StateChanged"
|
||||||
|
|
||||||
|
-- semi-random method to test to ensure that NetworkManager is up and on DBus
|
||||||
|
devDep :: DBusDependency_ SysClient
|
||||||
|
devDep =
|
||||||
|
Endpoint networkManagerPkgs networkManagerBus nmPath nmInterface $
|
||||||
|
Method_ "GetDeviceByIpIface"
|
||||||
|
|
||||||
|
type EthIO = PluginIO EthState SysClient
|
||||||
|
|
||||||
|
type EthState = M.Map ObjectPath T.Text
|
||||||
|
|
||||||
|
getConnectionProp :: MemberName -> ObjectPath -> EthIO [Variant]
|
||||||
|
getConnectionProp prop path = callPropertyGet nmBus path nmActiveInterface prop
|
||||||
|
|
||||||
|
getConnectionId :: ObjectPath -> EthIO (Maybe T.Text)
|
||||||
|
getConnectionId = fmap fromSingletonVariant . getConnectionProp "Id"
|
||||||
|
|
||||||
|
getConnectionType :: ObjectPath -> EthIO (Maybe T.Text)
|
||||||
|
getConnectionType = fmap fromSingletonVariant . getConnectionProp "Type"
|
||||||
|
|
||||||
|
updateConnected :: NE.NonEmpty T.Text -> ObjectPath -> EthIO ()
|
||||||
|
updateConnected contypes path = do
|
||||||
|
typeRes <- getConnectionType path
|
||||||
|
logMaybe "type" getId typeRes
|
||||||
|
where
|
||||||
|
path' = displayBytesUtf8 $ T.encodeUtf8 $ T.pack $ formatObjectPath path
|
||||||
|
logMaybe what = maybe (logError ("could not get " <> what <> " for " <> path'))
|
||||||
|
getId contype = do
|
||||||
|
when (contype `elem` contypes) $ do
|
||||||
|
idRes <- getConnectionId path
|
||||||
|
logMaybe "ID" insertId idRes
|
||||||
|
insertId i = do
|
||||||
|
s <- asks plugState
|
||||||
|
modifyMVar_ s $ return . M.insert path i
|
||||||
|
|
||||||
|
updateDisconnected :: ObjectPath -> EthIO ()
|
||||||
|
updateDisconnected path = do
|
||||||
|
s <- asks plugState
|
||||||
|
modifyMVar_ s $ return . M.delete path
|
||||||
|
|
||||||
|
testActiveType :: NE.NonEmpty T.Text -> Signal -> EthIO ()
|
||||||
|
testActiveType contypes sig = do
|
||||||
|
case signalBody sig of
|
||||||
|
[state, _] -> case fromVariant state of
|
||||||
|
Just (2 :: Word32) -> updateConnected contypes path >> pluginDisplay
|
||||||
|
Just 4 -> updateDisconnected path >> pluginDisplay
|
||||||
|
_ -> return ()
|
||||||
|
_ -> return ()
|
||||||
|
where
|
||||||
|
path = signalPath sig
|
||||||
|
|
||||||
|
initialState
|
||||||
|
:: ( SafeClient c
|
||||||
|
, MonadUnliftIO m
|
||||||
|
, MonadReader (env c) m
|
||||||
|
, HasClient env
|
||||||
|
, HasLogFunc (env c)
|
||||||
|
)
|
||||||
|
=> NE.NonEmpty T.Text
|
||||||
|
-> m EthState
|
||||||
|
initialState contypes =
|
||||||
|
M.mapMaybe go <$> callGetManagedObjects nmBus nmObjectTreePath
|
||||||
|
where
|
||||||
|
go = getId <=< M.lookup nmActiveInterface
|
||||||
|
getId m =
|
||||||
|
fromVariant
|
||||||
|
=<< (\t -> if t `elem` contypes then M.lookup "Id" m else Nothing)
|
||||||
|
=<< fromVariant
|
||||||
|
=<< M.lookup "Type" m
|
||||||
|
|
||||||
|
readState :: EthIO [T.Text]
|
||||||
|
readState = M.elems <$> (readMVar =<< asks plugState)
|
|
@ -1,11 +1,10 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
-- Common backlight plugin bits
|
-- Common backlight plugin bits
|
||||||
--
|
--
|
||||||
-- Use the custom DBus interface exported by the XMonad process so I can react
|
-- Use the custom DBus interface exported by the XMonad process so I can react
|
||||||
-- to signals spawned by commands
|
-- to signals spawned by commands
|
||||||
module Xmobar.Plugins.BacklightCommon (startBacklight) where
|
module Xmobar.Plugins.BacklightCommon (startBacklight) where
|
||||||
|
|
||||||
|
import DBus
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import RIO
|
import RIO
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
|
@ -13,15 +12,17 @@ import Xmobar.Plugins.Common
|
||||||
|
|
||||||
startBacklight
|
startBacklight
|
||||||
:: (MonadUnliftIO m, RealFrac a)
|
:: (MonadUnliftIO m, RealFrac a)
|
||||||
=> ((Maybe a -> m ()) -> SesClient -> m ())
|
=> Maybe BusName
|
||||||
-> (SesClient -> m (Maybe a))
|
-> Maybe FilePath
|
||||||
|
-> ((Maybe a -> DIO SimpleApp SesClient ()) -> DIO SimpleApp SesClient ())
|
||||||
|
-> DIO SimpleApp SesClient (Maybe a)
|
||||||
-> T.Text
|
-> T.Text
|
||||||
-> Callback
|
-> Callback
|
||||||
-> m ()
|
-> m ()
|
||||||
startBacklight matchSignal callGetBrightness icon cb = do
|
startBacklight n name matchSignal callGetBrightness icon cb = do
|
||||||
withDBusClientConnection cb $ \c -> do
|
withDBusClientConnection cb n name $ \c -> withDIO c $ do
|
||||||
matchSignal dpy c
|
matchSignal dpy
|
||||||
dpy =<< callGetBrightness c
|
dpy =<< callGetBrightness
|
||||||
where
|
where
|
||||||
formatBrightness b = return $ T.concat [icon, T.pack $ show (round b :: Integer), "%"]
|
formatBrightness b = return $ T.concat [icon, T.pack $ show (round b :: Integer), "%"]
|
||||||
dpy = displayMaybe cb formatBrightness
|
dpy = displayMaybe cb formatBrightness
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Bluetooth plugin
|
-- Bluetooth plugin
|
||||||
--
|
--
|
||||||
|
@ -9,28 +7,18 @@
|
||||||
-- Manager. The adapter is located at path "/org/bluez/hci<X>" where X is
|
-- Manager. The adapter is located at path "/org/bluez/hci<X>" where X is
|
||||||
-- usually 0, and each device is "/org/bluez/hci<X>/<MAC_ADDRESS>".
|
-- usually 0, and each device is "/org/bluez/hci<X>/<MAC_ADDRESS>".
|
||||||
--
|
--
|
||||||
-- This plugin will reflect if the adapter is powered and if any device is
|
-- Simple and somewhat crude way to do this is to have two monitors, one
|
||||||
-- connected to it. The rough outline for this procedure:
|
-- watching the powered state of the adaptor and one listening for connection
|
||||||
-- 1) get the adapter from the object manager
|
-- changes. The former is easy since this is just one /org/bluez/hciX. For the
|
||||||
-- 2) get all devices associated with the adapter using the object interface
|
-- latter, each 'Connected' property is embedded in each individual device path
|
||||||
-- 3) determine if the adapter is powered
|
-- on `org.bluez.Device1', so just watch the entire bluez bus for property
|
||||||
-- 4) determine if any devices are connected
|
-- changes and filter those that correspond to the aforementioned
|
||||||
-- 5) format the icon; powered vs not powered controls the color and connected
|
-- interface/property. Track all this in a state which keeps the powered
|
||||||
-- vs not connected controls the icon (connected bluetooth symbol has two
|
-- property and a running list of connected devices.
|
||||||
-- dots flanking it)
|
|
||||||
--
|
|
||||||
-- Step 3 can be accomplished using the "org.bluez.Adapter1" interface and
|
|
||||||
-- querying the "Powered" property. Step 4 can be done using the
|
|
||||||
-- "org.bluez.Device1" interface and the "Connected" property for each device
|
|
||||||
-- path. Since these are properties, we can asynchronously read changes to them
|
|
||||||
-- via the "PropertiesChanged" signal.
|
|
||||||
--
|
|
||||||
-- If any devices are added/removed, steps 2-4 will need to be redone and any
|
|
||||||
-- listeners will need to be updated. (TODO not sure which signals to use in
|
|
||||||
-- determining if a device is added)
|
|
||||||
--
|
--
|
||||||
-- TODO also not sure if I need to care about multiple adapters and/or the
|
-- TODO also not sure if I need to care about multiple adapters and/or the
|
||||||
-- adapter changing.
|
-- adapter changing. For now it should just get the first adaptor and only pay
|
||||||
|
-- attention to devices associated with it.
|
||||||
|
|
||||||
module Xmobar.Plugins.Bluetooth
|
module Xmobar.Plugins.Bluetooth
|
||||||
( Bluetooth (..)
|
( Bluetooth (..)
|
||||||
|
@ -42,11 +30,12 @@ where
|
||||||
import DBus
|
import DBus
|
||||||
import DBus.Client
|
import DBus.Client
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
import Data.Internal.Dependency
|
import Data.Internal.XIO
|
||||||
import RIO
|
import RIO
|
||||||
import RIO.FilePath
|
import RIO.FilePath
|
||||||
import RIO.List
|
import RIO.List
|
||||||
import qualified RIO.Map as M
|
import qualified RIO.Map as M
|
||||||
|
import qualified RIO.Set as S
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import XMonad.Internal.DBus.Common
|
import XMonad.Internal.DBus.Common
|
||||||
import Xmobar
|
import Xmobar
|
||||||
|
@ -65,31 +54,28 @@ data Bluetooth = Bluetooth Icons Colors deriving (Read, Show)
|
||||||
instance Exec Bluetooth where
|
instance Exec Bluetooth where
|
||||||
alias (Bluetooth _ _) = T.unpack btAlias
|
alias (Bluetooth _ _) = T.unpack btAlias
|
||||||
start (Bluetooth icons colors) cb =
|
start (Bluetooth icons colors) cb =
|
||||||
withDBusClientConnection cb $ startAdapter icons colors cb
|
withDBusClientConnection cb Nothing (Just "bluetooth.log") $
|
||||||
|
startAdapter icons colors cb
|
||||||
|
|
||||||
startAdapter
|
startAdapter
|
||||||
:: MonadUnliftIO m
|
:: Icons
|
||||||
=> Icons
|
|
||||||
-> Colors
|
-> Colors
|
||||||
-> Callback
|
-> Callback
|
||||||
-> SysClient
|
-> NamedSysConnection
|
||||||
-> m ()
|
-> RIO SimpleApp ()
|
||||||
startAdapter is cs cb cl = do
|
startAdapter is cs cb cl = do
|
||||||
ot <- getBtObjectTree cl
|
|
||||||
state <- newMVar emptyState
|
state <- newMVar emptyState
|
||||||
let dpy = displayIcon cb (iconFormatter is cs) state
|
let dpy cb' = displayIcon cb' (iconFormatter is cs)
|
||||||
forM_ (findAdapter ot) $ \adapter -> do
|
mapRIO (PluginEnv cl state dpy cb) $ do
|
||||||
-- set up adapter
|
ot <- getBtObjectTree
|
||||||
initAdapter state adapter cl
|
case findAdaptor ot of
|
||||||
-- TODO this step could fail; at least warn the user...
|
Nothing -> logError "could not find bluetooth adapter"
|
||||||
void $ addAdaptorListener state dpy adapter cl
|
Just adaptor -> do
|
||||||
-- set up devices on the adapter (and listeners for adding/removing devices)
|
initAdapterState adaptor
|
||||||
let devices = findDevices adapter ot
|
initDevicesState adaptor ot
|
||||||
addDeviceAddedListener state dpy adapter cl
|
startAdaptorListener adaptor
|
||||||
addDeviceRemovedListener state dpy adapter cl
|
startConnectedListener adaptor
|
||||||
forM_ devices $ \d -> addAndInitDevice state dpy d cl
|
pluginDisplay
|
||||||
-- after setting things up, show the icon based on the initialized state
|
|
||||||
dpy
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Icon Display
|
-- Icon Display
|
||||||
|
@ -101,9 +87,9 @@ type IconFormatter = (Maybe Bool -> Bool -> T.Text)
|
||||||
|
|
||||||
type Icons = (T.Text, T.Text)
|
type Icons = (T.Text, T.Text)
|
||||||
|
|
||||||
displayIcon :: MonadUnliftIO m => Callback -> IconFormatter -> MutableBtState -> m ()
|
displayIcon :: Callback -> IconFormatter -> BTIO ()
|
||||||
displayIcon callback formatter =
|
displayIcon callback formatter =
|
||||||
liftIO . callback . T.unpack . uncurry formatter <=< readState
|
liftIO . callback . T.unpack . uncurry formatter =<< readState
|
||||||
|
|
||||||
-- TODO maybe I want this to fail when any of the device statuses are Nothing
|
-- TODO maybe I want this to fail when any of the device statuses are Nothing
|
||||||
iconFormatter :: Icons -> Colors -> IconFormatter
|
iconFormatter :: Icons -> Colors -> IconFormatter
|
||||||
|
@ -114,47 +100,52 @@ iconFormatter (iconConn, iconDisc) cs powered connected =
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Connection State
|
-- Connection State
|
||||||
--
|
|
||||||
-- The signal handlers all run on separate threads, yet the icon depends on
|
|
||||||
-- the state reflected by all these signals. The best (only?) way to do this is
|
|
||||||
-- is to track the shared state of the bluetooth adaptor and its devices using
|
|
||||||
-- an MVar.
|
|
||||||
|
|
||||||
data BTDevice = BTDevice
|
type BTIO = PluginIO BtState SysClient
|
||||||
{ btDevConnected :: Maybe Bool
|
|
||||||
, btDevSigHandler :: SignalHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectedDevices = M.Map ObjectPath BTDevice
|
|
||||||
|
|
||||||
data BtState = BtState
|
data BtState = BtState
|
||||||
{ btDevices :: ConnectedDevices
|
{ btDevices :: S.Set ObjectPath
|
||||||
, btPowered :: Maybe Bool
|
, btPowered :: Maybe Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type MutableBtState = MVar BtState
|
|
||||||
|
|
||||||
emptyState :: BtState
|
emptyState :: BtState
|
||||||
emptyState =
|
emptyState =
|
||||||
BtState
|
BtState
|
||||||
{ btDevices = M.empty
|
{ btDevices = S.empty
|
||||||
, btPowered = Nothing
|
, btPowered = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
readState :: MonadUnliftIO m => MutableBtState -> m (Maybe Bool, Bool)
|
readState :: BTIO (Maybe Bool, Bool)
|
||||||
readState state = do
|
readState = do
|
||||||
p <- readPowered state
|
p <- readPowered
|
||||||
c <- readDevices state
|
c <- readDevices
|
||||||
return (p, anyDevicesConnected c)
|
return (p, not $ null c)
|
||||||
|
|
||||||
|
modifyState :: (BtState -> BTIO (BtState, a)) -> BTIO a
|
||||||
|
modifyState f = do
|
||||||
|
m <- asks plugState
|
||||||
|
modifyMVar m f
|
||||||
|
|
||||||
|
beforeDisplay :: BTIO () -> BTIO ()
|
||||||
|
beforeDisplay f = f >> pluginDisplay
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Object manager
|
-- Object manager
|
||||||
|
|
||||||
findAdapter :: ObjectTree -> Maybe ObjectPath
|
findAdaptor :: ObjectTree -> Maybe ObjectPath
|
||||||
findAdapter = find (("/org/bluez/hci" `isPrefixOf`) . formatObjectPath) . M.keys
|
findAdaptor = find (("/org/bluez/hci" `isPrefixOf`) . formatObjectPath) . M.keys
|
||||||
|
|
||||||
findDevices :: ObjectPath -> ObjectTree -> [ObjectPath]
|
-- | Search the object tree for devices which are in a connected state.
|
||||||
findDevices adapter = filter (adaptorHasDevice adapter) . M.keys
|
-- Return the object path for said devices.
|
||||||
|
findConnectedDevices :: ObjectPath -> ObjectTree -> [ObjectPath]
|
||||||
|
findConnectedDevices adaptor =
|
||||||
|
filter (adaptorHasDevice adaptor) . M.keys . M.filter isConnectedDev
|
||||||
|
where
|
||||||
|
isConnectedDev m = Just True == lookupState m
|
||||||
|
lookupState =
|
||||||
|
fromVariant
|
||||||
|
<=< M.lookup (memberNameT devConnected)
|
||||||
|
<=< M.lookup devInterface
|
||||||
|
|
||||||
adaptorHasDevice :: ObjectPath -> ObjectPath -> Bool
|
adaptorHasDevice :: ObjectPath -> ObjectPath -> Bool
|
||||||
adaptorHasDevice adaptor device = case splitPathNoRoot device of
|
adaptorHasDevice adaptor device = case splitPathNoRoot device of
|
||||||
|
@ -164,190 +155,120 @@ adaptorHasDevice adaptor device = case splitPathNoRoot device of
|
||||||
splitPathNoRoot :: ObjectPath -> [FilePath]
|
splitPathNoRoot :: ObjectPath -> [FilePath]
|
||||||
splitPathNoRoot = dropWhile (== "/") . splitDirectories . formatObjectPath
|
splitPathNoRoot = dropWhile (== "/") . splitDirectories . formatObjectPath
|
||||||
|
|
||||||
getBtObjectTree :: MonadUnliftIO m => SysClient -> m ObjectTree
|
getBtObjectTree
|
||||||
getBtObjectTree sys = callGetManagedObjects sys btBus btOMPath
|
:: ( HasClient env
|
||||||
|
, SafeClient c
|
||||||
|
, MonadReader (env c) m
|
||||||
|
, HasLogFunc (env c)
|
||||||
|
, MonadUnliftIO m
|
||||||
|
)
|
||||||
|
=> m ObjectTree
|
||||||
|
getBtObjectTree = callGetManagedObjects btBus btOMPath
|
||||||
|
|
||||||
btOMPath :: ObjectPath
|
btOMPath :: ObjectPath
|
||||||
btOMPath = objectPath_ "/"
|
btOMPath = objectPath_ "/"
|
||||||
|
|
||||||
addBtOMListener :: MonadUnliftIO m => SignalCallback m -> SysClient -> m ()
|
|
||||||
addBtOMListener sc = void . addInterfaceAddedListener btBus btOMPath sc
|
|
||||||
|
|
||||||
addDeviceAddedListener
|
|
||||||
:: MonadUnliftIO m
|
|
||||||
=> MutableBtState
|
|
||||||
-> m ()
|
|
||||||
-> ObjectPath
|
|
||||||
-> SysClient
|
|
||||||
-> m ()
|
|
||||||
addDeviceAddedListener state dpy adapter client =
|
|
||||||
addBtOMListener addDevice client
|
|
||||||
where
|
|
||||||
addDevice = pathCallback adapter dpy $ \d ->
|
|
||||||
addAndInitDevice state dpy d client
|
|
||||||
|
|
||||||
addDeviceRemovedListener
|
|
||||||
:: (MonadUnliftIO m)
|
|
||||||
=> MutableBtState
|
|
||||||
-> m ()
|
|
||||||
-> ObjectPath
|
|
||||||
-> SysClient
|
|
||||||
-> m ()
|
|
||||||
addDeviceRemovedListener state dpy adapter sys =
|
|
||||||
addBtOMListener remDevice sys
|
|
||||||
where
|
|
||||||
remDevice = pathCallback adapter dpy $ \d -> do
|
|
||||||
old <- removeDevice state d
|
|
||||||
forM_ old $ liftIO . removeMatch (toClient sys) . btDevSigHandler
|
|
||||||
|
|
||||||
pathCallback :: MonadUnliftIO m => ObjectPath -> m () -> (ObjectPath -> m ()) -> SignalCallback m
|
|
||||||
pathCallback adapter dpy f [device, _] = forM_ (fromVariant device) $ \d ->
|
|
||||||
when (adaptorHasDevice adapter d) $ f d >> dpy
|
|
||||||
pathCallback _ _ _ _ = return ()
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Adapter
|
-- Adapter
|
||||||
|
|
||||||
initAdapter
|
-- | Get powered state of adaptor and log the result
|
||||||
:: (MonadUnliftIO m)
|
initAdapterState :: ObjectPath -> BTIO ()
|
||||||
=> MutableBtState
|
initAdapterState adapter = do
|
||||||
-> ObjectPath
|
reply <- callGetPowered adapter
|
||||||
-> SysClient
|
putPowered $ fromSingletonVariant reply
|
||||||
-> m ()
|
|
||||||
initAdapter state adapter client = do
|
|
||||||
reply <- callGetPowered adapter client
|
|
||||||
putPowered state $ fromSingletonVariant reply
|
|
||||||
|
|
||||||
matchBTProperty
|
matchBTProperty
|
||||||
:: (MonadUnliftIO m)
|
:: ( SafeClient c
|
||||||
=> SysClient
|
, HasClient env
|
||||||
-> ObjectPath
|
, MonadReader (env c) m
|
||||||
|
, HasLogFunc (env c)
|
||||||
|
, MonadUnliftIO m
|
||||||
|
)
|
||||||
|
=> ObjectPath
|
||||||
-> m (Maybe MatchRule)
|
-> m (Maybe MatchRule)
|
||||||
matchBTProperty sys p = matchPropertyFull sys btBus (Just p)
|
matchBTProperty p = matchPropertyFull btBus (Just p)
|
||||||
|
|
||||||
addAdaptorListener
|
-- | Start a listener that monitors changes to the powered state of an adaptor
|
||||||
:: MonadUnliftIO m
|
startAdaptorListener :: ObjectPath -> BTIO ()
|
||||||
=> MutableBtState
|
startAdaptorListener adaptor = do
|
||||||
-> m ()
|
res <- matchBTProperty adaptor
|
||||||
-> ObjectPath
|
case res of
|
||||||
-> SysClient
|
Just rule -> void $ addMatchCallback rule callback
|
||||||
-> m (Maybe SignalHandler)
|
Nothing -> do
|
||||||
addAdaptorListener state dpy adaptor sys = do
|
logError $
|
||||||
rule <- matchBTProperty sys adaptor
|
"could not add listener for prop "
|
||||||
forM rule $ \r -> addMatchCallback r (procMatch . matchPowered) sys
|
<> displayMemberName adaptorPowered
|
||||||
|
<> " on path "
|
||||||
|
<> displayObjectPath adaptor
|
||||||
where
|
where
|
||||||
procMatch = withSignalMatch $ \b -> putPowered state b >> dpy
|
callback sig =
|
||||||
|
withNestedDBusClientConnection Nothing Nothing $
|
||||||
|
withSignalMatch procMatch $
|
||||||
|
matchPropertyChanged adaptorInterface adaptorPowered sig
|
||||||
|
procMatch = beforeDisplay . putPowered
|
||||||
|
|
||||||
callGetPowered :: MonadUnliftIO m => ObjectPath -> SysClient -> m [Variant]
|
callGetPowered
|
||||||
|
:: ( HasClient env
|
||||||
|
, MonadReader (env c) m
|
||||||
|
, HasLogFunc (env c)
|
||||||
|
, SafeClient c
|
||||||
|
, MonadUnliftIO m
|
||||||
|
)
|
||||||
|
=> ObjectPath
|
||||||
|
-> m [Variant]
|
||||||
callGetPowered adapter =
|
callGetPowered adapter =
|
||||||
callPropertyGet btBus adapter adapterInterface $
|
callPropertyGet btBus adapter adaptorInterface adaptorPowered
|
||||||
memberName_ $
|
|
||||||
T.unpack adaptorPowered
|
|
||||||
|
|
||||||
matchPowered :: [Variant] -> SignalMatch Bool
|
putPowered :: Maybe Bool -> BTIO ()
|
||||||
matchPowered = matchPropertyChanged adapterInterface adaptorPowered
|
putPowered ds = modifyState $ \s -> return (s {btPowered = ds}, ())
|
||||||
|
|
||||||
putPowered :: MonadUnliftIO m => MutableBtState -> Maybe Bool -> m ()
|
readPowered :: BTIO (Maybe Bool)
|
||||||
putPowered m ds = modifyMVar_ m (\s -> return s {btPowered = ds})
|
readPowered = fmap btPowered $ readMVar =<< asks plugState
|
||||||
|
|
||||||
readPowered :: MonadUnliftIO m => MutableBtState -> m (Maybe Bool)
|
adaptorInterface :: InterfaceName
|
||||||
readPowered = fmap btPowered . readMVar
|
adaptorInterface = interfaceName_ "org.bluez.Adapter1"
|
||||||
|
|
||||||
adapterInterface :: InterfaceName
|
adaptorPowered :: MemberName
|
||||||
adapterInterface = interfaceName_ "org.bluez.Adapter1"
|
|
||||||
|
|
||||||
adaptorPowered :: T.Text
|
|
||||||
adaptorPowered = "Powered"
|
adaptorPowered = "Powered"
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Devices
|
-- Devices
|
||||||
|
|
||||||
addAndInitDevice
|
initDevicesState :: ObjectPath -> ObjectTree -> BTIO ()
|
||||||
:: MonadUnliftIO m
|
initDevicesState adaptor ot = do
|
||||||
=> MutableBtState
|
let devices = findConnectedDevices adaptor ot
|
||||||
-> m ()
|
modifyState $ \s -> return (s {btDevices = S.fromList devices}, ())
|
||||||
-> ObjectPath
|
|
||||||
-> SysClient
|
|
||||||
-> m ()
|
|
||||||
addAndInitDevice state dpy device client = do
|
|
||||||
sh <- addDeviceListener state dpy device client
|
|
||||||
-- TODO add some intelligent error messages here
|
|
||||||
forM_ sh $ \s -> initDevice state s device client
|
|
||||||
|
|
||||||
initDevice
|
startConnectedListener :: ObjectPath -> BTIO ()
|
||||||
:: MonadUnliftIO m
|
startConnectedListener adaptor = do
|
||||||
=> MutableBtState
|
reply <- matchPropertyFull btBus Nothing
|
||||||
-> SignalHandler
|
case reply of
|
||||||
-> ObjectPath
|
Just rule -> do
|
||||||
-> SysClient
|
void $ addMatchCallbackSignal rule callback
|
||||||
-> m ()
|
logInfo $ "Started listening for device connections on " <> adaptor_
|
||||||
initDevice state sh device sys = do
|
Nothing -> logError "Could not listen for connection changes"
|
||||||
reply <- callGetConnected device sys
|
|
||||||
void $
|
|
||||||
insertDevice state device $
|
|
||||||
BTDevice
|
|
||||||
{ btDevConnected = fromVariant =<< listToMaybe reply
|
|
||||||
, btDevSigHandler = sh
|
|
||||||
}
|
|
||||||
|
|
||||||
addDeviceListener
|
|
||||||
:: MonadUnliftIO m
|
|
||||||
=> MutableBtState
|
|
||||||
-> m ()
|
|
||||||
-> ObjectPath
|
|
||||||
-> SysClient
|
|
||||||
-> m (Maybe SignalHandler)
|
|
||||||
addDeviceListener state dpy device sys = do
|
|
||||||
rule <- matchBTProperty sys device
|
|
||||||
forM rule $ \r -> addMatchCallback r (procMatch . matchConnected) sys
|
|
||||||
where
|
where
|
||||||
procMatch = withSignalMatch $ \c -> updateDevice state device c >> dpy
|
adaptor_ = displayWrapQuote $ displayObjectPath adaptor
|
||||||
|
callback sig =
|
||||||
|
withNestedDBusClientConnection Nothing Nothing $ do
|
||||||
|
let devpath = signalPath sig
|
||||||
|
when (adaptorHasDevice adaptor devpath) $
|
||||||
|
withSignalMatch (update devpath) $
|
||||||
|
matchConnected $
|
||||||
|
signalBody sig
|
||||||
|
matchConnected = matchPropertyChanged devInterface devConnected
|
||||||
|
update _ Nothing = return ()
|
||||||
|
update devpath (Just x) = do
|
||||||
|
let f = if x then S.insert else S.delete
|
||||||
|
beforeDisplay $
|
||||||
|
modifyState $
|
||||||
|
\s -> return (s {btDevices = f devpath $ btDevices s}, ())
|
||||||
|
|
||||||
matchConnected :: [Variant] -> SignalMatch Bool
|
readDevices :: BTIO (S.Set ObjectPath)
|
||||||
matchConnected = matchPropertyChanged devInterface devConnected
|
readDevices = fmap btDevices $ readMVar =<< asks plugState
|
||||||
|
|
||||||
callGetConnected :: MonadUnliftIO m => ObjectPath -> SysClient -> m [Variant]
|
|
||||||
callGetConnected p =
|
|
||||||
callPropertyGet btBus p devInterface $
|
|
||||||
memberName_ (T.unpack devConnected)
|
|
||||||
|
|
||||||
insertDevice
|
|
||||||
:: MonadUnliftIO m
|
|
||||||
=> MutableBtState
|
|
||||||
-> ObjectPath
|
|
||||||
-> BTDevice
|
|
||||||
-> m Bool
|
|
||||||
insertDevice m device dev = modifyMVar m $ \s -> do
|
|
||||||
let new = M.insert device dev $ btDevices s
|
|
||||||
return (s {btDevices = new}, anyDevicesConnected new)
|
|
||||||
|
|
||||||
updateDevice
|
|
||||||
:: MonadUnliftIO m
|
|
||||||
=> MutableBtState
|
|
||||||
-> ObjectPath
|
|
||||||
-> Maybe Bool
|
|
||||||
-> m Bool
|
|
||||||
updateDevice m device status = modifyMVar m $ \s -> do
|
|
||||||
let new = M.update (\d -> Just d {btDevConnected = status}) device $ btDevices s
|
|
||||||
return (s {btDevices = new}, anyDevicesConnected new)
|
|
||||||
|
|
||||||
anyDevicesConnected :: ConnectedDevices -> Bool
|
|
||||||
anyDevicesConnected = or . mapMaybe btDevConnected . M.elems
|
|
||||||
|
|
||||||
removeDevice
|
|
||||||
:: MonadUnliftIO m
|
|
||||||
=> MutableBtState
|
|
||||||
-> ObjectPath
|
|
||||||
-> m (Maybe BTDevice)
|
|
||||||
removeDevice m device = modifyMVar m $ \s -> do
|
|
||||||
let devs = btDevices s
|
|
||||||
return (s {btDevices = M.delete device devs}, M.lookup device devs)
|
|
||||||
|
|
||||||
readDevices :: MonadUnliftIO m => MutableBtState -> m ConnectedDevices
|
|
||||||
readDevices = fmap btDevices . readMVar
|
|
||||||
|
|
||||||
devInterface :: InterfaceName
|
devInterface :: InterfaceName
|
||||||
devInterface = interfaceName_ "org.bluez.Device1"
|
devInterface = interfaceName_ "org.bluez.Device1"
|
||||||
|
|
||||||
devConnected :: T.Text
|
devConnected :: MemberName
|
||||||
devConnected = "Connected"
|
devConnected = "Connected"
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Clevo Keyboard plugin
|
-- Clevo Keyboard plugin
|
||||||
--
|
--
|
||||||
|
@ -12,6 +10,7 @@ module Xmobar.Plugins.ClevoKeyboard
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import RIO
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import XMonad.Internal.DBus.Brightness.ClevoKeyboard
|
import XMonad.Internal.DBus.Brightness.ClevoKeyboard
|
||||||
import Xmobar
|
import Xmobar
|
||||||
|
@ -25,4 +24,9 @@ ckAlias = "clevokeyboard"
|
||||||
instance Exec ClevoKeyboard where
|
instance Exec ClevoKeyboard where
|
||||||
alias (ClevoKeyboard _) = T.unpack ckAlias
|
alias (ClevoKeyboard _) = T.unpack ckAlias
|
||||||
start (ClevoKeyboard icon) =
|
start (ClevoKeyboard icon) =
|
||||||
startBacklight matchSignalCK callGetBrightnessCK icon
|
startBacklight
|
||||||
|
(Just "org.xmobar.clevo")
|
||||||
|
(Just "clevo_kbd.log")
|
||||||
|
matchSignalCK
|
||||||
|
callGetBrightnessCK
|
||||||
|
icon
|
||||||
|
|
|
@ -1,27 +1,52 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
module Xmobar.Plugins.Common
|
module Xmobar.Plugins.Common
|
||||||
( colorText
|
( colorText
|
||||||
, startListener
|
, startListener
|
||||||
, procSignalMatch
|
, procSignalMatch
|
||||||
, na
|
, na
|
||||||
, fromSingletonVariant
|
, fromSingletonVariant
|
||||||
|
, withNestedDBusClientConnection
|
||||||
, withDBusClientConnection
|
, withDBusClientConnection
|
||||||
, Callback
|
, Callback
|
||||||
, Colors (..)
|
, Colors (..)
|
||||||
, displayMaybe
|
, displayMaybe
|
||||||
, displayMaybe'
|
, displayMaybe'
|
||||||
, xmobarFGColor
|
, xmobarFGColor
|
||||||
|
, PluginEnv (..)
|
||||||
|
, PluginIO
|
||||||
|
, pluginDisplay
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import DBus
|
import DBus
|
||||||
import DBus.Client
|
import DBus.Client
|
||||||
import Data.Internal.DBus
|
import Data.Internal.DBus
|
||||||
|
import Data.Internal.XIO
|
||||||
import RIO
|
import RIO
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import XMonad.Hooks.DynamicLog (xmobarColor)
|
import XMonad.Hooks.DynamicLog (xmobarColor)
|
||||||
|
|
||||||
|
data PluginEnv s c = PluginEnv
|
||||||
|
{ plugClient :: !(NamedConnection c)
|
||||||
|
, plugState :: !(MVar s)
|
||||||
|
, plugDisplay :: !(Callback -> PluginIO s c ())
|
||||||
|
, plugCallback :: !Callback
|
||||||
|
, plugEnv :: !SimpleApp
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginDisplay :: PluginIO s c ()
|
||||||
|
pluginDisplay = do
|
||||||
|
cb <- asks plugCallback
|
||||||
|
dpy <- asks plugDisplay
|
||||||
|
dpy cb
|
||||||
|
|
||||||
|
type PluginIO s c = RIO (PluginEnv s c)
|
||||||
|
|
||||||
|
instance HasClient (PluginEnv s) where
|
||||||
|
clientL = lens plugClient (\x y -> x {plugClient = y})
|
||||||
|
|
||||||
|
instance HasLogFunc (PluginEnv s c) where
|
||||||
|
logFuncL = lens plugEnv (\x y -> x {plugEnv = y}) . logFuncL
|
||||||
|
|
||||||
-- use string here since all the callbacks in xmobar use strings :(
|
-- use string here since all the callbacks in xmobar use strings :(
|
||||||
type Callback = String -> IO ()
|
type Callback = String -> IO ()
|
||||||
|
|
||||||
|
@ -32,18 +57,22 @@ data Colors = Colors
|
||||||
deriving (Eq, Show, Read)
|
deriving (Eq, Show, Read)
|
||||||
|
|
||||||
startListener
|
startListener
|
||||||
:: (MonadUnliftIO m, SafeClient c, IsVariant a)
|
:: ( HasClient env
|
||||||
|
, MonadReader (env c) m
|
||||||
|
, MonadUnliftIO m
|
||||||
|
, SafeClient c
|
||||||
|
, IsVariant a
|
||||||
|
)
|
||||||
=> MatchRule
|
=> MatchRule
|
||||||
-> (c -> m [Variant])
|
-> m [Variant]
|
||||||
-> ([Variant] -> SignalMatch a)
|
-> ([Variant] -> SignalMatch a)
|
||||||
-> (a -> m T.Text)
|
-> (a -> m T.Text)
|
||||||
-> Callback
|
-> Callback
|
||||||
-> c
|
|
||||||
-> m ()
|
-> m ()
|
||||||
startListener rule getProp fromSignal toColor cb client = do
|
startListener rule getProp fromSignal toColor cb = do
|
||||||
reply <- getProp client
|
reply <- getProp
|
||||||
displayMaybe cb toColor $ fromSingletonVariant reply
|
displayMaybe cb toColor $ fromSingletonVariant reply
|
||||||
void $ addMatchCallback rule (procMatch . fromSignal) client
|
void $ addMatchCallback rule (procMatch . fromSignal)
|
||||||
where
|
where
|
||||||
procMatch = procSignalMatch cb toColor
|
procMatch = procSignalMatch cb toColor
|
||||||
|
|
||||||
|
@ -70,6 +99,42 @@ displayMaybe' cb = maybe (liftIO $ cb $ T.unpack na)
|
||||||
withDBusClientConnection
|
withDBusClientConnection
|
||||||
:: (MonadUnliftIO m, SafeClient c)
|
:: (MonadUnliftIO m, SafeClient c)
|
||||||
=> Callback
|
=> Callback
|
||||||
-> (c -> m ())
|
-> Maybe BusName
|
||||||
|
-> Maybe FilePath
|
||||||
|
-> (NamedConnection c -> RIO SimpleApp ())
|
||||||
-> m ()
|
-> m ()
|
||||||
withDBusClientConnection cb f = displayMaybe' cb f =<< getDBusClient
|
withDBusClientConnection cb n logfile f =
|
||||||
|
maybe (run stderr) (`withLogFile` run) logfile
|
||||||
|
where
|
||||||
|
run h = do
|
||||||
|
logOpts <- setLogVerboseFormat True . setLogUseTime True <$> logOptionsHandle h False
|
||||||
|
withLogFunc logOpts $ \lf -> do
|
||||||
|
env <- mkSimpleApp lf Nothing
|
||||||
|
runRIO env $ displayMaybe' cb f =<< getDBusClient n
|
||||||
|
|
||||||
|
-- | Run a plugin action with a new DBus client and logfile path. This is
|
||||||
|
-- necessary for DBus callbacks which run in separate threads, which will
|
||||||
|
-- usually fire when the parent thread already exited and killed off its DBus
|
||||||
|
-- connection and closed its logfile. NOTE: unlike 'withDBusClientConnection'
|
||||||
|
-- this function will open and new logfile and client connection and close both
|
||||||
|
-- on completion. 'withDBusClientConnection' will only close the log file but
|
||||||
|
-- keep the client connection active upon termination; this client will only be
|
||||||
|
-- killed when the entire process is killed. This distinction is important
|
||||||
|
-- because callbacks only need ephemeral connections, while listeners (started
|
||||||
|
-- with 'withDBusClientConnection') need long-lasting connections.
|
||||||
|
withNestedDBusClientConnection
|
||||||
|
:: (MonadUnliftIO m, SafeClient c, MonadReader (PluginEnv s c) m)
|
||||||
|
=> Maybe BusName
|
||||||
|
-> Maybe FilePath
|
||||||
|
-> PluginIO s c ()
|
||||||
|
-> m ()
|
||||||
|
withNestedDBusClientConnection n logfile f = do
|
||||||
|
dpy <- asks plugDisplay
|
||||||
|
s <- asks plugState
|
||||||
|
cb <- asks plugCallback
|
||||||
|
let run h = do
|
||||||
|
logOpts <- setLogVerboseFormat True . setLogUseTime True <$> logOptionsHandle h False
|
||||||
|
withLogFunc logOpts $ \lf -> do
|
||||||
|
env <- mkSimpleApp lf Nothing
|
||||||
|
runRIO env $ withDBusClient_ n $ \cl -> mapRIO (PluginEnv cl s dpy cb) f
|
||||||
|
maybe (run stderr) (`withLogFile` run) logfile
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- Device plugin
|
|
||||||
--
|
|
||||||
-- Display different text depending on whether or not the interface has
|
|
||||||
-- connectivity
|
|
||||||
|
|
||||||
module Xmobar.Plugins.Device
|
|
||||||
( Device (..)
|
|
||||||
, devDep
|
|
||||||
)
|
|
||||||
where
|
|
||||||
|
|
||||||
import DBus
|
|
||||||
import Data.Internal.DBus
|
|
||||||
import Data.Internal.Dependency
|
|
||||||
import RIO
|
|
||||||
import qualified RIO.Text as T
|
|
||||||
import XMonad.Internal.Command.Desktop
|
|
||||||
import XMonad.Internal.DBus.Common
|
|
||||||
import Xmobar
|
|
||||||
import Xmobar.Plugins.Common
|
|
||||||
|
|
||||||
newtype Device = Device (T.Text, T.Text, Colors) deriving (Read, Show)
|
|
||||||
|
|
||||||
nmPath :: ObjectPath
|
|
||||||
nmPath = objectPath_ "/org/freedesktop/NetworkManager"
|
|
||||||
|
|
||||||
nmInterface :: InterfaceName
|
|
||||||
nmInterface = interfaceName_ "org.freedesktop.NetworkManager"
|
|
||||||
|
|
||||||
nmDeviceInterface :: InterfaceName
|
|
||||||
nmDeviceInterface = interfaceName_ "org.freedesktop.NetworkManager.Device"
|
|
||||||
|
|
||||||
getByIP :: MemberName
|
|
||||||
getByIP = memberName_ "GetDeviceByIpIface"
|
|
||||||
|
|
||||||
devSignal :: T.Text
|
|
||||||
devSignal = "Ip4Connectivity"
|
|
||||||
|
|
||||||
devDep :: DBusDependency_ SysClient
|
|
||||||
devDep =
|
|
||||||
Endpoint networkManagerPkgs networkManagerBus nmPath nmInterface $
|
|
||||||
Method_ getByIP
|
|
||||||
|
|
||||||
getDevice :: MonadUnliftIO m => SysClient -> T.Text -> m (Maybe ObjectPath)
|
|
||||||
getDevice sys iface = bodyToMaybe <$> callMethod' sys mc
|
|
||||||
where
|
|
||||||
mc =
|
|
||||||
(methodCallBus networkManagerBus nmPath nmInterface getByIP)
|
|
||||||
{ methodCallBody = [toVariant iface]
|
|
||||||
}
|
|
||||||
|
|
||||||
getDeviceConnected :: MonadUnliftIO m => ObjectPath -> SysClient -> m [Variant]
|
|
||||||
getDeviceConnected path =
|
|
||||||
callPropertyGet networkManagerBus path nmDeviceInterface $
|
|
||||||
memberName_ $
|
|
||||||
T.unpack devSignal
|
|
||||||
|
|
||||||
matchStatus :: [Variant] -> SignalMatch Word32
|
|
||||||
matchStatus = matchPropertyChanged nmDeviceInterface devSignal
|
|
||||||
|
|
||||||
instance Exec Device where
|
|
||||||
alias (Device (iface, _, _)) = T.unpack iface
|
|
||||||
start (Device (iface, text, colors)) cb = do
|
|
||||||
withDBusClientConnection cb $ \sys -> do
|
|
||||||
path <- getDevice sys iface
|
|
||||||
displayMaybe' cb (listener sys) path
|
|
||||||
where
|
|
||||||
listener sys path = do
|
|
||||||
rule <- matchPropertyFull sys networkManagerBus (Just path)
|
|
||||||
-- TODO warn the user here rather than silently drop the listener
|
|
||||||
forM_ rule $ \r ->
|
|
||||||
startListener r (getDeviceConnected path) matchStatus chooseColor' cb sys
|
|
||||||
chooseColor' = return . (\s -> colorText colors s text) . (> 1)
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Intel backlight plugin
|
-- Intel backlight plugin
|
||||||
--
|
--
|
||||||
|
@ -12,6 +10,7 @@ module Xmobar.Plugins.IntelBacklight
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import RIO
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import XMonad.Internal.DBus.Brightness.IntelBacklight
|
import XMonad.Internal.DBus.Brightness.IntelBacklight
|
||||||
import Xmobar
|
import Xmobar
|
||||||
|
@ -25,4 +24,9 @@ blAlias = "intelbacklight"
|
||||||
instance Exec IntelBacklight where
|
instance Exec IntelBacklight where
|
||||||
alias (IntelBacklight _) = T.unpack blAlias
|
alias (IntelBacklight _) = T.unpack blAlias
|
||||||
start (IntelBacklight icon) =
|
start (IntelBacklight icon) =
|
||||||
startBacklight matchSignalIB callGetBrightnessIB icon
|
startBacklight
|
||||||
|
(Just "org.xmobar.intelbacklight")
|
||||||
|
(Just "intel_backlight.log")
|
||||||
|
matchSignalIB
|
||||||
|
callGetBrightnessIB
|
||||||
|
icon
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Screensaver plugin
|
-- Screensaver plugin
|
||||||
--
|
--
|
||||||
|
@ -12,6 +10,8 @@ module Xmobar.Plugins.Screensaver
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import Data.Internal.DBus
|
||||||
|
import RIO
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import XMonad.Internal.DBus.Screensaver
|
import XMonad.Internal.DBus.Screensaver
|
||||||
import Xmobar
|
import Xmobar
|
||||||
|
@ -24,9 +24,13 @@ ssAlias = "screensaver"
|
||||||
|
|
||||||
instance Exec Screensaver where
|
instance Exec Screensaver where
|
||||||
alias (Screensaver _) = T.unpack ssAlias
|
alias (Screensaver _) = T.unpack ssAlias
|
||||||
start (Screensaver (text, colors)) cb = do
|
start (Screensaver (text, colors)) cb =
|
||||||
withDBusClientConnection cb $ \sys -> do
|
withDBusClientConnection
|
||||||
matchSignal display sys
|
cb
|
||||||
display =<< callQuery sys
|
(Just "org.xmobar.screensaver")
|
||||||
|
(Just "screensaver.log")
|
||||||
|
$ \cl -> withDIO cl $ do
|
||||||
|
matchSignal dpy
|
||||||
|
dpy =<< callQuery
|
||||||
where
|
where
|
||||||
display = displayMaybe cb $ return . (\s -> colorText colors s text)
|
dpy = displayMaybe cb $ return . (\s -> colorText colors s text)
|
||||||
|
|
|
@ -1,133 +0,0 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- VPN plugin
|
|
||||||
--
|
|
||||||
-- Use the networkmanager to detect when a VPN interface is added or removed.
|
|
||||||
-- Specifically, monitor the object tree to detect paths with the interface
|
|
||||||
-- "org.freedesktop.NetworkManager.Device.Tun".
|
|
||||||
|
|
||||||
module Xmobar.Plugins.VPN
|
|
||||||
( VPN (..)
|
|
||||||
, vpnAlias
|
|
||||||
, vpnDep
|
|
||||||
)
|
|
||||||
where
|
|
||||||
|
|
||||||
import DBus
|
|
||||||
import Data.Internal.DBus
|
|
||||||
import Data.Internal.Dependency
|
|
||||||
import RIO
|
|
||||||
import qualified RIO.Map as M
|
|
||||||
import qualified RIO.Set as S
|
|
||||||
import qualified RIO.Text as T
|
|
||||||
import XMonad.Internal.Command.Desktop
|
|
||||||
import XMonad.Internal.DBus.Common
|
|
||||||
import Xmobar
|
|
||||||
import Xmobar.Plugins.Common
|
|
||||||
|
|
||||||
newtype VPN = VPN (T.Text, Colors) deriving (Read, Show)
|
|
||||||
|
|
||||||
instance Exec VPN where
|
|
||||||
alias (VPN _) = T.unpack vpnAlias
|
|
||||||
start (VPN (text, colors)) cb =
|
|
||||||
withDBusClientConnection cb $ \c -> do
|
|
||||||
state <- initState c
|
|
||||||
let dpy = displayMaybe cb iconFormatter . Just =<< readState state
|
|
||||||
let signalCallback' f = f state dpy
|
|
||||||
vpnAddedListener (signalCallback' addedCallback) c
|
|
||||||
vpnRemovedListener (signalCallback' removedCallback) c
|
|
||||||
dpy
|
|
||||||
where
|
|
||||||
iconFormatter b = return $ colorText colors b text
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- VPN State
|
|
||||||
--
|
|
||||||
-- Maintain a set of paths which are the currently active VPNs. Most of the time
|
|
||||||
-- this will be a null or singleton set, but this setup could handle the edge
|
|
||||||
-- case of multiple VPNs being active at once without puking.
|
|
||||||
|
|
||||||
type VPNState = S.Set ObjectPath
|
|
||||||
|
|
||||||
type MutableVPNState = MVar VPNState
|
|
||||||
|
|
||||||
initState :: MonadUnliftIO m => SysClient -> m MutableVPNState
|
|
||||||
initState client = do
|
|
||||||
ot <- getVPNObjectTree client
|
|
||||||
newMVar $ findTunnels ot
|
|
||||||
|
|
||||||
readState :: MonadUnliftIO m => MutableVPNState -> m Bool
|
|
||||||
readState = fmap (not . null) . readMVar
|
|
||||||
|
|
||||||
updateState
|
|
||||||
:: MonadUnliftIO m
|
|
||||||
=> (ObjectPath -> VPNState -> VPNState)
|
|
||||||
-> MutableVPNState
|
|
||||||
-> ObjectPath
|
|
||||||
-> m ()
|
|
||||||
updateState f state op = modifyMVar_ state $ return . f op
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- Tunnel Device Detection
|
|
||||||
|
|
||||||
getVPNObjectTree :: MonadUnliftIO m => SysClient -> m ObjectTree
|
|
||||||
getVPNObjectTree sys = callGetManagedObjects sys vpnBus vpnPath
|
|
||||||
|
|
||||||
findTunnels :: ObjectTree -> VPNState
|
|
||||||
findTunnels = S.fromList . M.keys . M.filter (elem vpnDeviceTun . M.keys)
|
|
||||||
|
|
||||||
vpnAddedListener :: MonadUnliftIO m => SignalCallback m -> SysClient -> m ()
|
|
||||||
vpnAddedListener cb = void . addInterfaceAddedListener vpnBus vpnPath cb
|
|
||||||
|
|
||||||
vpnRemovedListener :: MonadUnliftIO m => SignalCallback m -> SysClient -> m ()
|
|
||||||
vpnRemovedListener cb = void . addInterfaceRemovedListener vpnBus vpnPath cb
|
|
||||||
|
|
||||||
addedCallback :: MonadUnliftIO m => MutableVPNState -> m () -> SignalCallback m
|
|
||||||
addedCallback state dpy [device, added] = update >> dpy
|
|
||||||
where
|
|
||||||
added' = fromVariant added :: Maybe (M.Map T.Text (M.Map T.Text Variant))
|
|
||||||
is = M.keys $ fromMaybe M.empty added'
|
|
||||||
update = updateDevice S.insert state device is
|
|
||||||
addedCallback _ _ _ = return ()
|
|
||||||
|
|
||||||
removedCallback :: MonadUnliftIO m => MutableVPNState -> m () -> SignalCallback m
|
|
||||||
removedCallback state dpy [device, interfaces] = update >> dpy
|
|
||||||
where
|
|
||||||
is = fromMaybe [] $ fromVariant interfaces :: [T.Text]
|
|
||||||
update = updateDevice S.delete state device is
|
|
||||||
removedCallback _ _ _ = return ()
|
|
||||||
|
|
||||||
updateDevice
|
|
||||||
:: MonadUnliftIO m
|
|
||||||
=> (ObjectPath -> VPNState -> VPNState)
|
|
||||||
-> MutableVPNState
|
|
||||||
-> Variant
|
|
||||||
-> [T.Text]
|
|
||||||
-> m ()
|
|
||||||
updateDevice f state device interfaces =
|
|
||||||
when (vpnDeviceTun `elem` interfaces) $
|
|
||||||
forM_ d $
|
|
||||||
updateState f state
|
|
||||||
where
|
|
||||||
d = fromVariant device :: Maybe ObjectPath
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- DBus Interface
|
|
||||||
|
|
||||||
vpnBus :: BusName
|
|
||||||
vpnBus = busName_ "org.freedesktop.NetworkManager"
|
|
||||||
|
|
||||||
vpnPath :: ObjectPath
|
|
||||||
vpnPath = objectPath_ "/org/freedesktop"
|
|
||||||
|
|
||||||
vpnDeviceTun :: T.Text
|
|
||||||
vpnDeviceTun = "org.freedesktop.NetworkManager.Device.Tun"
|
|
||||||
|
|
||||||
vpnAlias :: T.Text
|
|
||||||
vpnAlias = "vpn"
|
|
||||||
|
|
||||||
vpnDep :: DBusDependency_ SysClient
|
|
||||||
vpnDep =
|
|
||||||
Endpoint networkManagerPkgs networkManagerBus vpnPath omInterface $
|
|
||||||
Method_ getManagedObjects
|
|
69
package.yaml
69
package.yaml
|
@ -9,9 +9,47 @@ extra-source-files:
|
||||||
- README.md
|
- README.md
|
||||||
- fourmolu.yaml
|
- fourmolu.yaml
|
||||||
- make_pkgs
|
- make_pkgs
|
||||||
- icons/*
|
- runtime_pkgs
|
||||||
|
- assets/icons/*
|
||||||
|
- assets/sound/*
|
||||||
- scripts/*
|
- scripts/*
|
||||||
- sound/*
|
|
||||||
|
default-extensions:
|
||||||
|
- OverloadedStrings
|
||||||
|
- FlexibleContexts
|
||||||
|
- FlexibleInstances
|
||||||
|
- InstanceSigs
|
||||||
|
- MultiParamTypeClasses
|
||||||
|
- EmptyCase
|
||||||
|
- LambdaCase
|
||||||
|
- MultiWayIf
|
||||||
|
- NamedFieldPuns
|
||||||
|
- TupleSections
|
||||||
|
- DeriveFoldable
|
||||||
|
- DeriveFunctor
|
||||||
|
- DeriveGeneric
|
||||||
|
- DeriveLift
|
||||||
|
- DeriveTraversable
|
||||||
|
- DerivingStrategies
|
||||||
|
- DeriveDataTypeable
|
||||||
|
- EmptyDataDecls
|
||||||
|
- PartialTypeSignatures
|
||||||
|
- GeneralizedNewtypeDeriving
|
||||||
|
- StandaloneDeriving
|
||||||
|
- BangPatterns
|
||||||
|
- TypeOperators
|
||||||
|
- ScopedTypeVariables
|
||||||
|
- TypeApplications
|
||||||
|
- ConstraintKinds
|
||||||
|
- RankNTypes
|
||||||
|
- GADTs
|
||||||
|
- DefaultSignatures
|
||||||
|
- NoImplicitPrelude
|
||||||
|
- FunctionalDependencies
|
||||||
|
- DataKinds
|
||||||
|
- TypeFamilies
|
||||||
|
- BinaryLiterals
|
||||||
|
- ViewPatterns
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
- rio >= 0.1.21.0
|
- rio >= 0.1.21.0
|
||||||
|
@ -35,14 +73,21 @@ dependencies:
|
||||||
- typed-process >= 0.2.8.0
|
- typed-process >= 0.2.8.0
|
||||||
- network >= 3.1.2.7
|
- network >= 3.1.2.7
|
||||||
- unliftio >= 0.2.21.0
|
- unliftio >= 0.2.21.0
|
||||||
|
- optparse-applicative >= 0.16.1.0
|
||||||
|
|
||||||
|
ghc-options:
|
||||||
|
- -Wall
|
||||||
|
- -Wcompat
|
||||||
|
- -Widentities
|
||||||
|
- -Wincomplete-record-updates
|
||||||
|
- -Wincomplete-uni-patterns
|
||||||
|
- -Wredundant-constraints
|
||||||
|
- -Wpartial-fields
|
||||||
|
- -Werror
|
||||||
|
- -O2
|
||||||
|
|
||||||
library:
|
library:
|
||||||
source-dirs: lib/
|
source-dirs: lib/
|
||||||
ghc-options:
|
|
||||||
- -Wall
|
|
||||||
- -Werror
|
|
||||||
- -Wpartial-fields
|
|
||||||
- -O2
|
|
||||||
|
|
||||||
executables:
|
executables:
|
||||||
xmobar: &bin
|
xmobar: &bin
|
||||||
|
@ -52,21 +97,15 @@ executables:
|
||||||
- xmonad-config
|
- xmonad-config
|
||||||
ghc-options:
|
ghc-options:
|
||||||
- -threaded
|
- -threaded
|
||||||
- -Wall
|
|
||||||
- -Werror
|
|
||||||
- -Wpartial-fields
|
|
||||||
- -O2
|
|
||||||
xmonad:
|
xmonad:
|
||||||
<<: *bin
|
<<: *bin
|
||||||
main: xmonad.hs
|
main: xmonad.hs
|
||||||
ghc-options:
|
ghc-options:
|
||||||
- -threaded
|
- -threaded
|
||||||
- -Wall
|
|
||||||
- -Werror
|
|
||||||
- -Wpartial-fields
|
|
||||||
- -O2
|
|
||||||
# this is needed to avoid writing super complex layout types
|
# this is needed to avoid writing super complex layout types
|
||||||
- -fno-warn-missing-signatures
|
- -fno-warn-missing-signatures
|
||||||
vbox-start:
|
vbox-start:
|
||||||
<<: *bin
|
<<: *bin
|
||||||
main: vbox-start.hs
|
main: vbox-start.hs
|
||||||
|
ghc-options:
|
||||||
|
- -threaded
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
## capture a screenshot using scrot
|
||||||
|
|
||||||
|
SS_DIR="$XDG_CACHE_HOME/screenshots"
|
||||||
|
|
||||||
|
while getopts ":sw" opt; do
|
||||||
|
case ${opt} in
|
||||||
|
s)
|
||||||
|
scrot "$SS_DIR/desktop/%Y-%m-%d-%H:%M:%S_desktop.png"
|
||||||
|
notify-send "Screen captured"
|
||||||
|
;;
|
||||||
|
w)
|
||||||
|
scrot -u "$SS_DIR/window/%Y-%m-%d-%H:%M:%S-\$wx\$h.png"
|
||||||
|
notify-send "Window captured"
|
||||||
|
;;
|
||||||
|
\?)
|
||||||
|
echo "invalid option, read the code"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
## lock the screen using i3lock (and maybe suspend)
|
||||||
|
|
||||||
|
## usage: screenlock [SUSPEND]
|
||||||
|
|
||||||
|
# WORKAROUND make the date show up in the right place on 2+ monitor setups
|
||||||
|
# I want it to only show up on the primary screen, so use xrandr to get the
|
||||||
|
# dimensions and position of the primary monitor and calculate the date position
|
||||||
|
# from that
|
||||||
|
geometry=$(xrandr | sed -n 's/^.*primary \([0-9]*\)x[0-9]*+\([0-9]\)*+[0-9]* .*/\1 \2/p')
|
||||||
|
width=$(echo "$geometry" | cut -f1 -d" ")
|
||||||
|
xpos=$(echo "$geometry" | cut -f2 -d" ")
|
||||||
|
xoffset=$(("$xpos" + "$width" / 2))
|
||||||
|
datepos="$xoffset:600"
|
||||||
|
|
||||||
|
# lock and fork so we can suspend with the screen locked
|
||||||
|
i3lock --color=000000 \
|
||||||
|
--pass-media-keys \
|
||||||
|
--nofork \
|
||||||
|
--ignore-empty-password \
|
||||||
|
--screen=0 \
|
||||||
|
--indicator \
|
||||||
|
--inside-color=00000055 \
|
||||||
|
--insidever-color=00000055 \
|
||||||
|
--insidewrong-color=00000055 \
|
||||||
|
--ring-color=555555ff \
|
||||||
|
--ringwrong-color=ff3333ff \
|
||||||
|
--ringver-color=99ceffff \
|
||||||
|
--keyhl-color=99ceffff \
|
||||||
|
--bshl-color=9523ffff \
|
||||||
|
--line-color=00000000 \
|
||||||
|
--separator-color=00000000 \
|
||||||
|
--clock \
|
||||||
|
--verif-color=99ceffff \
|
||||||
|
--wrong-color=ff8282ff \
|
||||||
|
--time-color=ffffffff \
|
||||||
|
--time-size=72 \
|
||||||
|
--time-str="%H:%M" \
|
||||||
|
--date-color=ffffffff \
|
||||||
|
--date-size=42 \
|
||||||
|
--date-str="%b %d, %Y" \
|
||||||
|
--date-align 0 \
|
||||||
|
--date-pos="$datepos" \
|
||||||
|
--wrong-size=72 \
|
||||||
|
--verif-size=72 \
|
||||||
|
--radius=300 \
|
||||||
|
--ring-width=25 &
|
||||||
|
|
||||||
|
# suspend if we want, and if this machine is currently using a battery
|
||||||
|
batpath=/sys/class/power_supply/BAT0/status
|
||||||
|
|
||||||
|
if [ -f "$batpath" ] && \
|
||||||
|
[ "$(cat $batpath)" == "Discharging" ] && \
|
||||||
|
[ "$1" == "true" ]; then
|
||||||
|
systemctl suspend
|
||||||
|
fi
|
||||||
|
|
||||||
|
# block until the screen is unlocked (since xss-lock expects the locker to exit
|
||||||
|
# only when unlocked)
|
||||||
|
wait
|
|
@ -17,8 +17,8 @@
|
||||||
#
|
#
|
||||||
# resolver: ./custom-snapshot.yaml
|
# resolver: ./custom-snapshot.yaml
|
||||||
# resolver: https://example.com/snapshots/2018-01-01.yaml
|
# resolver: https://example.com/snapshots/2018-01-01.yaml
|
||||||
#resolver: lts-17.4
|
resolver: lts-19.33
|
||||||
resolver: nightly-2022-03-03
|
#resolver: nightly-2022-03-03
|
||||||
|
|
||||||
# User packages to be built.
|
# User packages to be built.
|
||||||
# Various formats can be used as shown in the example below.
|
# Various formats can be used as shown in the example below.
|
||||||
|
|
Loading…
Reference in New Issue