xmonad-config/lib/XMonad/Internal/Dependency.hs

247 lines
7.6 KiB
Haskell

--------------------------------------------------------------------------------
-- | Functions for handling dependencies
module XMonad.Internal.Dependency
( MaybeExe(..)
, UnitType(..)
, Dependency(..)
, DependencyData(..)
, MaybeX
, exe
, systemUnit
, userUnit
, pathR
, pathW
, pathRW
, checkInstalled
, createInstalled
, runIfInstalled
, depInstalled
, warnMissing
, whenInstalled
, ifInstalled
, spawnIfInstalled
, spawnCmdIfInstalled
, noCheck
, fmtCmd
, spawnCmd
, doubleQuote
, singleQuote
, (#!&&)
, (#!||)
, (#!|)
, (#!>>)
, playSound
, spawnSound
) where
import Control.Arrow ((***))
import Control.Monad (filterM, join)
import Control.Monad.IO.Class
import Data.List (partition)
import Data.Maybe (isJust)
import System.Directory (findExecutable, readable, writable)
import System.Exit
import System.FilePath
import XMonad.Core (X, getXMonadDir)
import XMonad.Internal.IO
import XMonad.Internal.Process
import XMonad.Internal.Shell
--------------------------------------------------------------------------------
-- | Gracefully handling missing binaries
data UnitType = SystemUnit | UserUnit deriving (Eq, Show)
data DependencyData = Executable String
| AccessiblePath FilePath Bool Bool
| Systemd UnitType String
deriving (Eq, Show)
data Dependency = Dependency
{ depRequired :: Bool
, depData :: DependencyData
}
deriving (Eq, Show)
exe :: String -> Dependency
exe n = Dependency
{ depRequired = True
, depData = Executable n
}
unit :: UnitType -> String -> Dependency
unit t n = Dependency
{ depRequired = True
, depData = Systemd t n
}
path :: Bool -> Bool -> String -> Dependency
path r w n = Dependency
{ depRequired = True
, depData = AccessiblePath n r w
}
pathR :: String -> Dependency
pathR = path True False
pathW :: String -> Dependency
pathW = path False True
pathRW :: String -> Dependency
pathRW = path True True
systemUnit :: String -> Dependency
systemUnit = unit SystemUnit
userUnit :: String -> Dependency
userUnit = unit UserUnit
-- TODO this is poorly named. This actually represents an action that has
-- one or more dependencies (where "action" is not necessarily executing an exe)
data MaybeExe a = Installed a [DependencyData]
| Missing [DependencyData] [DependencyData]
| Ignore
deriving (Eq, Show)
instance Functor MaybeExe where
fmap f (Installed x ds) = Installed (f x) ds
fmap _ (Missing req opt) = Missing req opt
fmap _ Ignore = Ignore
type MaybeX = MaybeExe (X ())
exeInstalled :: String -> IO Bool
exeInstalled x = isJust <$> findExecutable x
unitInstalled :: UnitType -> String -> IO Bool
unitInstalled u x = do
(rc, _, _) <- readCreateProcessWithExitCode' (shell cmd) ""
return $ case rc of
ExitSuccess -> True
_ -> False
where
cmd = fmtCmd "systemctl" $ ["--user" | u == UserUnit] ++ ["status", x]
-- pathAccessible :: FilePath -> Bool -> Bool -> IO (Maybe String)
pathAccessible :: FilePath -> Bool -> Bool -> IO Bool
pathAccessible p testread testwrite = do
res <- getPermissionsSafe p
let msg = permMsg res
return msg
-- return $ fmap (\m -> m ++ ": " ++ p) msg
where
testPerm False _ _ = Nothing
testPerm True f r = Just $ f r
-- permMsg NotFoundError = Just "file not found"
-- permMsg PermError = Just "could not get permissions"
permMsg NotFoundError = False
permMsg PermError = False
permMsg (PermResult r) =
case (testPerm testread readable r, testPerm testwrite writable r) of
-- (Just False, Just False) -> Just "file not readable or writable"
-- (Just False, _) -> Just "file not readable"
-- (_, Just False) -> Just "file not writable"
-- _ -> Nothing
(Just True, Just True) -> True
(Just True, Nothing) -> True
(Nothing, Just True) -> True
_ -> False
-- (Just False, Just False) -> Just "file not readable or writable"
-- (Just False, _) -> Just "file not readable"
-- (_, Just False) -> Just "file not writable"
-- _ -> Nothing
-- TODO somehow get this to preserve error messages if something isn't found
depInstalled :: DependencyData -> IO Bool
depInstalled (Executable n) = exeInstalled n
depInstalled (Systemd t n) = unitInstalled t n
depInstalled (AccessiblePath p r w) = pathAccessible p r w
-- (AccessiblePath p r w) -> pathAccessible p r w
checkInstalled :: [Dependency] -> IO ([DependencyData], [DependencyData])
checkInstalled = fmap go . filterMissing
where
go = join (***) (fmap depData) . partition depRequired
createInstalled :: [DependencyData] -> [DependencyData] -> a -> MaybeExe a
createInstalled req opt x = if null req then Installed x opt else Missing req opt
filterMissing :: [Dependency] -> IO [Dependency]
filterMissing = filterM (fmap not . depInstalled . depData)
runIfInstalled :: MonadIO m => [Dependency] -> m a -> IO (MaybeExe (m a))
runIfInstalled ds x = do
(req, opt) <- checkInstalled ds
return $ createInstalled req opt x
spawnIfInstalled :: MonadIO m => String -> IO (MaybeExe (m ()))
spawnIfInstalled n = runIfInstalled [exe n] $ spawn n
spawnCmdIfInstalled :: MonadIO m => String -> [String] -> IO (MaybeExe (m ()))
spawnCmdIfInstalled n args = runIfInstalled [exe n] $ spawnCmd n args
whenInstalled :: Monad m => MaybeExe (m ()) -> m ()
whenInstalled = flip ifInstalled skip
ifInstalled :: MaybeExe a -> a -> a
ifInstalled (Installed x _) _ = x
ifInstalled _ alt = alt
noCheck :: Monad m => a () -> m (MaybeExe (a ()))
noCheck = return . flip Installed []
-- not sure what to do with these
soundDir :: FilePath
soundDir = "sound"
spawnSound :: MonadIO m => FilePath -> m () -> m () -> IO (MaybeExe (m ()))
spawnSound file pre post = runIfInstalled [exe "paplay"]
$ pre >> playSound file >> post
playSound :: MonadIO m => FilePath -> m ()
playSound file = do
p <- (</> soundDir </> file) <$> getXMonadDir
-- paplay seems to have less latency than aplay
spawnCmd "paplay" [p]
partitionMissing :: [MaybeExe a] -> ([DependencyData], [DependencyData])
partitionMissing = foldl (\(a, b) -> ((a++) *** (b++)) . go) ([], [])
where
go (Installed _ opt) = ([], opt)
go (Missing req opt) = (req, opt)
go Ignore = ([], [])
fmtMissing :: DependencyData -> String
-- TODO this error message is lame
fmtMissing (AccessiblePath p True False) = "path '" ++ p ++ "' not readable"
fmtMissing (AccessiblePath p False True) = "path '" ++ p ++ "' not writable"
fmtMissing (AccessiblePath p True True) = "path '" ++ p ++ "' not readable/writable"
fmtMissing (AccessiblePath p _ _) = "path '" ++ p ++ "' not ...something"
fmtMissing (Executable n) = "executable '" ++ n ++ "' not found"
fmtMissing (Systemd st n) = "systemd " ++ unitType st ++ " unit '"
++ n ++ "' not found"
where
unitType SystemUnit = "system"
unitType UserUnit = "user"
fmtMsgs :: [DependencyData] -> [DependencyData] -> [String]
fmtMsgs req opt = ("[WARNING] "++)
<$> (("[REQUIRED DEP] "++) . fmtMissing <$> req)
++ (("[OPTIONAL DEP] "++) . fmtMissing <$> opt)
warnMissing :: [MaybeExe a] -> IO ()
warnMissing = mapM_ putStrLn . uncurry fmtMsgs . partitionMissing
-- fmtType (AccessiblePath _ _ _) = undefined
-- splitDeps :: [MaybeExe a] -> ([a], [String])
-- splitDeps xs = undefined
-- splitDeps' :: [m (MaybeExe a)] -> ([m a], [String])
-- splitDeps' xs = undefined