{-# LANGUAGE OverloadedStrings, ViewPatterns #-} module Main where import Network.DBus.Connection import Network.DBus.Message import Network.DBus.Value import Data.Maybe import Control.Applicative import Control.Concurrent import Control.Monad import Control.Monad.Reader import Control.Monad.Identity import System.Process import System.Exit import System.Environment import System.Console.GetOpt import System.Directory import System.FilePath import Data.String instance IsString DString where fromString str = runIdentity (mkDString str) data RunMode = StoreMute --store-mute | StoreUnmute --store-unmute | StoreAll --store | Daemon --daemon, -d | Help --help,-h,-? deriving (Show) data Config = Config { mute :: FilePath , unmute :: FilePath , appPath :: FilePath } deriving (Show) type ConfigM a = ReaderT Config IO a -- Example (unlock) message. Lock would set True instead of False. -- Message { -- mType = Signal, -- mFlags = [NoReplyExpected], -- mSerial = 134, -- mPath = Just "/org/gnome/ScreenSaver", -- mInterface = Just "org.gnome.ScreenSaver", -- mMember = Just "ActiveChanged", -- mErrorName = Nothing, -- mReplySerial = Nothing, -- mDestination = Nothing, -- mSender = Just ":1.19", -- mBody = [Variant (False) {- b -}] -- } -- | constants muteConfig :: String muteConfig = "mute.ctl" unmuteConfig :: String unmuteConfig = "unmute.ctl" alsaCmd :: String alsaCmd = "alsactl" programName :: String programName = "mute-unmute" -- | helpers checkExitCode :: (Monad m) => String -> ExitCode -> m () checkExitCode _ ExitSuccess = return () checkExitCode cmd ef = fail (show (cmd,ef)) alsactl :: (MonadIO m) => String -> String -> m () alsactl cmd file = liftIO (checkExitCode "alsactl" =<< rawSystem alsaCmd [cmd,"-f",file]) mkConfig :: FilePath -> Config mkConfig aPath = Config { mute = aPath muteConfig , unmute = aPath unmuteConfig , appPath = aPath } -- | message handling handleMessage :: Message -> ConfigM () handleMessage msg@(mBody -> [var]) = case fromVariant var of Nothing -> liftIO (putStrLn "Something went wrong, bad type:" >> print msg) Just True -> handleLock Just False -> handleUnlock handleMessage msg = liftIO (putStrLn "Something went wrong, wrong number of elements in mBody:" >> print msg) handleLock, handleUnlock :: ConfigM () handleLock = alsactl "restore" . mute =<< ask handleUnlock = alsactl "restore" . unmute =<< ask storeMute, storeUnmute :: ConfigM () storeUnmute = alsactl "store" . unmute =<< ask storeMute = alsactl "store" . mute =<< ask -- | 'main' functions askMute, askUnmute :: ConfigM () askMute = returnMsg "Mute" >> storeMute askUnmute = returnMsg "Unmute" >> storeUnmute returnMsg :: (MonadIO m) => String -> m () returnMsg msg = liftIO $ (putStrLn (msg ++ " sound card and press RETURN") >> getLine >> return ()) checkAppDir :: ConfigM () checkAppDir = do app <- appPath <$> ask appExist <- liftIO $ doesDirectoryExist app when (not appExist) . liftIO $ do putStrLn "Creating config directory..." createDirectory app checkConfig :: ConfigM () checkConfig = do checkAppDir mu <- mute <$> ask muExist <- liftIO $ doesFileExist mu when (not muExist) $ do liftIO $ putStrLn "Mute config is missing..." askMute un <- unmute <$> ask unExist <- liftIO $ doesFileExist un when (not unExist) $ do liftIO $ putStrLn "Unmute config is missing..." askUnmute daemon :: ConfigM () daemon = do checkConfig path <- mkObjectPath "/org/gnome/ScreenSaver" let clauses = [ MatchType Signal , MatchInterface "org.gnome.ScreenSaver" , MatchMember "ActiveChanged" , MatchPath path] conf <- ask liftIO $ do session <- fromJust <$> getSessionBusAddress conn <- connectToBus session addHandler conn (Just $ clauses) (\m -> runReaderT (handleMessage m) conf) forever (threadDelay (10^6)) programOpt :: [OptDescr RunMode] programOpt = [ Option [] ["--store"] (NoArg StoreAll) "ask for mute and unmute configurations" , Option [] ["--store-mute"] (NoArg StoreMute) "ask for mute configuration" , Option [] ["--store-unmute"] (NoArg StoreUnmute) "ask for unmute configuration" , Option ['d'] ["--daemon"] (NoArg StoreAll) "wait for screensaver state changes" , Option ['h','?'] ["--help"] (NoArg StoreAll) "show help" ] parseOptions :: [String] -> IO RunMode parseOptions args = do case getOpt Permute programOpt args of ([],[],[]) -> return Daemon ([o],[],[]) -> return o other -> fail $ show ("Bad program options" :: String,other) main :: IO () main = do applicationPath <- getAppUserDataDirectory programName m <- parseOptions =<< getArgs (flip runReaderT) (mkConfig applicationPath) $ case m of StoreMute -> checkAppDir >> askMute StoreUnmute -> checkAppDir >> askUnmute StoreAll -> checkAppDir >> askMute >> askUnmute Daemon -> daemon Help -> liftIO $ putStrLn (usageInfo programName programOpt)