REF wrap everything in simpleapp
This commit is contained in:
parent
05ecda045e
commit
c3fc38d785
|
@ -10,17 +10,17 @@ import Bitwarden.Internal
|
||||||
import qualified Data.Text.IO as TI
|
import qualified Data.Text.IO as TI
|
||||||
import Data.Yaml
|
import Data.Yaml
|
||||||
import RIO
|
import RIO
|
||||||
|
import RIO.Directory
|
||||||
import qualified RIO.List as L
|
import qualified RIO.List as L
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import System.Directory
|
|
||||||
import System.Environment
|
|
||||||
import System.FilePath.Posix
|
import System.FilePath.Posix
|
||||||
import System.Posix.Process
|
import System.Posix.Process
|
||||||
|
import UnliftIO.Environment
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = runSimpleApp $ do
|
||||||
hSetBuffering stdout LineBuffering
|
hSetBuffering stdout LineBuffering
|
||||||
TI.putStrLn "OK Pleased to meet you"
|
logInfo "OK Pleased to meet you"
|
||||||
pinentryLoop =<< readPinConf
|
pinentryLoop =<< readPinConf
|
||||||
|
|
||||||
newtype PinConf = PinConf {pcBwName :: T.Text} deriving (Eq, Show)
|
newtype PinConf = PinConf {pcBwName :: T.Text} deriving (Eq, Show)
|
||||||
|
@ -29,25 +29,27 @@ instance FromJSON PinConf where
|
||||||
parseJSON (Object o) = PinConf <$> o .:? "bitwarden-name" .!= "gnupg"
|
parseJSON (Object o) = PinConf <$> o .:? "bitwarden-name" .!= "gnupg"
|
||||||
parseJSON _ = fail "pinentry yaml parse error"
|
parseJSON _ = fail "pinentry yaml parse error"
|
||||||
|
|
||||||
readPinConf :: IO PinConf
|
readPinConf :: RIO SimpleApp PinConf
|
||||||
readPinConf = do
|
readPinConf = do
|
||||||
c <- decodeFileEither =<< pinConfDir
|
c <- liftIO . decodeFileEither =<< pinConfDir
|
||||||
case c of
|
case c of
|
||||||
Left e -> TI.putStrLn (T.pack $ show e) >> exitWith (ExitFailure 1)
|
Left e -> do
|
||||||
|
logError $ displayShow e
|
||||||
|
exitWith (ExitFailure 1)
|
||||||
Right r -> return r
|
Right r -> return r
|
||||||
|
|
||||||
pinConfDir :: IO FilePath
|
pinConfDir :: RIO SimpleApp FilePath
|
||||||
pinConfDir = maybe defHome (return . (</> confname)) =<< lookupEnv "GNUPGHOME"
|
pinConfDir = maybe defHome (return . (</> confname)) =<< lookupEnv "GNUPGHOME"
|
||||||
where
|
where
|
||||||
defHome = (</> ".gnupg" </> confname) <$> getHomeDirectory
|
defHome = (</> ".gnupg" </> confname) <$> getHomeDirectory
|
||||||
confname = "pinentry-rofi.yml"
|
confname = "pinentry-rofi.yml"
|
||||||
|
|
||||||
pinentryLoop :: PinConf -> IO ()
|
pinentryLoop :: PinConf -> RIO SimpleApp ()
|
||||||
pinentryLoop p = do
|
pinentryLoop p = do
|
||||||
processLine p . T.words =<< TI.getLine
|
processLine p . T.words =<< liftIO TI.getLine
|
||||||
pinentryLoop p
|
pinentryLoop p
|
||||||
|
|
||||||
processLine :: PinConf -> [T.Text] -> IO ()
|
processLine :: PinConf -> [T.Text] -> RIO SimpleApp ()
|
||||||
processLine _ [] = noop
|
processLine _ [] = noop
|
||||||
processLine _ ["BYE"] = exitSuccess
|
processLine _ ["BYE"] = exitSuccess
|
||||||
processLine p ["GETPIN"] = getPin p
|
processLine p ["GETPIN"] = getPin p
|
||||||
|
@ -66,33 +68,36 @@ processLine _ ["CONFIRM"] = noop
|
||||||
processLine _ ["CONFIRM", "--one-button", _] = noop
|
processLine _ ["CONFIRM", "--one-button", _] = noop
|
||||||
processLine _ ss = unknownCommand $ T.unwords ss
|
processLine _ ss = unknownCommand $ T.unwords ss
|
||||||
|
|
||||||
unknownCommand :: T.Text -> IO ()
|
unknownCommand :: T.Text -> RIO SimpleApp ()
|
||||||
unknownCommand c = TI.putStrLn $ T.append "ERR 275 Unknown command " c
|
unknownCommand c = putStrLnT $ T.append "ERR 275 Unknown command " c
|
||||||
|
|
||||||
getPin :: PinConf -> IO ()
|
getPin :: PinConf -> RIO SimpleApp ()
|
||||||
getPin p = do
|
getPin p = do
|
||||||
its <- getItems
|
its <- getItems
|
||||||
let w = (password . login) =<< L.find (\i -> pcBwName p == name i) its
|
let w = (password . login) =<< L.find (\i -> pcBwName p == name i) its
|
||||||
maybe err send w
|
maybe err send w
|
||||||
where
|
where
|
||||||
err = TI.putStrLn "ERR 83886179 Operation canceled <rofi>"
|
err = putStrLnT "ERR 83886179 Operation canceled <rofi>"
|
||||||
|
|
||||||
-- these are the only supported options for GETINFO; anything else is an error
|
-- these are the only supported options for GETINFO; anything else is an error
|
||||||
processGetInfo :: T.Text -> IO ()
|
processGetInfo :: T.Text -> RIO SimpleApp ()
|
||||||
processGetInfo "pid" = send . T.pack . show =<< getProcessID
|
processGetInfo "pid" = send . T.pack . show =<< liftIO getProcessID
|
||||||
processGetInfo "version" = noop
|
processGetInfo "version" = noop
|
||||||
processGetInfo "flavor" = noop
|
processGetInfo "flavor" = noop
|
||||||
processGetInfo "ttyinfo" = noop
|
processGetInfo "ttyinfo" = noop
|
||||||
processGetInfo _ = TI.putStrLn "ERR 83886360 IPC parameter error <rofi>"
|
processGetInfo _ = putStrLnT "ERR 83886360 IPC parameter error <rofi>"
|
||||||
|
|
||||||
processOption :: T.Text -> IO ()
|
processOption :: T.Text -> RIO SimpleApp ()
|
||||||
processOption _ = noop
|
processOption _ = noop
|
||||||
|
|
||||||
send :: T.Text -> IO ()
|
send :: T.Text -> RIO SimpleApp ()
|
||||||
send s = TI.putStrLn (T.append "D " s) >> ok
|
send s = putStrLnT (T.append "D " s) >> ok
|
||||||
|
|
||||||
noop :: IO ()
|
noop :: RIO SimpleApp ()
|
||||||
noop = ok
|
noop = ok
|
||||||
|
|
||||||
ok :: IO ()
|
ok :: RIO SimpleApp ()
|
||||||
ok = TI.putStrLn "OK"
|
ok = putStrLnT "OK"
|
||||||
|
|
||||||
|
putStrLnT :: MonadIO m => T.Text -> m ()
|
||||||
|
putStrLnT = liftIO . TI.putStrLn
|
||||||
|
|
|
@ -5,29 +5,27 @@
|
||||||
|
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
|
|
||||||
import Control.Monad
|
|
||||||
import Data.Maybe
|
|
||||||
import qualified Data.Text.IO as TI
|
|
||||||
import RIO
|
import RIO
|
||||||
|
import RIO.Directory
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import Rofi.Command
|
import Rofi.Command
|
||||||
import System.Directory
|
|
||||||
import System.Environment
|
|
||||||
import System.FilePath.Posix
|
import System.FilePath.Posix
|
||||||
import System.Process
|
import System.Process
|
||||||
|
import UnliftIO.Environment
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = runChecks >> getArgs >>= runPrompt
|
main = runSimpleApp $ do
|
||||||
|
runChecks
|
||||||
|
getArgs >>= runPrompt
|
||||||
|
|
||||||
-- TOOD not DRY
|
runChecks :: (MonadReader c m, HasLogFunc c, MonadIO m) => m ()
|
||||||
runChecks :: IO ()
|
|
||||||
runChecks = checkExe "autorandr" >> checkExe "rofi"
|
runChecks = checkExe "autorandr" >> checkExe "rofi"
|
||||||
|
|
||||||
checkExe :: String -> IO ()
|
checkExe :: (MonadReader c m, HasLogFunc c, MonadIO m) => String -> m ()
|
||||||
checkExe cmd = do
|
checkExe cmd = do
|
||||||
res <- findExecutable cmd
|
res <- findExecutable cmd
|
||||||
unless (isJust res) $ do
|
unless (isJust res) $ do
|
||||||
TI.putStrLn $ T.append "Could not find executable: " $ T.pack cmd
|
logError $ displayBytesUtf8 $ encodeUtf8 $ T.append "Could not find executable: " $ T.pack cmd
|
||||||
exitWith $ ExitFailure 1
|
exitWith $ ExitFailure 1
|
||||||
|
|
||||||
newtype ARClientConf = ARClientConf [T.Text]
|
newtype ARClientConf = ARClientConf [T.Text]
|
||||||
|
@ -35,7 +33,7 @@ newtype ARClientConf = ARClientConf [T.Text]
|
||||||
instance HasRofiConf ARClientConf where
|
instance HasRofiConf ARClientConf where
|
||||||
defArgs (ARClientConf a) = a
|
defArgs (ARClientConf a) = a
|
||||||
|
|
||||||
runPrompt :: [String] -> IO ()
|
runPrompt :: MonadIO m => [String] -> m ()
|
||||||
runPrompt a = do
|
runPrompt a = do
|
||||||
let c = ARClientConf $ fmap T.pack a
|
let c = ARClientConf $ fmap T.pack a
|
||||||
staticProfs <- getAutoRandrProfiles
|
staticProfs <- getAutoRandrProfiles
|
||||||
|
@ -54,13 +52,13 @@ virtProfs :: [T.Text]
|
||||||
virtProfs = ["off", "common", "clone-largest", "horizontal", "vertical"]
|
virtProfs = ["off", "common", "clone-largest", "horizontal", "vertical"]
|
||||||
|
|
||||||
-- TODO filter profiles based on which xrandr outputs are actually connected
|
-- TODO filter profiles based on which xrandr outputs are actually connected
|
||||||
getAutoRandrProfiles :: IO [T.Text]
|
getAutoRandrProfiles :: MonadIO m => m [T.Text]
|
||||||
getAutoRandrProfiles = do
|
getAutoRandrProfiles = do
|
||||||
dir <- getAutoRandrDir
|
dir <- getAutoRandrDir
|
||||||
contents <- listDirectory dir
|
contents <- listDirectory dir
|
||||||
(fmap T.pack) <$> filterM (doesDirectoryExist . (dir </>)) contents
|
(fmap T.pack) <$> filterM (doesDirectoryExist . (dir </>)) contents
|
||||||
|
|
||||||
getAutoRandrDir :: IO FilePath
|
getAutoRandrDir :: MonadIO m => m FilePath
|
||||||
getAutoRandrDir = do
|
getAutoRandrDir = do
|
||||||
c <- getXdgDirectory XdgConfig "autorandr"
|
c <- getXdgDirectory XdgConfig "autorandr"
|
||||||
e <- doesDirectoryExist c
|
e <- doesDirectoryExist c
|
||||||
|
@ -69,6 +67,7 @@ getAutoRandrDir = do
|
||||||
appendToHome p = (</> p) <$> getHomeDirectory
|
appendToHome p = (</> p) <$> getHomeDirectory
|
||||||
|
|
||||||
selectProfile :: T.Text -> RIO ARClientConf ()
|
selectProfile :: T.Text -> RIO ARClientConf ()
|
||||||
selectProfile name = liftIO $ do
|
selectProfile name =
|
||||||
TI.putStrLn name
|
liftIO $
|
||||||
void $ spawnProcess "autorandr" ["--change", T.unpack name]
|
void $
|
||||||
|
spawnProcess "autorandr" ["--change", T.unpack name]
|
||||||
|
|
|
@ -8,46 +8,53 @@ import DBus
|
||||||
import DBus.Client
|
import DBus.Client
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import qualified Data.Text.IO as TI
|
|
||||||
import RIO
|
import RIO
|
||||||
import qualified RIO.List as L
|
import qualified RIO.List as L
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import Rofi.Command
|
import Rofi.Command
|
||||||
import System.Environment
|
import UnliftIO.Environment
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = getArgs >>= runPrompt
|
main = runSimpleApp $ getArgs >>= runPrompt
|
||||||
|
|
||||||
data RofiBTConf = RofiBTConf [T.Text] ObjectPath
|
data RofiBTConf = RofiBTConf
|
||||||
|
{ btArgs :: ![T.Text]
|
||||||
|
, btAdapter :: !ObjectPath
|
||||||
|
, btEnv :: !SimpleApp
|
||||||
|
}
|
||||||
|
|
||||||
instance HasRofiConf RofiBTConf where
|
instance HasRofiConf RofiBTConf where
|
||||||
defArgs (RofiBTConf as _) = as
|
defArgs = btArgs
|
||||||
|
|
||||||
|
instance HasLogFunc RofiBTConf where
|
||||||
|
logFuncL = lens btEnv (\x y -> x {btEnv = y}) . logFuncL
|
||||||
|
|
||||||
type BTAction = RofiAction RofiBTConf
|
type BTAction = RofiAction RofiBTConf
|
||||||
|
|
||||||
runPrompt :: [String] -> IO ()
|
runPrompt :: [String] -> RIO SimpleApp ()
|
||||||
runPrompt args = do
|
runPrompt args = do
|
||||||
c <- getClient
|
c <- getClient
|
||||||
maybe (TI.putStrLn "could not get DBus client") run c
|
maybe (logError "could not get DBus client") run c
|
||||||
where
|
where
|
||||||
run client = do
|
run client = do
|
||||||
paths <- M.keys <$> getObjectTree client
|
paths <- M.keys <$> getObjectTree client
|
||||||
maybe (TI.putStrLn "could not get DBus adapter") (actions client paths) $
|
case getAdapter paths of
|
||||||
getAdapter paths
|
Nothing -> logError "could not get DBus adapter"
|
||||||
actions client paths adapter = do
|
Just adapter -> do
|
||||||
ras <- getRofiActions client paths
|
ras <- getRofiActions client paths
|
||||||
runRofi (RofiBTConf (fmap T.pack args) adapter) $
|
mapRIO (RofiBTConf (fmap T.pack args) adapter) $
|
||||||
|
selectAction $
|
||||||
emptyMenu
|
emptyMenu
|
||||||
{ groups = [untitledGroup $ toRofiActions ras]
|
{ groups = [untitledGroup $ toRofiActions ras]
|
||||||
, prompt = Just "Select Device"
|
, prompt = Just "Select Device"
|
||||||
}
|
}
|
||||||
|
|
||||||
getRofiActions :: Client -> [ObjectPath] -> IO [BTAction]
|
getRofiActions :: MonadIO m => Client -> [ObjectPath] -> m [BTAction]
|
||||||
getRofiActions client os = do
|
getRofiActions client os = do
|
||||||
devs <- getDevices client os
|
devs <- getDevices client os
|
||||||
catMaybes <$> mapM (deviceToRofiAction client) devs
|
catMaybes <$> mapM (deviceToRofiAction client) devs
|
||||||
|
|
||||||
deviceToRofiAction :: Client -> ObjectPath -> IO (Maybe BTAction)
|
deviceToRofiAction :: MonadIO m => Client -> ObjectPath -> m (Maybe BTAction)
|
||||||
deviceToRofiAction client dev = do
|
deviceToRofiAction client dev = do
|
||||||
c <- getDeviceConnected client dev
|
c <- getDeviceConnected client dev
|
||||||
n <- getDeviceName client dev
|
n <- getDeviceName client dev
|
||||||
|
@ -64,11 +71,11 @@ deviceToRofiAction client dev = do
|
||||||
|
|
||||||
powerAdapterMaybe :: Client -> RIO RofiBTConf ()
|
powerAdapterMaybe :: Client -> RIO RofiBTConf ()
|
||||||
powerAdapterMaybe client = do
|
powerAdapterMaybe client = do
|
||||||
(RofiBTConf _ adapter) <- ask
|
adapter <- asks btAdapter
|
||||||
let mc = btMethodCall adapter i m
|
let mc = btMethodCall adapter i m
|
||||||
let powerOnMaybe = flip unless $ void $ setProperty client mc value
|
let powerOnMaybe = flip unless $ void $ liftIO $ setProperty client mc value
|
||||||
powered <- io $ getBTProperty client adapter i m
|
powered <- getBTProperty client adapter i m
|
||||||
io $ maybe (TI.putStrLn "could not get adapter powered status") powerOnMaybe powered
|
maybe (logError "could not get adapter powered status") powerOnMaybe powered
|
||||||
where
|
where
|
||||||
i = interfaceName_ "org.bluez.Adapter1"
|
i = interfaceName_ "org.bluez.Adapter1"
|
||||||
m = memberName_ "Powered"
|
m = memberName_ "Powered"
|
||||||
|
@ -85,12 +92,12 @@ formatDeviceEntry connected name = T.unwords [prefix connected, name]
|
||||||
getAdapter :: [ObjectPath] -> Maybe ObjectPath
|
getAdapter :: [ObjectPath] -> Maybe ObjectPath
|
||||||
getAdapter = L.find pathIsAdaptor
|
getAdapter = L.find pathIsAdaptor
|
||||||
|
|
||||||
getDevices :: Client -> [ObjectPath] -> IO [ObjectPath]
|
getDevices :: MonadIO m => Client -> [ObjectPath] -> m [ObjectPath]
|
||||||
getDevices client = filterM (getDevicePaired client) . filter pathIsDevice
|
getDevices client = filterM (getDevicePaired client) . filter pathIsDevice
|
||||||
|
|
||||||
type ObjectTree = M.Map ObjectPath (M.Map T.Text (M.Map T.Text Variant))
|
type ObjectTree = M.Map ObjectPath (M.Map T.Text (M.Map T.Text Variant))
|
||||||
|
|
||||||
getObjectTree :: Client -> IO ObjectTree
|
getObjectTree :: MonadIO m => Client -> m ObjectTree
|
||||||
getObjectTree client =
|
getObjectTree client =
|
||||||
fromMaybe M.empty . eitherMaybe from <$> callBTMethod client o i m
|
fromMaybe M.empty . eitherMaybe from <$> callBTMethod client o i m
|
||||||
where
|
where
|
||||||
|
@ -99,19 +106,19 @@ getObjectTree client =
|
||||||
m = memberName_ "GetManagedObjects"
|
m = memberName_ "GetManagedObjects"
|
||||||
from = fromVariant <=< listToMaybe . methodReturnBody
|
from = fromVariant <=< listToMaybe . methodReturnBody
|
||||||
|
|
||||||
getDeviceConnected :: Client -> ObjectPath -> IO (Maybe Bool)
|
getDeviceConnected :: MonadIO m => Client -> ObjectPath -> m (Maybe Bool)
|
||||||
getDeviceConnected = getDevProperty "Connected"
|
getDeviceConnected = getDevProperty "Connected"
|
||||||
|
|
||||||
getDeviceName :: Client -> ObjectPath -> IO (Maybe T.Text)
|
getDeviceName :: MonadIO m => Client -> ObjectPath -> m (Maybe T.Text)
|
||||||
getDeviceName = getDevProperty "Name"
|
getDeviceName = getDevProperty "Name"
|
||||||
|
|
||||||
getDevicePaired :: Client -> ObjectPath -> IO Bool
|
getDevicePaired :: MonadIO m => Client -> ObjectPath -> m Bool
|
||||||
getDevicePaired c = fmap (fromMaybe False) . getDevProperty "Paired" c
|
getDevicePaired c = fmap (fromMaybe False) . getDevProperty "Paired" c
|
||||||
|
|
||||||
callDeviceConnect :: Client -> ObjectPath -> IO ()
|
callDeviceConnect :: MonadIO m => Client -> ObjectPath -> m ()
|
||||||
callDeviceConnect = callDevMethod "Connect"
|
callDeviceConnect = callDevMethod "Connect"
|
||||||
|
|
||||||
callDeviceDisconnect :: Client -> ObjectPath -> IO ()
|
callDeviceDisconnect :: MonadIO m => Client -> ObjectPath -> m ()
|
||||||
callDeviceDisconnect = callDevMethod "Disconnect"
|
callDeviceDisconnect = callDevMethod "Disconnect"
|
||||||
|
|
||||||
pathIsAdaptor :: ObjectPath -> Bool
|
pathIsAdaptor :: ObjectPath -> Bool
|
||||||
|
@ -130,39 +137,42 @@ pathIsAdaptorPrefix a b c = a == "org" && b == "bluez" && "hci" `T.isPrefixOf` c
|
||||||
splitPath :: ObjectPath -> [T.Text]
|
splitPath :: ObjectPath -> [T.Text]
|
||||||
splitPath = T.split (== '/') . T.dropWhile (== '/') . T.pack . formatObjectPath
|
splitPath = T.split (== '/') . T.dropWhile (== '/') . T.pack . formatObjectPath
|
||||||
|
|
||||||
getClient :: IO (Maybe Client)
|
getClient :: (MonadReader c m, HasLogFunc c, MonadUnliftIO m) => m (Maybe Client)
|
||||||
getClient = either warn (return . Just) =<< try connectSystem
|
getClient = either warn (return . Just) =<< try (liftIO connectSystem)
|
||||||
where
|
where
|
||||||
warn e = TI.putStrLn (T.pack $ clientErrorMessage e) >> return Nothing
|
warn e = do
|
||||||
|
logWarn $ displayBytesUtf8 $ encodeUtf8 $ (T.pack $ clientErrorMessage e)
|
||||||
|
return Nothing
|
||||||
|
|
||||||
callDevMethod :: T.Text -> Client -> ObjectPath -> IO ()
|
callDevMethod :: MonadIO m => T.Text -> Client -> ObjectPath -> m ()
|
||||||
callDevMethod mem client dev =
|
callDevMethod mem client dev =
|
||||||
void $ callBTMethod client dev btDevInterface $ memberName_ $ T.unpack mem
|
void $ callBTMethod client dev btDevInterface $ memberName_ $ T.unpack mem
|
||||||
|
|
||||||
getDevProperty :: IsVariant a => T.Text -> Client -> ObjectPath -> IO (Maybe a)
|
getDevProperty :: (MonadIO m, IsVariant a) => T.Text -> Client -> ObjectPath -> m (Maybe a)
|
||||||
getDevProperty mem client dev =
|
getDevProperty mem client dev =
|
||||||
getBTProperty client dev btDevInterface $ memberName_ $ T.unpack mem
|
getBTProperty client dev btDevInterface $ memberName_ $ T.unpack mem
|
||||||
|
|
||||||
callBTMethod
|
callBTMethod
|
||||||
:: Client
|
:: MonadIO m
|
||||||
|
=> Client
|
||||||
-> ObjectPath
|
-> ObjectPath
|
||||||
-> InterfaceName
|
-> InterfaceName
|
||||||
-> MemberName
|
-> MemberName
|
||||||
-> IO (Either MethodError MethodReturn)
|
-> m (Either MethodError MethodReturn)
|
||||||
callBTMethod client o i m = call client (btMethodCall o i m)
|
callBTMethod client o i m = liftIO $ call client (btMethodCall o i m)
|
||||||
|
|
||||||
-- eitherMaybe (fromVariant <=< listToMaybe . methodReturnBody)
|
-- eitherMaybe (fromVariant <=< listToMaybe . methodReturnBody)
|
||||||
-- <$> call client (btMethodCall o i m)
|
-- <$> call client (btMethodCall o i m)
|
||||||
|
|
||||||
getBTProperty
|
getBTProperty
|
||||||
:: IsVariant a
|
:: (MonadIO m, IsVariant a)
|
||||||
=> Client
|
=> Client
|
||||||
-> ObjectPath
|
-> ObjectPath
|
||||||
-> InterfaceName
|
-> InterfaceName
|
||||||
-> MemberName
|
-> MemberName
|
||||||
-> IO (Maybe a)
|
-> m (Maybe a)
|
||||||
getBTProperty client o i m =
|
getBTProperty client o i m =
|
||||||
eitherMaybe fromVariant <$> getProperty client (btMethodCall o i m)
|
eitherMaybe fromVariant <$> (liftIO $ getProperty client (btMethodCall o i m))
|
||||||
|
|
||||||
btMethodCall :: ObjectPath -> InterfaceName -> MemberName -> MethodCall
|
btMethodCall :: ObjectPath -> InterfaceName -> MemberName -> MethodCall
|
||||||
btMethodCall o i m = (methodCall o i m) {methodCallDestination = Just btBus}
|
btMethodCall o i m = (methodCall o i m) {methodCallDestination = Just btBus}
|
||||||
|
|
|
@ -17,36 +17,37 @@
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
|
|
||||||
import Bitwarden.Internal
|
import Bitwarden.Internal
|
||||||
import qualified Data.Text.IO as TI
|
|
||||||
import RIO
|
import RIO
|
||||||
import RIO.Directory
|
import RIO.Directory
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import Rofi.Command
|
import UnliftIO.Environment
|
||||||
import System.Environment
|
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = runChecks >> getArgs >>= parse
|
main = runSimpleApp $ runChecks >> getArgs >>= parse
|
||||||
|
|
||||||
-- TODO check if daemon is running when running client
|
-- TODO check if daemon is running when running client
|
||||||
parse :: [String] -> IO ()
|
parse :: HasLogFunc c => [String] -> RIO c ()
|
||||||
parse ["-d", t] = case readMaybe t of Just t' -> runDaemon t'; _ -> usage
|
parse ["-d", t] = case readMaybe t of Just t' -> runDaemon t'; _ -> usage
|
||||||
parse ("-c" : args) = runClient $ fmap T.pack args
|
parse ("-c" : args) = runClient $ fmap T.pack args
|
||||||
parse _ = usage
|
parse _ = usage
|
||||||
|
|
||||||
usage :: IO ()
|
usage :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => m ()
|
||||||
usage =
|
usage =
|
||||||
TI.putStrLn $
|
logInfo $
|
||||||
joinNewline
|
displayBytesUtf8 $
|
||||||
|
encodeUtf8 $
|
||||||
|
T.unlines
|
||||||
[ "daemon mode: rofi-bw -d TIMEOUT"
|
[ "daemon mode: rofi-bw -d TIMEOUT"
|
||||||
, "client mode: rofi-bw -c [ROFI-ARGS]"
|
, "client mode: rofi-bw -c [ROFI-ARGS]"
|
||||||
]
|
]
|
||||||
|
|
||||||
runChecks :: IO ()
|
runChecks :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => m ()
|
||||||
runChecks = checkExe "bw" >> checkExe "rofi"
|
runChecks = checkExe "bw" >> checkExe "rofi"
|
||||||
|
|
||||||
checkExe :: String -> IO ()
|
-- TODO not DRY
|
||||||
|
checkExe :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => String -> m ()
|
||||||
checkExe cmd = do
|
checkExe cmd = do
|
||||||
res <- findExecutable cmd
|
res <- findExecutable cmd
|
||||||
unless (isJust res) $ do
|
unless (isJust res) $ do
|
||||||
TI.putStrLn $ T.append "Could not find executable: " $ T.pack cmd
|
logError $ displayBytesUtf8 $ encodeUtf8 $ T.append "Could not find executable: " $ T.pack cmd
|
||||||
exitWith $ ExitFailure 1
|
exitWith $ ExitFailure 1
|
||||||
|
|
143
app/rofi-dev.hs
143
app/rofi-dev.hs
|
@ -12,7 +12,6 @@
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
|
|
||||||
import Bitwarden.Internal
|
import Bitwarden.Internal
|
||||||
import qualified Data.Text.IO as TI
|
|
||||||
import Dhall hiding (maybe, sequence, void)
|
import Dhall hiding (maybe, sequence, void)
|
||||||
import Dhall.TH
|
import Dhall.TH
|
||||||
import RIO
|
import RIO
|
||||||
|
@ -23,10 +22,10 @@ import qualified RIO.NonEmpty as NE
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import Rofi.Command
|
import Rofi.Command
|
||||||
import System.Console.GetOpt
|
import System.Console.GetOpt
|
||||||
import System.Environment
|
|
||||||
import System.FilePath.Posix
|
import System.FilePath.Posix
|
||||||
import System.Posix.User (getEffectiveUserName)
|
import System.Posix.User (getEffectiveUserName)
|
||||||
import System.Process
|
import System.Process
|
||||||
|
import UnliftIO.Environment
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Static device configuration (dhall)
|
-- Static device configuration (dhall)
|
||||||
|
@ -51,12 +50,17 @@ makeHaskellTypesWith
|
||||||
]
|
]
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = getArgs >>= parse
|
main = runSimpleApp $ getArgs >>= parse
|
||||||
|
|
||||||
parse :: [String] -> IO ()
|
parse :: [String] -> RIO SimpleApp ()
|
||||||
parse args = case getOpt Permute options args of
|
parse args = case getOpt Permute options args of
|
||||||
(o, n, []) -> runMounts $ L.foldl (flip id) (defaultOpts (fmap T.pack n)) o
|
(o, n, []) -> runMounts $ L.foldl (flip id) (defaultOpts (fmap T.pack n)) o
|
||||||
(_, _, errs) -> TI.putStrLn $ T.pack $ concat errs ++ usageInfo h options
|
(_, _, errs) ->
|
||||||
|
logError $
|
||||||
|
displayBytesUtf8 $
|
||||||
|
encodeUtf8 $
|
||||||
|
T.pack $
|
||||||
|
concat errs ++ usageInfo h options
|
||||||
where
|
where
|
||||||
h = "Usage: rofi-dev [OPTIONS] [-- ROFI-OPTIONS]"
|
h = "Usage: rofi-dev [OPTIONS] [-- ROFI-OPTIONS]"
|
||||||
defaultOpts r =
|
defaultOpts r =
|
||||||
|
@ -101,29 +105,35 @@ data Opts = Opts
|
||||||
-- mounts grouped by device type (eg removable, sshfs, cifs, etc). I like
|
-- mounts grouped by device type (eg removable, sshfs, cifs, etc). I like
|
||||||
-- pretty things, so ensure the entries are aligned properly as well
|
-- pretty things, so ensure the entries are aligned properly as well
|
||||||
|
|
||||||
runMounts :: Opts -> IO ()
|
runMounts :: Opts -> RIO SimpleApp ()
|
||||||
runMounts opts = do
|
runMounts opts = do
|
||||||
static <- join <$> traverse parseStaticConfig (optsConfig opts)
|
static <- join <$> traverse parseStaticConfig (optsConfig opts)
|
||||||
defaultTmpPath <- ("/tmp/media" </>) <$> getEffectiveUserName
|
defaultTmpPath <- ("/tmp/media" </>) <$> liftIO getEffectiveUserName
|
||||||
let tmpPath = fromMaybe defaultTmpPath $ (fmap T.unpack . scTmpPath) =<< static
|
let tmpPath = fromMaybe defaultTmpPath $ (fmap T.unpack . scTmpPath) =<< static
|
||||||
let staticDevs = maybe M.empty (M.fromList . fmap (\(TreeMap k v) -> (k, v)) . scDevices) static
|
let staticDevs = maybe M.empty (M.fromList . fmap (\(TreeMap k v) -> (k, v)) . scDevices) static
|
||||||
let verbose = fromMaybe False $ scVerbose =<< static
|
let verbose = fromMaybe False $ scVerbose =<< static
|
||||||
let mountconf =
|
let mountconf e =
|
||||||
MountConf
|
MountConf
|
||||||
{ mountconfVolatilePath = tmpPath
|
{ mountconfVolatilePath = tmpPath
|
||||||
, mountconfRofiArgs = optsRofiArgs opts
|
, mountconfRofiArgs = optsRofiArgs opts
|
||||||
, mountconfStaticDevs = staticDevs
|
, mountconfStaticDevs = staticDevs
|
||||||
, mountconfVerbose = verbose
|
, mountconfVerbose = verbose
|
||||||
|
, mountconfEnv = e
|
||||||
}
|
}
|
||||||
let byAlias = mountByAlias $ optsUnmount opts
|
let byAlias = mountByAlias $ optsUnmount opts
|
||||||
let byPrompt = runPrompt =<< getGroups
|
let byPrompt = runPrompt =<< getGroups
|
||||||
runRIO mountconf $ maybe byPrompt byAlias $ optsAlias opts
|
mapRIO mountconf $ maybe byPrompt byAlias $ optsAlias opts
|
||||||
|
|
||||||
parseStaticConfig :: FilePath -> IO (Maybe StaticConfig)
|
parseStaticConfig
|
||||||
|
:: (HasLogFunc c, MonadReader c m, MonadUnliftIO m)
|
||||||
|
=> FilePath
|
||||||
|
-> m (Maybe StaticConfig)
|
||||||
parseStaticConfig p = do
|
parseStaticConfig p = do
|
||||||
res <- tryIO $ inputFile auto p
|
res <- tryIO $ liftIO $ inputFile auto p
|
||||||
case res of
|
case res of
|
||||||
Left e -> TI.putStrLn (T.pack $ show e) >> return Nothing
|
Left e -> do
|
||||||
|
logError $ displayBytesUtf8 $ encodeUtf8 $ T.pack $ show e
|
||||||
|
return Nothing
|
||||||
Right c -> return $ Just c
|
Right c -> return $ Just c
|
||||||
|
|
||||||
runPrompt :: HasRofiConf c => [RofiGroup c] -> RIO c ()
|
runPrompt :: HasRofiConf c => [RofiGroup c] -> RIO c ()
|
||||||
|
@ -134,7 +144,7 @@ runPrompt gs =
|
||||||
, prompt = Just "Select Device"
|
, prompt = Just "Select Device"
|
||||||
}
|
}
|
||||||
|
|
||||||
getGroups :: RofiMountIO [RofiGroup MountConf]
|
getGroups :: MIO [RofiGroup MountConf]
|
||||||
getGroups = do
|
getGroups = do
|
||||||
actions <- sequence [getStaticActions, getRemovableActions, getMTPActions]
|
actions <- sequence [getStaticActions, getRemovableActions, getMTPActions]
|
||||||
return $
|
return $
|
||||||
|
@ -147,17 +157,17 @@ getGroups = do
|
||||||
titledGroup "Meta Actions" $
|
titledGroup "Meta Actions" $
|
||||||
toRofiActions [(" Dismount All", dismountAll)]
|
toRofiActions [(" Dismount All", dismountAll)]
|
||||||
|
|
||||||
dismountAll :: RofiMountIO ()
|
dismountAll :: MIO ()
|
||||||
dismountAll = do
|
dismountAll = do
|
||||||
umount =<< asks (configToTree' . mountconfStaticDevs)
|
umount =<< asks (configToTree' . mountconfStaticDevs)
|
||||||
umount =<< getRemovableDevices
|
umount =<< getRemovableDevices
|
||||||
umount =<< getMTPDevices
|
umount =<< getMTPDevices
|
||||||
return ()
|
return ()
|
||||||
where
|
where
|
||||||
umount :: Mountable a => [a] -> RofiMountIO ()
|
umount :: Mountable a => [a] -> MIO ()
|
||||||
umount = mapM_ (`mountMaybe` True)
|
umount = mapM_ (`mountMaybe` True)
|
||||||
|
|
||||||
mountByAlias :: Bool -> T.Text -> RofiMountIO ()
|
mountByAlias :: Bool -> T.Text -> MIO ()
|
||||||
mountByAlias unmountFlag alias = do
|
mountByAlias unmountFlag alias = do
|
||||||
static <- asks mountconfStaticDevs
|
static <- asks mountconfStaticDevs
|
||||||
mapM_ (`mountMaybe` unmountFlag) $ configToTree static <$> M.lookup alias static
|
mapM_ (`mountMaybe` unmountFlag) $ configToTree static <$> M.lookup alias static
|
||||||
|
@ -170,7 +180,7 @@ mkGroup as = titledGroup h $ toRofiActions $ NE.toList $ alignEntries $ snd <$>
|
||||||
alignSep :: T.Text
|
alignSep :: T.Text
|
||||||
alignSep = " | "
|
alignSep = " | "
|
||||||
|
|
||||||
alignEntries :: NE.NonEmpty (ProtoAction) -> NE.NonEmpty (T.Text, RofiMountIO ())
|
alignEntries :: NE.NonEmpty (ProtoAction) -> NE.NonEmpty (T.Text, MIO ())
|
||||||
alignEntries ps = NE.zip (align es) as
|
alignEntries ps = NE.zip (align es) as
|
||||||
where
|
where
|
||||||
(es, as) = NE.unzip $ fmap (\(ProtoAction e a) -> (e, a)) ps
|
(es, as) = NE.unzip $ fmap (\(ProtoAction e a) -> (e, a)) ps
|
||||||
|
@ -195,6 +205,7 @@ data MountConf = MountConf
|
||||||
, mountconfRofiArgs :: [T.Text]
|
, mountconfRofiArgs :: [T.Text]
|
||||||
, mountconfStaticDevs :: M.Map T.Text TreeConfig
|
, mountconfStaticDevs :: M.Map T.Text TreeConfig
|
||||||
, mountconfVerbose :: Bool
|
, mountconfVerbose :: Bool
|
||||||
|
, mountconfEnv :: !SimpleApp
|
||||||
}
|
}
|
||||||
|
|
||||||
-- deriving (Show)
|
-- deriving (Show)
|
||||||
|
@ -202,6 +213,9 @@ data MountConf = MountConf
|
||||||
instance HasRofiConf MountConf where
|
instance HasRofiConf MountConf where
|
||||||
defArgs MountConf {mountconfRofiArgs = a} = a
|
defArgs MountConf {mountconfRofiArgs = a} = a
|
||||||
|
|
||||||
|
instance HasLogFunc MountConf where
|
||||||
|
logFuncL = lens mountconfEnv (\x y -> x {mountconfEnv = y}) . logFuncL
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Mountable typeclass
|
-- Mountable typeclass
|
||||||
--
|
--
|
||||||
|
@ -215,9 +229,9 @@ mountedState _ = False
|
||||||
|
|
||||||
class Mountable a where
|
class Mountable a where
|
||||||
-- | Mount the given type (or dismount if False is passed)
|
-- | Mount the given type (or dismount if False is passed)
|
||||||
mount :: a -> Bool -> RofiMountIO MountResult
|
mount :: a -> Bool -> MIO MountResult
|
||||||
|
|
||||||
mountMaybe :: a -> Bool -> RofiMountIO ()
|
mountMaybe :: a -> Bool -> MIO ()
|
||||||
mountMaybe dev mountFlag = do
|
mountMaybe dev mountFlag = do
|
||||||
let lab = getLabel dev
|
let lab = getLabel dev
|
||||||
mounted <- isMounted dev
|
mounted <- isMounted dev
|
||||||
|
@ -232,16 +246,16 @@ class Mountable a where
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
-- | Check if the mounting utilities are present
|
-- | Check if the mounting utilities are present
|
||||||
allInstalled :: a -> RofiMountIO Bool
|
allInstalled :: a -> MIO Bool
|
||||||
|
|
||||||
-- | Return a string representing the label of the device
|
-- | Return a string representing the label of the device
|
||||||
getLabel :: a -> T.Text
|
getLabel :: a -> T.Text
|
||||||
|
|
||||||
-- | Determine if the given type is mounted or not
|
-- | Determine if the given type is mounted or not
|
||||||
isMounted :: a -> RofiMountIO Bool
|
isMounted :: a -> MIO Bool
|
||||||
isMounted dev = mountedState <$> mountState dev
|
isMounted dev = mountedState <$> mountState dev
|
||||||
|
|
||||||
mountState :: a -> RofiMountIO MountState
|
mountState :: a -> MIO MountState
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Actionable typeclass
|
-- Actionable typeclass
|
||||||
|
@ -261,7 +275,7 @@ class Mountable a => Actionable a where
|
||||||
|
|
||||||
-- | Given a mountable type, return a rofi action (string to go in the
|
-- | Given a mountable type, return a rofi action (string to go in the
|
||||||
-- Rofi prompt and an action to perform when it is selected)
|
-- Rofi prompt and an action to perform when it is selected)
|
||||||
mkAction :: a -> RofiMountIO (Header, ProtoAction)
|
mkAction :: a -> MIO (Header, ProtoAction)
|
||||||
mkAction dev = do
|
mkAction dev = do
|
||||||
m <- mountState dev
|
m <- mountState dev
|
||||||
i <- allInstalled dev
|
i <- allInstalled dev
|
||||||
|
@ -278,11 +292,11 @@ class Mountable a => Actionable a where
|
||||||
|
|
||||||
mountableToAction
|
mountableToAction
|
||||||
:: Actionable a
|
:: Actionable a
|
||||||
=> RofiMountIO [a]
|
=> MIO [a]
|
||||||
-> RofiMountIO [(Header, ProtoAction)]
|
-> MIO [(Header, ProtoAction)]
|
||||||
mountableToAction ms = mapM mkAction =<< ms
|
mountableToAction ms = mapM mkAction =<< ms
|
||||||
|
|
||||||
type RofiMountIO a = RIO MountConf a
|
type MIO a = RIO MountConf a
|
||||||
|
|
||||||
-- headers appear in the order listed here (per Enum)
|
-- headers appear in the order listed here (per Enum)
|
||||||
data Header
|
data Header
|
||||||
|
@ -303,7 +317,7 @@ instance Show Header where
|
||||||
where
|
where
|
||||||
suffix = (++ " Devices")
|
suffix = (++ " Devices")
|
||||||
|
|
||||||
data ProtoAction = ProtoAction (NE.NonEmpty T.Text) (RofiMountIO ())
|
data ProtoAction = ProtoAction (NE.NonEmpty T.Text) (MIO ())
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Static devices trees
|
-- Static devices trees
|
||||||
|
@ -367,7 +381,6 @@ instance Mountable DeviceConfig where
|
||||||
mount DeviceConfig {deviceMount = m, deviceData = devData} False = do
|
mount DeviceConfig {deviceMount = m, deviceData = devData} False = do
|
||||||
m' <- getAbsMountpoint m
|
m' <- getAbsMountpoint m
|
||||||
withTmpMountDir m' $
|
withTmpMountDir m' $
|
||||||
io $
|
|
||||||
case devData of
|
case devData of
|
||||||
SSHFSConfig (SSHFSData {sshfsRemote = r, sshfsPassword = p}) ->
|
SSHFSConfig (SSHFSData {sshfsRemote = r, sshfsPassword = p}) ->
|
||||||
mountSSHFS m' p r
|
mountSSHFS m' p r
|
||||||
|
@ -416,19 +429,25 @@ instance Mountable DeviceConfig where
|
||||||
{ deviceMount = MountConfig {mpPath = p, mpLabel = l}
|
{ deviceMount = MountConfig {mpPath = p, mpLabel = l}
|
||||||
} = fromMaybe (T.pack $ takeFileName $ T.unpack p) l
|
} = fromMaybe (T.pack $ takeFileName $ T.unpack p) l
|
||||||
|
|
||||||
mountSSHFS :: FilePath -> Maybe PasswordConfig -> T.Text -> IO MountResult
|
mountSSHFS
|
||||||
|
:: (HasLogFunc c, MonadReader c m, MonadUnliftIO m)
|
||||||
|
=> FilePath
|
||||||
|
-> Maybe PasswordConfig
|
||||||
|
-> T.Text
|
||||||
|
-> m MountResult
|
||||||
mountSSHFS mountpoint pwdConfig remote =
|
mountSSHFS mountpoint pwdConfig remote =
|
||||||
withPasswordGetter pwdConfig (run ["-o", "password_stdin"]) $ run [] ""
|
withPasswordGetter pwdConfig (run ["-o", "password_stdin"]) $ run [] ""
|
||||||
where
|
where
|
||||||
run other = runMount "sshfs" (other ++ [remote, T.pack mountpoint])
|
run other = runMount "sshfs" (other ++ [remote, T.pack mountpoint])
|
||||||
|
|
||||||
mountCIFS
|
mountCIFS
|
||||||
:: Bool
|
:: (HasLogFunc c, MonadReader c m, MonadUnliftIO m)
|
||||||
|
=> Bool
|
||||||
-> T.Text
|
-> T.Text
|
||||||
-> FilePath
|
-> FilePath
|
||||||
-> Maybe CIFSOpts
|
-> Maybe CIFSOpts
|
||||||
-> Maybe PasswordConfig
|
-> Maybe PasswordConfig
|
||||||
-> IO MountResult
|
-> m MountResult
|
||||||
mountCIFS useSudo remote mountpoint opts pwdConfig =
|
mountCIFS useSudo remote mountpoint opts pwdConfig =
|
||||||
withPasswordGetter pwdConfig runPwd run
|
withPasswordGetter pwdConfig runPwd run
|
||||||
where
|
where
|
||||||
|
@ -448,7 +467,12 @@ fromCIFSOpts o = T.intercalate "," $ mapMaybe concatMaybe fs
|
||||||
]
|
]
|
||||||
concatMaybe (k, f) = (\v -> T.concat [k, "=", v]) <$> f o
|
concatMaybe (k, f) = (\v -> T.concat [k, "=", v]) <$> f o
|
||||||
|
|
||||||
mountVeracrypt :: FilePath -> Maybe PasswordConfig -> T.Text -> IO MountResult
|
mountVeracrypt
|
||||||
|
:: (HasLogFunc c, MonadReader c m, MonadUnliftIO m)
|
||||||
|
=> FilePath
|
||||||
|
-> Maybe PasswordConfig
|
||||||
|
-> T.Text
|
||||||
|
-> m MountResult
|
||||||
mountVeracrypt mountpoint pwdConfig volume =
|
mountVeracrypt mountpoint pwdConfig volume =
|
||||||
withPasswordGetter pwdConfig (runVeraCrypt (args ++ ["--stdin"])) $
|
withPasswordGetter pwdConfig (runVeraCrypt (args ++ ["--stdin"])) $
|
||||||
runVeraCrypt args ""
|
runVeraCrypt args ""
|
||||||
|
@ -457,12 +481,12 @@ mountVeracrypt mountpoint pwdConfig volume =
|
||||||
|
|
||||||
-- NOTE: the user is assumed to have added themselves to the sudoers file so
|
-- NOTE: the user is assumed to have added themselves to the sudoers file so
|
||||||
-- that this command will work
|
-- that this command will work
|
||||||
runVeraCrypt :: [T.Text] -> T.Text -> IO MountResult
|
runVeraCrypt :: MonadIO m => [T.Text] -> T.Text -> m MountResult
|
||||||
runVeraCrypt args = runMount "sudo" (defaultArgs ++ args)
|
runVeraCrypt args = runMount "sudo" (defaultArgs ++ args)
|
||||||
where
|
where
|
||||||
defaultArgs = ["/usr/bin/veracrypt", "--text", "--non-interactive"]
|
defaultArgs = ["/usr/bin/veracrypt", "--text", "--non-interactive"]
|
||||||
|
|
||||||
veracryptMountState :: MountConfig -> RofiMountIO MountState
|
veracryptMountState :: MountConfig -> MIO MountState
|
||||||
veracryptMountState mc = do
|
veracryptMountState mc = do
|
||||||
mp <- getAbsMountpoint mc
|
mp <- getAbsMountpoint mc
|
||||||
primary <- io $ lookupSpec mp
|
primary <- io $ lookupSpec mp
|
||||||
|
@ -479,29 +503,29 @@ veracryptMountState mc = do
|
||||||
Just (i, _) -> if i `elem` ['0' .. '9'] then Just i else Nothing
|
Just (i, _) -> if i `elem` ['0' .. '9'] then Just i else Nothing
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
getAbsMountpoint :: MountConfig -> RofiMountIO FilePath
|
getAbsMountpoint :: MountConfig -> MIO FilePath
|
||||||
getAbsMountpoint MountConfig {mpPath = m} =
|
getAbsMountpoint MountConfig {mpPath = m} =
|
||||||
asks $ flip appendRoot (T.unpack m) . mountconfVolatilePath
|
asks $ flip appendRoot (T.unpack m) . mountconfVolatilePath
|
||||||
|
|
||||||
getStaticActions :: RofiMountIO [(Header, ProtoAction)]
|
getStaticActions :: MIO [(Header, ProtoAction)]
|
||||||
getStaticActions = mountableToAction $ asks $ configToTree' . mountconfStaticDevs
|
getStaticActions = mountableToAction $ asks $ configToTree' . mountconfStaticDevs
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Password-getting functions for static devices
|
-- Password-getting functions for static devices
|
||||||
|
|
||||||
type PasswordGetter = IO (Maybe T.Text)
|
type PasswordGetter m = m (Maybe T.Text)
|
||||||
|
|
||||||
runSecret :: M.Map T.Text T.Text -> PasswordGetter
|
runSecret :: MonadUnliftIO m => M.Map T.Text T.Text -> PasswordGetter m
|
||||||
runSecret kvs = readCmdSuccess "secret-tool" ("lookup" : kvs') ""
|
runSecret kvs = readCmdSuccess "secret-tool" ("lookup" : kvs') ""
|
||||||
where
|
where
|
||||||
kvs' = concatMap (\(k, v) -> [k, v]) $ M.toList kvs
|
kvs' = concatMap (\(k, v) -> [k, v]) $ M.toList kvs
|
||||||
|
|
||||||
runBitwarden :: T.Text -> PasswordGetter
|
runBitwarden :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => T.Text -> PasswordGetter m
|
||||||
runBitwarden pname =
|
runBitwarden pname =
|
||||||
((password . login) <=< L.find (\i -> name i == pname))
|
((password . login) <=< L.find (\i -> name i == pname))
|
||||||
<$> getItems
|
<$> getItems
|
||||||
|
|
||||||
runPromptLoop :: Natural -> PasswordGetter -> PasswordGetter
|
runPromptLoop :: MonadUnliftIO m => Natural -> PasswordGetter m -> PasswordGetter m
|
||||||
runPromptLoop n pwd = do
|
runPromptLoop n pwd = do
|
||||||
res <- pwd
|
res <- pwd
|
||||||
if isNothing res
|
if isNothing res
|
||||||
|
@ -523,17 +547,18 @@ runPromptLoop n pwd = do
|
||||||
-- getPrompt = maybe (return Nothing) (flip runPromptLoop readPassword . promptTries)
|
-- getPrompt = maybe (return Nothing) (flip runPromptLoop readPassword . promptTries)
|
||||||
-- runMaybe x y = (\r -> if isNothing r then y else return r) =<< x
|
-- runMaybe x y = (\r -> if isNothing r then y else return r) =<< x
|
||||||
|
|
||||||
configToPwd :: PasswordConfig -> PasswordGetter
|
configToPwd :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => PasswordConfig -> PasswordGetter m
|
||||||
configToPwd (PwdBW (BitwardenConfig {bwKey = k, bwTries = n})) =
|
configToPwd (PwdBW (BitwardenConfig {bwKey = k, bwTries = n})) =
|
||||||
runPromptLoop n $ runBitwarden k
|
runPromptLoop n $ runBitwarden k
|
||||||
configToPwd (PwdLS s) = runSecret $ M.fromList $ fmap (\(SecretMap k v) -> (k, v)) $ secretAttributes s
|
configToPwd (PwdLS s) = runSecret $ M.fromList $ fmap (\(SecretMap k v) -> (k, v)) $ secretAttributes s
|
||||||
configToPwd (PwdPr p) = flip runPromptLoop readPassword $ promptTries p
|
configToPwd (PwdPr p) = flip runPromptLoop readPassword $ promptTries p
|
||||||
|
|
||||||
withPasswordGetter
|
withPasswordGetter
|
||||||
:: Maybe PasswordConfig
|
:: (HasLogFunc c, MonadReader c m, MonadUnliftIO m)
|
||||||
-> (T.Text -> IO MountResult)
|
=> Maybe PasswordConfig
|
||||||
-> IO MountResult
|
-> (T.Text -> m MountResult)
|
||||||
-> IO MountResult
|
-> m MountResult
|
||||||
|
-> m MountResult
|
||||||
withPasswordGetter (Just pwdConfig) runPwd _ =
|
withPasswordGetter (Just pwdConfig) runPwd _ =
|
||||||
maybe (return $ MountError "Password could not be obtained") runPwd
|
maybe (return $ MountError "Password could not be obtained") runPwd
|
||||||
=<< configToPwd pwdConfig
|
=<< configToPwd pwdConfig
|
||||||
|
@ -590,7 +615,7 @@ getRemovableDevices =
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
mk d l = Just $ Removable {removablePath = d, removableLabel = l}
|
mk d l = Just $ Removable {removablePath = d, removableLabel = l}
|
||||||
|
|
||||||
getRemovableActions :: RofiMountIO [(Header, ProtoAction)]
|
getRemovableActions :: MIO [(Header, ProtoAction)]
|
||||||
getRemovableActions = mountableToAction getRemovableDevices
|
getRemovableActions = mountableToAction getRemovableDevices
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
@ -625,7 +650,7 @@ instance Mountable MTPFS where
|
||||||
getLabel = mtpfsDescription
|
getLabel = mtpfsDescription
|
||||||
|
|
||||||
-- | Return list of all available MTP devices
|
-- | Return list of all available MTP devices
|
||||||
getMTPDevices :: RofiMountIO [MTPFS]
|
getMTPDevices :: MIO [MTPFS]
|
||||||
getMTPDevices = do
|
getMTPDevices = do
|
||||||
i <- io mtpExeInstalled
|
i <- io mtpExeInstalled
|
||||||
if i then go else return []
|
if i then go else return []
|
||||||
|
@ -656,7 +681,7 @@ getMTPDevices = do
|
||||||
| c == ' ' = Just '-'
|
| c == ' ' = Just '-'
|
||||||
| otherwise = Just c
|
| otherwise = Just c
|
||||||
|
|
||||||
getMTPActions :: RofiMountIO [(Header, ProtoAction)]
|
getMTPActions :: MIO [(Header, ProtoAction)]
|
||||||
getMTPActions = mountableToAction getMTPDevices
|
getMTPActions = mountableToAction getMTPDevices
|
||||||
|
|
||||||
mtpExeInstalled :: IO Bool
|
mtpExeInstalled :: IO Bool
|
||||||
|
@ -697,19 +722,19 @@ notify icon summary body =
|
||||||
|
|
||||||
data MountResult = MountSuccess | MountError T.Text deriving (Show, Eq)
|
data MountResult = MountSuccess | MountError T.Text deriving (Show, Eq)
|
||||||
|
|
||||||
runMount :: T.Text -> [T.Text] -> T.Text -> IO MountResult
|
runMount :: MonadIO m => T.Text -> [T.Text] -> T.Text -> m MountResult
|
||||||
runMount cmd args stdin_ = eitherToMountResult <$> readCmdEither cmd args stdin_
|
runMount cmd args stdin_ = eitherToMountResult <$> readCmdEither cmd args stdin_
|
||||||
|
|
||||||
runMount' :: T.Text -> [T.Text] -> T.Text -> [(T.Text, T.Text)] -> IO MountResult
|
runMount' :: MonadIO m => T.Text -> [T.Text] -> T.Text -> [(T.Text, T.Text)] -> m MountResult
|
||||||
runMount' cmd args stdin_ environ =
|
runMount' cmd args stdin_ environ =
|
||||||
eitherToMountResult
|
eitherToMountResult
|
||||||
<$> readCmdEither' cmd args stdin_ environ
|
<$> readCmdEither' cmd args stdin_ environ
|
||||||
|
|
||||||
runMountSudoMaybe :: Bool -> T.Text -> [T.Text] -> IO MountResult
|
runMountSudoMaybe :: MonadIO m => Bool -> T.Text -> [T.Text] -> m MountResult
|
||||||
runMountSudoMaybe useSudo cmd args =
|
runMountSudoMaybe useSudo cmd args =
|
||||||
runMountSudoMaybe' useSudo cmd args []
|
runMountSudoMaybe' useSudo cmd args []
|
||||||
|
|
||||||
runMountSudoMaybe' :: Bool -> T.Text -> [T.Text] -> [(T.Text, T.Text)] -> IO MountResult
|
runMountSudoMaybe' :: MonadIO m => Bool -> T.Text -> [T.Text] -> [(T.Text, T.Text)] -> m MountResult
|
||||||
runMountSudoMaybe' useSudo cmd args environ =
|
runMountSudoMaybe' useSudo cmd args environ =
|
||||||
maybe
|
maybe
|
||||||
(runMount' cmd args "" environ)
|
(runMount' cmd args "" environ)
|
||||||
|
@ -720,7 +745,7 @@ runMountSudoMaybe' useSudo cmd args environ =
|
||||||
-- runSudoMount :: T.Text -> T.Text -> [T.Text] -> T.Text -> IO MountResult
|
-- runSudoMount :: T.Text -> T.Text -> [T.Text] -> T.Text -> IO MountResult
|
||||||
-- runSudoMount rootpass cmd args stdin = runSudoMount' rootpass cmd args stdin []
|
-- runSudoMount rootpass cmd args stdin = runSudoMount' rootpass cmd args stdin []
|
||||||
|
|
||||||
runSudoMount' :: T.Text -> T.Text -> [T.Text] -> [(T.Text, T.Text)] -> IO MountResult
|
runSudoMount' :: MonadIO m => T.Text -> T.Text -> [T.Text] -> [(T.Text, T.Text)] -> m MountResult
|
||||||
runSudoMount' rootpass cmd args environ = runMount "sudo" args' rootpass
|
runSudoMount' rootpass cmd args environ = runMount "sudo" args' rootpass
|
||||||
where
|
where
|
||||||
args' = ["-S"] ++ environ' ++ [cmd] ++ args
|
args' = ["-S"] ++ environ' ++ [cmd] ++ args
|
||||||
|
@ -758,7 +783,7 @@ lookupSpec mountpoint = M.lookup mountpoint <$> mountMap
|
||||||
-- base path in /tmp, so all this is saying is that umounting everything will
|
-- base path in /tmp, so all this is saying is that umounting everything will
|
||||||
-- leave /tmp/media/USER without removing all the way down to /tmp)
|
-- leave /tmp/media/USER without removing all the way down to /tmp)
|
||||||
|
|
||||||
rmDirOnMountError :: FilePath -> RofiMountIO MountResult -> RofiMountIO MountResult
|
rmDirOnMountError :: FilePath -> MIO MountResult -> MIO MountResult
|
||||||
rmDirOnMountError d f = do
|
rmDirOnMountError d f = do
|
||||||
res <- f
|
res <- f
|
||||||
unless (res == MountSuccess) $ rmDirMaybe d
|
unless (res == MountSuccess) $ rmDirMaybe d
|
||||||
|
@ -766,22 +791,22 @@ rmDirOnMountError d f = do
|
||||||
|
|
||||||
-- | Run a mount command and create the mountpoint if it does not exist, and
|
-- | Run a mount command and create the mountpoint if it does not exist, and
|
||||||
-- remove the mountpoint if a mount error occurs
|
-- remove the mountpoint if a mount error occurs
|
||||||
withTmpMountDir :: FilePath -> RofiMountIO MountResult -> RofiMountIO MountResult
|
withTmpMountDir :: FilePath -> MIO MountResult -> MIO MountResult
|
||||||
withTmpMountDir m =
|
withTmpMountDir m =
|
||||||
rmDirOnMountError m
|
rmDirOnMountError m
|
||||||
. bracketOnError_ (mkDirMaybe m) (rmDirMaybe m)
|
. bracketOnError_ (mkDirMaybe m) (rmDirMaybe m)
|
||||||
|
|
||||||
-- | Run an unmount command and remove the mountpoint if no errors occur
|
-- | Run an unmount command and remove the mountpoint if no errors occur
|
||||||
runAndRemoveDir :: FilePath -> RofiMountIO MountResult -> RofiMountIO MountResult
|
runAndRemoveDir :: FilePath -> MIO MountResult -> MIO MountResult
|
||||||
runAndRemoveDir m f = do
|
runAndRemoveDir m f = do
|
||||||
res <- catch f (return . MountError . (T.pack . displayException :: SomeException -> T.Text))
|
res <- catch f (return . MountError . (T.pack . displayException :: SomeException -> T.Text))
|
||||||
when (res == MountSuccess) $ rmDirMaybe m
|
when (res == MountSuccess) $ rmDirMaybe m
|
||||||
return res
|
return res
|
||||||
|
|
||||||
mkDirMaybe :: FilePath -> RofiMountIO ()
|
mkDirMaybe :: FilePath -> MIO ()
|
||||||
mkDirMaybe fp = whenInMountDir fp $ io $ createDirectoryIfMissing True fp
|
mkDirMaybe fp = whenInMountDir fp $ io $ createDirectoryIfMissing True fp
|
||||||
|
|
||||||
rmDirMaybe :: FilePath -> RofiMountIO ()
|
rmDirMaybe :: FilePath -> MIO ()
|
||||||
rmDirMaybe fp =
|
rmDirMaybe fp =
|
||||||
whenInMountDir fp $
|
whenInMountDir fp $
|
||||||
unlessMountpoint fp $
|
unlessMountpoint fp $
|
||||||
|
@ -791,7 +816,7 @@ rmDirMaybe fp =
|
||||||
removePathForcibly cur
|
removePathForcibly cur
|
||||||
rmUntil (takeDirectory cur) target
|
rmUntil (takeDirectory cur) target
|
||||||
|
|
||||||
whenInMountDir :: FilePath -> RofiMountIO () -> RofiMountIO ()
|
whenInMountDir :: FilePath -> MIO () -> MIO ()
|
||||||
whenInMountDir fp f = do
|
whenInMountDir fp f = do
|
||||||
mDir <- asks mountconfVolatilePath
|
mDir <- asks mountconfVolatilePath
|
||||||
when (mDir `L.isPrefixOf` fp) f
|
when (mDir `L.isPrefixOf` fp) f
|
||||||
|
|
|
@ -4,17 +4,16 @@
|
||||||
|
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
|
|
||||||
import Data.Maybe
|
|
||||||
import RIO
|
import RIO
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import Rofi.Command
|
import Rofi.Command
|
||||||
import System.Environment
|
|
||||||
import System.Process
|
import System.Process
|
||||||
|
import UnliftIO.Environment
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = getArgs >>= runPrompt
|
main = runSimpleApp $ getArgs >>= runPrompt
|
||||||
|
|
||||||
runPrompt :: [String] -> IO ()
|
runPrompt :: [String] -> RIO SimpleApp ()
|
||||||
runPrompt args = do
|
runPrompt args = do
|
||||||
servers <- getServers
|
servers <- getServers
|
||||||
maybe (return ()) run servers
|
maybe (return ()) run servers
|
||||||
|
@ -42,19 +41,19 @@ type VPNServer = (T.Text, T.Text)
|
||||||
|
|
||||||
data VPNStatus = VPNStatus (Maybe T.Text) [VPNServer] deriving (Show)
|
data VPNStatus = VPNStatus (Maybe T.Text) [VPNServer] deriving (Show)
|
||||||
|
|
||||||
getServers :: IO (Maybe VPNStatus)
|
getServers :: MonadIO m => m (Maybe VPNStatus)
|
||||||
getServers = do
|
getServers = do
|
||||||
running <- daemonIsRunning
|
running <- daemonIsRunning
|
||||||
if running
|
if running
|
||||||
then Just <$> getStatus
|
then Just <$> getStatus
|
||||||
else notify IconError "ExpressVPN daemon not running" >> return Nothing
|
else notify IconError "ExpressVPN daemon not running" >> return Nothing
|
||||||
|
|
||||||
getStatus :: IO VPNStatus
|
getStatus :: MonadIO m => m VPNStatus
|
||||||
getStatus = do
|
getStatus = do
|
||||||
connected <- getConnectedServer
|
connected <- getConnectedServer
|
||||||
VPNStatus connected <$> getAvailableServers
|
VPNStatus connected <$> getAvailableServers
|
||||||
|
|
||||||
getConnectedServer :: IO (Maybe T.Text)
|
getConnectedServer :: MonadIO m => m (Maybe T.Text)
|
||||||
getConnectedServer = (procStatus =<<) <$> readCmdSuccess eVPN ["status"] ""
|
getConnectedServer = (procStatus =<<) <$> readCmdSuccess eVPN ["status"] ""
|
||||||
where
|
where
|
||||||
procStatus = listToMaybe . mapMaybe procLine . T.lines
|
procStatus = listToMaybe . mapMaybe procLine . T.lines
|
||||||
|
@ -63,7 +62,7 @@ getConnectedServer = (procStatus =<<) <$> readCmdSuccess eVPN ["status"] ""
|
||||||
("\ESC[1;32;49mConnected" : "to" : server) -> Just $ T.unwords server
|
("\ESC[1;32;49mConnected" : "to" : server) -> Just $ T.unwords server
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
getAvailableServers :: IO [VPNServer]
|
getAvailableServers :: MonadIO m => m [VPNServer]
|
||||||
getAvailableServers = procOut =<< readCmdSuccess eVPN ["ls"] ""
|
getAvailableServers = procOut =<< readCmdSuccess eVPN ["ls"] ""
|
||||||
where
|
where
|
||||||
procOut Nothing = do
|
procOut Nothing = do
|
||||||
|
@ -91,7 +90,7 @@ getAvailableServers = procOut =<< readCmdSuccess eVPN ["ls"] ""
|
||||||
matchLine [i, _, _, _, l] = Just (i, l)
|
matchLine [i, _, _, _, l] = Just (i, l)
|
||||||
matchLine _ = Nothing
|
matchLine _ = Nothing
|
||||||
|
|
||||||
daemonIsRunning :: IO Bool
|
daemonIsRunning :: MonadIO m => m Bool
|
||||||
daemonIsRunning = isJust <$> readCmdSuccess "pgrep" [eVPND] ""
|
daemonIsRunning = isJust <$> readCmdSuccess "pgrep" [eVPND] ""
|
||||||
|
|
||||||
getDisconnectAction :: T.Text -> VPNAction
|
getDisconnectAction :: T.Text -> VPNAction
|
||||||
|
@ -119,7 +118,7 @@ eVPN = "expressvpn"
|
||||||
eVPND :: T.Text
|
eVPND :: T.Text
|
||||||
eVPND = "expressvpnd"
|
eVPND = "expressvpnd"
|
||||||
|
|
||||||
connect :: VPNServer -> IO ()
|
connect :: MonadIO m => VPNServer -> m ()
|
||||||
connect (sid, sname) = do
|
connect (sid, sname) = do
|
||||||
res <- readCmdSuccess' eVPN ["connect", sid]
|
res <- readCmdSuccess' eVPN ["connect", sid]
|
||||||
notifyIf
|
notifyIf
|
||||||
|
@ -127,7 +126,7 @@ connect (sid, sname) = do
|
||||||
(T.append "connected to " sname)
|
(T.append "connected to " sname)
|
||||||
(T.append "failed to connect to " sname)
|
(T.append "failed to connect to " sname)
|
||||||
|
|
||||||
disconnect :: T.Text -> IO Bool
|
disconnect :: MonadIO m => T.Text -> m Bool
|
||||||
disconnect server = do
|
disconnect server = do
|
||||||
res <- readCmdSuccess' eVPN ["disconnect"]
|
res <- readCmdSuccess' eVPN ["disconnect"]
|
||||||
notifyIf
|
notifyIf
|
||||||
|
@ -136,7 +135,7 @@ disconnect server = do
|
||||||
(T.append "failed to disconnect from " server)
|
(T.append "failed to disconnect from " server)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
readCmdSuccess' :: T.Text -> [T.Text] -> IO Bool
|
readCmdSuccess' :: MonadIO m => T.Text -> [T.Text] -> m Bool
|
||||||
readCmdSuccess' cmd args = isJust <$> readCmdSuccess cmd args ""
|
readCmdSuccess' cmd args = isJust <$> readCmdSuccess cmd args ""
|
||||||
|
|
||||||
-- TODO not DRY
|
-- TODO not DRY
|
||||||
|
@ -146,12 +145,12 @@ instance Show NotifyIcon where
|
||||||
show IconError = "dialog-error-symbolic"
|
show IconError = "dialog-error-symbolic"
|
||||||
show IconInfo = "dialog-information-symbolic"
|
show IconInfo = "dialog-information-symbolic"
|
||||||
|
|
||||||
notifyIf :: Bool -> T.Text -> T.Text -> IO ()
|
notifyIf :: MonadIO m => Bool -> T.Text -> T.Text -> m ()
|
||||||
notifyIf True s _ = notify IconInfo s
|
notifyIf True s _ = notify IconInfo s
|
||||||
notifyIf False _ s = notify IconError s
|
notifyIf False _ s = notify IconError s
|
||||||
|
|
||||||
notify :: NotifyIcon -> T.Text -> IO ()
|
notify :: MonadIO m => NotifyIcon -> T.Text -> m ()
|
||||||
notify icon body = void $ spawnProcess "notify-send" $ args ++ [T.unpack body]
|
notify icon body = liftIO $ void $ spawnProcess "notify-send" $ args ++ [T.unpack body]
|
||||||
where
|
where
|
||||||
args = ["-i", show icon, summary]
|
args = ["-i", show icon, summary]
|
||||||
summary = "ExpressVPN"
|
summary = "ExpressVPN"
|
||||||
|
|
|
@ -12,7 +12,6 @@ where
|
||||||
import DBus
|
import DBus
|
||||||
import DBus.Client
|
import DBus.Client
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import qualified Data.Text.IO as TI
|
|
||||||
import Data.UnixTime
|
import Data.UnixTime
|
||||||
import GHC.Generics
|
import GHC.Generics
|
||||||
import RIO hiding (timeout)
|
import RIO hiding (timeout)
|
||||||
|
@ -37,23 +36,23 @@ newtype BWServerConf = BWServerConf
|
||||||
|
|
||||||
-- TODO add a cache so the browse list will load faster
|
-- TODO add a cache so the browse list will load faster
|
||||||
data CurrentSession = CurrentSession
|
data CurrentSession = CurrentSession
|
||||||
{ timestamp :: UnixTime
|
{ timestamp :: !UnixTime
|
||||||
, hash :: T.Text
|
, hash :: !T.Text
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session = MVar (Maybe CurrentSession)
|
type Session = MVar (Maybe CurrentSession)
|
||||||
|
|
||||||
runDaemon :: Int -> IO ()
|
runDaemon :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => Int -> m ()
|
||||||
runDaemon t = do
|
runDaemon t = do
|
||||||
ses <- newMVar Nothing
|
ses <- newMVar Nothing
|
||||||
let c = BWServerConf {timeout = UnixDiffTime (fromIntegral t) 0}
|
let c = BWServerConf {timeout = UnixDiffTime (fromIntegral t) 0}
|
||||||
startService c ses
|
startService c ses
|
||||||
forever $ threadDelay 1000000
|
forever $ threadDelay 1000000
|
||||||
|
|
||||||
lockSession :: Session -> IO ()
|
lockSession :: MonadIO m => Session -> m ()
|
||||||
lockSession ses = void $ swapMVar ses Nothing
|
lockSession ses = void $ swapMVar ses Nothing
|
||||||
|
|
||||||
syncSession :: BWServerConf -> Session -> IO ()
|
syncSession :: MonadUnliftIO m => BWServerConf -> Session -> m ()
|
||||||
syncSession conf ses = notify =<< fmap join . mapM cmd =<< getSession' conf ses
|
syncSession conf ses = notify =<< fmap join . mapM cmd =<< getSession' conf ses
|
||||||
where
|
where
|
||||||
cmd h = readCmdSuccess "bw" ["sync", "--session", h] ""
|
cmd h = readCmdSuccess "bw" ["sync", "--session", h] ""
|
||||||
|
@ -61,9 +60,9 @@ syncSession conf ses = notify =<< fmap join . mapM cmd =<< getSession' conf ses
|
||||||
let j = isJust res
|
let j = isJust res
|
||||||
in notifyStatus j $ if j then "sync succeeded" else "sync failed"
|
in notifyStatus j $ if j then "sync succeeded" else "sync failed"
|
||||||
|
|
||||||
getSession' :: BWServerConf -> Session -> IO (Maybe T.Text)
|
getSession' :: MonadUnliftIO m => BWServerConf -> Session -> m (Maybe T.Text)
|
||||||
getSession' BWServerConf {timeout = t} ses = do
|
getSession' BWServerConf {timeout = t} ses = do
|
||||||
ut <- getUnixTime
|
ut <- liftIO $ getUnixTime
|
||||||
modifyMVar ses $ \s -> case s of
|
modifyMVar ses $ \s -> case s of
|
||||||
Just CurrentSession {timestamp = ts, hash = h} ->
|
Just CurrentSession {timestamp = ts, hash = h} ->
|
||||||
if diffUnixTime ut ts > t then getNewSession else return (s, Just h)
|
if diffUnixTime ut ts > t then getNewSession else return (s, Just h)
|
||||||
|
@ -74,18 +73,18 @@ getSession' BWServerConf {timeout = t} ses = do
|
||||||
newHash <- join <$> mapM readSession pwd
|
newHash <- join <$> mapM readSession pwd
|
||||||
(,newHash) <$> mapM newSession newHash
|
(,newHash) <$> mapM newSession newHash
|
||||||
newSession h = do
|
newSession h = do
|
||||||
ut <- getUnixTime
|
ut <- liftIO $ getUnixTime
|
||||||
return CurrentSession {timestamp = ut, hash = h}
|
return CurrentSession {timestamp = ut, hash = h}
|
||||||
|
|
||||||
getSession :: BWServerConf -> Session -> IO T.Text
|
getSession :: MonadUnliftIO m => BWServerConf -> Session -> m T.Text
|
||||||
getSession conf ses = fromMaybe "" <$> getSession' conf ses
|
getSession conf ses = fromMaybe "" <$> getSession' conf ses
|
||||||
|
|
||||||
readSession :: T.Text -> IO (Maybe T.Text)
|
readSession :: MonadIO m => T.Text -> m (Maybe T.Text)
|
||||||
readSession pwd = readCmdSuccess "bw" ["unlock", pwd, "--raw"] ""
|
readSession pwd = readCmdSuccess "bw" ["unlock", pwd, "--raw"] ""
|
||||||
|
|
||||||
notifyStatus :: Bool -> T.Text -> IO ()
|
notifyStatus :: MonadIO m => Bool -> T.Text -> m ()
|
||||||
notifyStatus succeeded msg =
|
notifyStatus succeeded msg =
|
||||||
void $ spawnProcess "notify-send" ["-i", i, T.unpack msg]
|
void $ liftIO $ spawnProcess "notify-send" ["-i", i, T.unpack msg]
|
||||||
where
|
where
|
||||||
i =
|
i =
|
||||||
if succeeded
|
if succeeded
|
||||||
|
@ -108,15 +107,21 @@ notifyStatus succeeded msg =
|
||||||
-- - username (if applicable) -> copy to clipboard
|
-- - username (if applicable) -> copy to clipboard
|
||||||
-- - password (if applicable) -> copy to clipboard
|
-- - password (if applicable) -> copy to clipboard
|
||||||
-- - anything else (notes and such) -> copy to clipboard
|
-- - anything else (notes and such) -> copy to clipboard
|
||||||
newtype BWClientConf = BWClientConf [T.Text]
|
data BWClientConf c = BWClientConf
|
||||||
|
{ bwArgs :: ![T.Text]
|
||||||
|
, bwEnv :: !c
|
||||||
|
}
|
||||||
|
|
||||||
instance HasRofiConf BWClientConf where
|
instance HasRofiConf (BWClientConf c) where
|
||||||
defArgs (BWClientConf a) = a
|
defArgs = bwArgs
|
||||||
|
|
||||||
runClient :: [T.Text] -> IO ()
|
instance HasLogFunc c => HasLogFunc (BWClientConf c) where
|
||||||
runClient a = do
|
logFuncL = lens bwEnv (\x y -> x {bwEnv = y}) . logFuncL
|
||||||
let c = BWClientConf a
|
|
||||||
runRofi c $
|
runClient :: HasLogFunc c => [T.Text] -> RIO c ()
|
||||||
|
runClient a =
|
||||||
|
mapRIO (BWClientConf a) $
|
||||||
|
selectAction $
|
||||||
emptyMenu
|
emptyMenu
|
||||||
{ groups = [untitledGroup $ toRofiActions ras]
|
{ groups = [untitledGroup $ toRofiActions ras]
|
||||||
, prompt = Just "Action"
|
, prompt = Just "Action"
|
||||||
|
@ -124,19 +129,19 @@ runClient a = do
|
||||||
where
|
where
|
||||||
ras =
|
ras =
|
||||||
[ ("Browse Logins", browseLogins)
|
[ ("Browse Logins", browseLogins)
|
||||||
, ("Sync Session", io callSyncSession)
|
, ("Sync Session", callSyncSession)
|
||||||
, ("Lock Session", io callLockSession)
|
, ("Lock Session", callLockSession)
|
||||||
]
|
]
|
||||||
|
|
||||||
browseLogins :: HasRofiConf c => RIO c ()
|
browseLogins :: (HasLogFunc c, HasRofiConf c) => RIO c ()
|
||||||
browseLogins = io getItems >>= selectItem
|
browseLogins = getItems >>= selectItem
|
||||||
|
|
||||||
getItems :: IO [Item]
|
getItems :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => m [Item]
|
||||||
getItems = maybe (return []) getItems' =<< callGetSession
|
getItems = maybe (return []) getItems' =<< callGetSession
|
||||||
|
|
||||||
getItems' :: T.Text -> IO [Item]
|
getItems' :: MonadIO m => T.Text -> m [Item]
|
||||||
getItems' session = do
|
getItems' session = do
|
||||||
items <- io $ readProcess "bw" ["list", "items", "--session", T.unpack session] ""
|
items <- liftIO $ readProcess "bw" ["list", "items", "--session", T.unpack session] ""
|
||||||
return $ filter notEmpty $ fromMaybe [] $ decode $ fromString items
|
return $ filter notEmpty $ fromMaybe [] $ decode $ fromString items
|
||||||
where
|
where
|
||||||
notEmpty Item {login = Login {username = Nothing, password = Nothing}} =
|
notEmpty Item {login = Login {username = Nothing, password = Nothing}} =
|
||||||
|
@ -166,7 +171,7 @@ instance FromJSON Login
|
||||||
|
|
||||||
-- TODO make menu buttons here to go back and to copy without leaving
|
-- TODO make menu buttons here to go back and to copy without leaving
|
||||||
-- the current menu
|
-- the current menu
|
||||||
selectItem :: HasRofiConf c => [Item] -> RIO c ()
|
selectItem :: (HasLogFunc c, HasRofiConf c) => [Item] -> RIO c ()
|
||||||
selectItem items =
|
selectItem items =
|
||||||
selectAction $
|
selectAction $
|
||||||
emptyMenu
|
emptyMenu
|
||||||
|
@ -174,10 +179,10 @@ selectItem items =
|
||||||
, prompt = Just "Login"
|
, prompt = Just "Login"
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsToRofiActions :: HasRofiConf c => [Item] -> RofiActions c
|
itemsToRofiActions :: (HasLogFunc c, HasRofiConf c) => [Item] -> RofiActions c
|
||||||
itemsToRofiActions = toRofiActions . fmap (\i -> (name i, selectCopy $ login i))
|
itemsToRofiActions = toRofiActions . fmap (\i -> (name i, selectCopy $ login i))
|
||||||
|
|
||||||
selectCopy :: HasRofiConf c => Login -> RIO c ()
|
selectCopy :: (HasLogFunc c, HasRofiConf c) => Login -> RIO c ()
|
||||||
selectCopy l =
|
selectCopy l =
|
||||||
selectAction $
|
selectAction $
|
||||||
emptyMenu
|
emptyMenu
|
||||||
|
@ -213,17 +218,18 @@ loginToRofiActions Login {username = u, password = p} a =
|
||||||
user = copyIfJust fmtUsername u
|
user = copyIfJust fmtUsername u
|
||||||
pwd = copyIfJust fmtPassword p
|
pwd = copyIfJust fmtPassword p
|
||||||
|
|
||||||
getItemPassword' :: BWServerConf -> Session -> T.Text -> IO (Maybe T.Text)
|
getItemPassword' :: MonadUnliftIO m => BWServerConf -> Session -> T.Text -> m (Maybe T.Text)
|
||||||
getItemPassword' conf session item = mapM getPwd =<< getSession' conf session
|
getItemPassword' conf session item = mapM getPwd =<< getSession' conf session
|
||||||
where
|
where
|
||||||
getPwd s =
|
getPwd = fmap T.pack . pr
|
||||||
T.pack
|
pr s =
|
||||||
<$> readProcess
|
liftIO $
|
||||||
|
readProcess
|
||||||
"bw"
|
"bw"
|
||||||
["get", "password", T.unpack item, "--session", T.unpack s]
|
["get", "password", T.unpack item, "--session", T.unpack s]
|
||||||
""
|
""
|
||||||
|
|
||||||
getItemPassword :: BWServerConf -> Session -> T.Text -> IO T.Text
|
getItemPassword :: MonadUnliftIO m => BWServerConf -> Session -> T.Text -> m T.Text
|
||||||
getItemPassword conf session item =
|
getItemPassword conf session item =
|
||||||
fromMaybe ""
|
fromMaybe ""
|
||||||
<$> getItemPassword' conf session item
|
<$> getItemPassword' conf session item
|
||||||
|
@ -231,22 +237,23 @@ getItemPassword conf session item =
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- | DBus
|
-- | DBus
|
||||||
startService :: BWServerConf -> Session -> IO ()
|
startService :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => BWServerConf -> Session -> m ()
|
||||||
startService c ses = do
|
startService c ses = do
|
||||||
client <- connectSession
|
client <- liftIO $ connectSession
|
||||||
let flags = [nameAllowReplacement, nameReplaceExisting]
|
let flags = [nameAllowReplacement, nameReplaceExisting]
|
||||||
_ <- requestName client busname flags
|
_ <- liftIO $ requestName client busname flags
|
||||||
TI.putStrLn "Started rofi bitwarden dbus client"
|
logInfo "Started rofi bitwarden dbus client"
|
||||||
|
withRunInIO $ \runIO ->
|
||||||
export
|
export
|
||||||
client
|
client
|
||||||
path
|
path
|
||||||
defaultInterface
|
defaultInterface
|
||||||
{ interfaceName = interface
|
{ interfaceName = interface
|
||||||
, interfaceMethods =
|
, interfaceMethods =
|
||||||
[ autoMethod memGetSession $ getSession c ses
|
[ autoMethod memGetSession $ runIO $ getSession c ses
|
||||||
, autoMethod memLockSession $ lockSession ses
|
, autoMethod memLockSession $ runIO $ lockSession ses
|
||||||
, autoMethod memSyncSession $ syncSession c ses
|
, autoMethod memSyncSession $ runIO $ syncSession c ses
|
||||||
, autoMethod memGetPassword $ getItemPassword c ses
|
, autoMethod memGetPassword $ runIO . getItemPassword c ses
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,20 +278,25 @@ memSyncSession = "SyncSession"
|
||||||
memGetPassword :: MemberName
|
memGetPassword :: MemberName
|
||||||
memGetPassword = "GetPassword"
|
memGetPassword = "GetPassword"
|
||||||
|
|
||||||
callMember :: MemberName -> IO [Variant]
|
callMember :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => MemberName -> m [Variant]
|
||||||
callMember m = do
|
callMember m = do
|
||||||
reply <- callMethod $ methodCall path interface m
|
reply <- callMethod $ methodCall path interface m
|
||||||
case reply of
|
case reply of
|
||||||
Left err -> TI.putStrLn (T.pack (methodErrorMessage err)) >> return []
|
Left err -> do
|
||||||
|
logError $
|
||||||
|
displayBytesUtf8 $
|
||||||
|
encodeUtf8 $
|
||||||
|
(T.pack (methodErrorMessage err))
|
||||||
|
return []
|
||||||
Right body -> return body
|
Right body -> return body
|
||||||
|
|
||||||
callLockSession :: IO ()
|
callLockSession :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => m ()
|
||||||
callLockSession = void $ callMember memLockSession
|
callLockSession = void $ callMember memLockSession
|
||||||
|
|
||||||
callSyncSession :: IO ()
|
callSyncSession :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => m ()
|
||||||
callSyncSession = void $ callMember memSyncSession
|
callSyncSession = void $ callMember memSyncSession
|
||||||
|
|
||||||
callGetSession :: IO (Maybe T.Text)
|
callGetSession :: (HasLogFunc c, MonadReader c m, MonadUnliftIO m) => m (Maybe T.Text)
|
||||||
callGetSession = getBodyString <$> callMember memGetSession
|
callGetSession = getBodyString <$> callMember memGetSession
|
||||||
|
|
||||||
-- TODO maybe will need to add a caller for getItemPassword
|
-- TODO maybe will need to add a caller for getItemPassword
|
||||||
|
@ -295,8 +307,8 @@ getBodyString [b] = case fromVariant b :: Maybe T.Text of
|
||||||
s -> s
|
s -> s
|
||||||
getBodyString _ = Nothing
|
getBodyString _ = Nothing
|
||||||
|
|
||||||
callMethod :: MethodCall -> IO (Either MethodError [Variant])
|
callMethod :: MonadIO m => MethodCall -> m (Either MethodError [Variant])
|
||||||
callMethod mc = do
|
callMethod mc = liftIO $ do
|
||||||
client <- connectSession
|
client <- connectSession
|
||||||
reply <- call client mc {methodCallDestination = Just busname}
|
reply <- call client mc {methodCallDestination = Just busname}
|
||||||
disconnect client
|
disconnect client
|
||||||
|
|
|
@ -149,27 +149,29 @@ readRofi uargs input = do
|
||||||
dargs <- asks defArgs
|
dargs <- asks defArgs
|
||||||
io $ readCmdEither "rofi" (dmenuArgs ++ dargs ++ uargs) input
|
io $ readCmdEither "rofi" (dmenuArgs ++ dargs ++ uargs) input
|
||||||
|
|
||||||
readCmdSuccess :: T.Text -> [T.Text] -> T.Text -> IO (Maybe T.Text)
|
readCmdSuccess :: MonadIO m => T.Text -> [T.Text] -> T.Text -> m (Maybe T.Text)
|
||||||
readCmdSuccess cmd args input =
|
readCmdSuccess cmd args input =
|
||||||
either (const Nothing) Just
|
either (const Nothing) Just
|
||||||
<$> readCmdEither cmd args input
|
<$> readCmdEither cmd args input
|
||||||
|
|
||||||
readCmdEither
|
readCmdEither
|
||||||
:: T.Text
|
:: MonadIO m
|
||||||
|
=> T.Text
|
||||||
-> [T.Text]
|
-> [T.Text]
|
||||||
-> T.Text
|
-> T.Text
|
||||||
-> IO (Either (Int, T.Text, T.Text) T.Text)
|
-> m (Either (Int, T.Text, T.Text) T.Text)
|
||||||
readCmdEither cmd args input = readCmdEither' cmd args input []
|
readCmdEither cmd args input = readCmdEither' cmd args input []
|
||||||
|
|
||||||
readCmdEither'
|
readCmdEither'
|
||||||
:: T.Text
|
:: MonadIO m
|
||||||
|
=> T.Text
|
||||||
-> [T.Text]
|
-> [T.Text]
|
||||||
-> T.Text
|
-> T.Text
|
||||||
-> [(T.Text, T.Text)]
|
-> [(T.Text, T.Text)]
|
||||||
-> IO (Either (Int, T.Text, T.Text) T.Text)
|
-> m (Either (Int, T.Text, T.Text) T.Text)
|
||||||
readCmdEither' cmd args input environ =
|
readCmdEither' cmd args input environ =
|
||||||
resultToEither
|
resultToEither
|
||||||
<$> readCreateProcessWithExitCode p (T.unpack input)
|
<$> (liftIO $ readCreateProcessWithExitCode p (T.unpack input))
|
||||||
where
|
where
|
||||||
e = case environ of
|
e = case environ of
|
||||||
[] -> Nothing
|
[] -> Nothing
|
||||||
|
@ -187,10 +189,10 @@ resultToEither (ExitFailure n, out, err) =
|
||||||
joinNewline :: [T.Text] -> T.Text
|
joinNewline :: [T.Text] -> T.Text
|
||||||
joinNewline = T.intercalate "\n"
|
joinNewline = T.intercalate "\n"
|
||||||
|
|
||||||
readPassword :: IO (Maybe T.Text)
|
readPassword :: MonadIO m => m (Maybe T.Text)
|
||||||
readPassword = readPassword' "Password"
|
readPassword = readPassword' "Password"
|
||||||
|
|
||||||
readPassword' :: T.Text -> IO (Maybe T.Text)
|
readPassword' :: MonadIO m => T.Text -> m (Maybe T.Text)
|
||||||
readPassword' p = readCmdSuccess "rofi" args ""
|
readPassword' p = readCmdSuccess "rofi" args ""
|
||||||
where
|
where
|
||||||
args = dmenuArgs ++ ["-p", p, "-password"]
|
args = dmenuArgs ++ ["-p", p, "-password"]
|
||||||
|
|
Loading…
Reference in New Issue