ADD dismount all option
This commit is contained in:
parent
7094dac44e
commit
cfe0607e2e
330
app/rofi-dev.hs
330
app/rofi-dev.hs
|
@ -5,7 +5,7 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | rofi-dev - a rofi prompt for mountable devices
|
||||
-- rofi-dev - a rofi prompt for mountable devices
|
||||
--
|
||||
-- Like all "mount helpers" this is basically a wrapper for low-level utilities
|
||||
-- the mount things from the command line. It also creates/destroys mountpoint
|
||||
|
@ -14,11 +14,9 @@
|
|||
module Main (main) where
|
||||
|
||||
import Bitwarden.Internal
|
||||
|
||||
import Control.Lens
|
||||
import Control.Monad
|
||||
import Control.Monad.Reader
|
||||
|
||||
import Data.List
|
||||
import Data.List.Split (splitOn)
|
||||
import qualified Data.Map as M
|
||||
|
@ -26,21 +24,16 @@ import Data.Maybe
|
|||
import qualified Data.Text as T
|
||||
import Data.Typeable
|
||||
import qualified Data.Vector as V
|
||||
|
||||
import Dhall hiding (maybe, sequence, void)
|
||||
import qualified Dhall.Map as DM
|
||||
|
||||
import Rofi.Command
|
||||
|
||||
import Text.Printf
|
||||
|
||||
import System.Console.GetOpt
|
||||
import System.Directory
|
||||
import System.Environment
|
||||
import System.FilePath.Posix
|
||||
import System.Posix.User (getEffectiveUserName)
|
||||
import System.Process
|
||||
|
||||
import Text.Printf
|
||||
import UnliftIO.Exception
|
||||
|
||||
main :: IO ()
|
||||
|
@ -52,7 +45,8 @@ parse args = case getOpt Permute options args of
|
|||
(_, _, errs) -> ioError $ userError $ concat errs ++ usageInfo h options
|
||||
where
|
||||
h = "Usage: rofi-dev [OPTIONS] [-- ROFI-OPTIONS]"
|
||||
defaultOpts r = Opts
|
||||
defaultOpts r =
|
||||
Opts
|
||||
{ optsConfig = Nothing
|
||||
, optsAlias = Nothing
|
||||
, optsUnmount = False
|
||||
|
@ -61,13 +55,20 @@ parse args = case getOpt Permute options args of
|
|||
|
||||
options :: [OptDescr (Opts -> Opts)]
|
||||
options =
|
||||
[ Option ['c'] ["config"]
|
||||
(ReqArg (\s m -> m { optsConfig = Just s } ) "CONF")
|
||||
[ Option
|
||||
['c']
|
||||
["config"]
|
||||
(ReqArg (\s m -> m {optsConfig = Just s}) "CONF")
|
||||
"The path to the config file"
|
||||
, Option ['m'] ["mount"]
|
||||
(ReqArg (\s m -> m { optsAlias = Just s } ) "ALIAS")
|
||||
, Option
|
||||
['m']
|
||||
["mount"]
|
||||
(ReqArg (\s m -> m {optsAlias = Just s}) "ALIAS")
|
||||
"Mount the device specified by ALIAS directly"
|
||||
, Option ['u'] ["unmount"] (NoArg (\m -> m { optsUnmount = True } ))
|
||||
, Option
|
||||
['u']
|
||||
["unmount"]
|
||||
(NoArg (\m -> m {optsUnmount = True}))
|
||||
"Unmount the device specified by ALIAS instead of mounting it."
|
||||
]
|
||||
|
||||
|
@ -76,10 +77,11 @@ data Opts = Opts
|
|||
, optsAlias :: Maybe String
|
||||
, optsUnmount :: Bool
|
||||
, optsRofiArgs :: [String]
|
||||
} deriving Show
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Main prompt
|
||||
-- Main prompt
|
||||
--
|
||||
-- This command will have one Rofi prompt and will display all available
|
||||
-- mounts grouped by device type (eg removable, sshfs, cifs, etc). I like
|
||||
|
@ -92,7 +94,8 @@ runMounts opts = do
|
|||
let tmpPath = fromMaybe defaultTmpPath $ scTmpPath =<< static
|
||||
let staticDevs = maybe M.empty scDevices static
|
||||
let verbose = fromMaybe False $ scVerbose =<< static
|
||||
let mountconf = MountConf
|
||||
let mountconf =
|
||||
MountConf
|
||||
{ mountconfVolatilePath = tmpPath
|
||||
, mountconfRofiArgs = optsRofiArgs opts
|
||||
, mountconfStaticDevs = staticDevs
|
||||
|
@ -110,7 +113,9 @@ parseStaticConfig p = do
|
|||
Right c -> return $ Just (c :: StaticConfig)
|
||||
where
|
||||
es = over substitutions (DM.union vars) defaultEvaluateSettings
|
||||
vars = DM.fromList $ catMaybes
|
||||
vars =
|
||||
DM.fromList $
|
||||
catMaybes
|
||||
[ toVar (auto :: Decoder TreeConfig)
|
||||
, toVar (auto :: Decoder DeviceConfig)
|
||||
, toVar (auto :: Decoder DataConfig)
|
||||
|
@ -124,11 +129,17 @@ parseStaticConfig p = do
|
|||
, toVar (auto :: Decoder BitwardenConfig)
|
||||
, toVar (auto :: Decoder MountConfig)
|
||||
]
|
||||
toVar a = fmap (\n -> (T.pack $ show n, maximum $ expected a))
|
||||
$ listToMaybe $ snd $ splitTyConApp $ typeOf a
|
||||
toVar a =
|
||||
fmap (\n -> (T.pack $ show n, maximum $ expected a)) $
|
||||
listToMaybe $
|
||||
snd $
|
||||
splitTyConApp $
|
||||
typeOf a
|
||||
|
||||
runPrompt :: RofiConf c => [RofiGroup c] -> RofiIO c ()
|
||||
runPrompt gs = selectAction $ emptyMenu
|
||||
runPrompt gs =
|
||||
selectAction $
|
||||
emptyMenu
|
||||
{ groups = gs
|
||||
, prompt = Just "Select Device"
|
||||
}
|
||||
|
@ -136,10 +147,26 @@ runPrompt gs = selectAction $ emptyMenu
|
|||
getGroups :: RofiMountIO [RofiGroup MountConf]
|
||||
getGroups = do
|
||||
actions <- sequence [getStaticActions, getRemovableActions, getMTPActions]
|
||||
return $ mapMaybe mkGroup
|
||||
$ groupBy (\(hx, _) (hy, _) -> hx == hy)
|
||||
$ sortBy (\(hx, _) (hy, _) -> compare hx hy)
|
||||
$ concat actions
|
||||
return $
|
||||
(++ [metaActions]) $
|
||||
mapMaybe mkGroup $
|
||||
groupBy (\(hx, _) (hy, _) -> hx == hy) $
|
||||
sortBy (\(hx, _) (hy, _) -> compare hx hy) $
|
||||
concat actions
|
||||
where
|
||||
metaActions =
|
||||
titledGroup "Meta Actions" $
|
||||
toRofiActions [(" Dismount All", dismountAll)]
|
||||
|
||||
dismountAll :: RofiMountIO ()
|
||||
dismountAll = do
|
||||
umount =<< asks (configToTree' . mountconfStaticDevs)
|
||||
umount =<< getRemovableDevices
|
||||
umount =<< getMTPDevices
|
||||
return ()
|
||||
where
|
||||
umount :: Mountable a => [a] -> RofiMountIO ()
|
||||
umount = mapM_ (`mountMaybe` True)
|
||||
|
||||
mountByAlias :: Bool -> String -> RofiMountIO ()
|
||||
mountByAlias unmountFlag alias = do
|
||||
|
@ -148,8 +175,9 @@ mountByAlias unmountFlag alias = do
|
|||
|
||||
mkGroup :: [(Header, ProtoAction [String])] -> Maybe (RofiGroup MountConf)
|
||||
mkGroup [] = Nothing
|
||||
mkGroup as = let ((h, _):_) = as in
|
||||
Just $ titledGroup (show h) $ toRofiActions $ alignEntries $ fmap snd as
|
||||
mkGroup as =
|
||||
let ((h, _) : _) = as
|
||||
in Just $ titledGroup (show h) $ toRofiActions $ alignEntries $ fmap snd as
|
||||
|
||||
alignSep :: String
|
||||
alignSep = " | "
|
||||
|
@ -158,7 +186,8 @@ alignEntries :: [ProtoAction [String]] -> [(String, RofiMountIO ())]
|
|||
alignEntries ps = zip (align es) as
|
||||
where
|
||||
(es, as) = unzip $ fmap (\(ProtoAction e a) -> (e, a)) ps
|
||||
align = fmap (intercalate alignSep)
|
||||
align =
|
||||
fmap (intercalate alignSep)
|
||||
. transpose
|
||||
. mapToLast pad
|
||||
. transpose
|
||||
|
@ -166,23 +195,24 @@ alignEntries ps = zip (align es) as
|
|||
getMax = maximum . fmap length
|
||||
mapToLast _ [] = []
|
||||
mapToLast _ [x] = [x]
|
||||
mapToLast f (x:xs) = f x : mapToLast f xs
|
||||
mapToLast f (x : xs) = f x : mapToLast f xs
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Global config used in the reader monad stack
|
||||
-- Global config used in the reader monad stack
|
||||
|
||||
data MountConf = MountConf
|
||||
{ mountconfVolatilePath :: FilePath
|
||||
, mountconfRofiArgs :: [String]
|
||||
, mountconfStaticDevs :: M.Map String TreeConfig
|
||||
, mountconfVerbose :: Bool
|
||||
} deriving Show
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
instance RofiConf MountConf where
|
||||
defArgs MountConf { mountconfRofiArgs = a } = a
|
||||
defArgs MountConf {mountconfRofiArgs = a} = a
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Mountable typeclass
|
||||
-- Mountable typeclass
|
||||
--
|
||||
-- Class to provide common interface for anything that can be mounted.
|
||||
|
||||
|
@ -219,7 +249,7 @@ class Mountable a where
|
|||
mountState :: a -> RofiMountIO MountState
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Actionable typeclass
|
||||
-- Actionable typeclass
|
||||
--
|
||||
-- Class to provide common interface for anything that can be presented in the
|
||||
-- Rofi menu as an action. Note that this must be separate from the Mountable
|
||||
|
@ -243,7 +273,7 @@ class Mountable a => Actionable a where
|
|||
let h = groupHeader dev
|
||||
let action = when i $ mountMaybe dev $ mountedState m
|
||||
let entry = case fmtEntry dev of
|
||||
(e:es) -> (mountedPrefix m i ++ e):es
|
||||
(e : es) -> (mountedPrefix m i ++ e) : es
|
||||
_ -> []
|
||||
return (h, ProtoAction entry action)
|
||||
where
|
||||
|
@ -252,13 +282,17 @@ class Mountable a => Actionable a where
|
|||
mountedPrefix Mounted True = "* "
|
||||
mountedPrefix Partial True = "- "
|
||||
|
||||
mountableToAction :: Actionable a => RofiMountIO [a] -> RofiMountIO [(Header, ProtoAction [String])]
|
||||
mountableToAction
|
||||
:: Actionable a
|
||||
=> RofiMountIO [a]
|
||||
-> RofiMountIO [(Header, ProtoAction [String])]
|
||||
mountableToAction ms = mapM mkAction =<< ms
|
||||
|
||||
type RofiMountIO a = RofiIO MountConf a
|
||||
|
||||
-- headers appear in the order listed here (per Enum)
|
||||
data Header = CIFSHeader
|
||||
data Header
|
||||
= CIFSHeader
|
||||
| SSHFSHeader
|
||||
| VeracryptHeader
|
||||
| RemovableHeader
|
||||
|
@ -281,27 +315,30 @@ instance Ord Header where
|
|||
data ProtoAction a = ProtoAction a (RofiMountIO ())
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Static device configuration (dhall)
|
||||
-- Static device configuration (dhall)
|
||||
|
||||
data MountConfig = MountConfig
|
||||
{ mpPath :: FilePath
|
||||
, mpLabel :: Maybe String
|
||||
} deriving (Show, Generic, FromDhall)
|
||||
}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
data BitwardenConfig = BitwardenConfig
|
||||
{ bwKey :: String
|
||||
, bwTries :: Integer }
|
||||
, bwTries :: Integer
|
||||
}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
newtype SecretConfig = SecretConfig
|
||||
{ secretAttributes :: M.Map String String }
|
||||
{secretAttributes :: M.Map String String}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
newtype PromptConfig = PromptConfig
|
||||
{ promptTries :: Integer }
|
||||
{promptTries :: Integer}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
data PasswordConfig = PwdBW BitwardenConfig
|
||||
data PasswordConfig
|
||||
= PwdBW BitwardenConfig
|
||||
| PwdLS SecretConfig
|
||||
| PwdPr PromptConfig
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
@ -312,9 +349,11 @@ data CIFSOpts = CIFSOpts
|
|||
, cifsoptsUID :: Maybe Integer
|
||||
, cifsoptsGID :: Maybe Integer
|
||||
, cifsoptsIocharset :: Maybe String
|
||||
} deriving (Show, Generic, FromDhall)
|
||||
}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
data DataConfig = VeracryptConfig VeracryptData
|
||||
data DataConfig
|
||||
= VeracryptConfig VeracryptData
|
||||
| SSHFSConfig SSHFSData
|
||||
| CIFSConfig CIFSData
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
@ -322,39 +361,45 @@ data DataConfig = VeracryptConfig VeracryptData
|
|||
data VeracryptData = VeracryptData
|
||||
{ vcVolume :: String
|
||||
, vcPassword :: Maybe PasswordConfig
|
||||
} deriving (Show, Generic, FromDhall)
|
||||
}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
data SSHFSData = SSHFSData
|
||||
{ sshfsRemote :: String
|
||||
, sshfsPassword :: Maybe PasswordConfig
|
||||
} deriving (Show, Generic, FromDhall)
|
||||
}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
data CIFSData = CIFSData
|
||||
{ cifsRemote :: String
|
||||
, cifsSudo :: Bool
|
||||
, cifsPassword :: Maybe PasswordConfig
|
||||
, cifsOpts :: Maybe CIFSOpts
|
||||
} deriving (Show, Generic, FromDhall)
|
||||
}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
data DeviceConfig = DeviceConfig
|
||||
{ deviceMount :: MountConfig
|
||||
, deviceData :: DataConfig
|
||||
} deriving (Show, Generic, FromDhall)
|
||||
}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
data TreeConfig = TreeConfig
|
||||
{ tcParent :: DeviceConfig
|
||||
, tcChildren :: V.Vector String
|
||||
} deriving (Show, Generic, FromDhall)
|
||||
}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
data StaticConfig = StaticConfig
|
||||
{ scTmpPath :: Maybe String
|
||||
, scVerbose :: Maybe Bool
|
||||
, scDevices :: M.Map String TreeConfig
|
||||
} deriving (Show, Generic, FromDhall)
|
||||
}
|
||||
deriving (Show, Generic, FromDhall)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Static devices trees
|
||||
--
|
||||
-- Static devices trees
|
||||
|
||||
-- Static devices as defined in the config file may declare dependencies on
|
||||
-- other static devices, and thus are best represented as a tree. Note that the
|
||||
-- tree is both Actionable and Mountable, where each node in the tree is only
|
||||
|
@ -378,29 +423,29 @@ instance Mountable a => Mountable (Tree a) where
|
|||
getLabel (Tree p _) = getLabel p
|
||||
|
||||
instance Actionable (Tree DeviceConfig) where
|
||||
fmtEntry (Tree p@DeviceConfig{ deviceData = d } _) = [getLabel p, target d]
|
||||
fmtEntry (Tree p@DeviceConfig {deviceData = d} _) = [getLabel p, target d]
|
||||
where
|
||||
target (CIFSConfig (CIFSData { cifsRemote = r })) = r
|
||||
target (SSHFSConfig (SSHFSData { sshfsRemote = r })) = r
|
||||
target (VeracryptConfig (VeracryptData { vcVolume = v })) = v
|
||||
target (CIFSConfig (CIFSData {cifsRemote = r})) = r
|
||||
target (SSHFSConfig (SSHFSData {sshfsRemote = r})) = r
|
||||
target (VeracryptConfig (VeracryptData {vcVolume = v})) = v
|
||||
|
||||
groupHeader (Tree DeviceConfig{ deviceData = d } _) =
|
||||
groupHeader (Tree DeviceConfig {deviceData = d} _) =
|
||||
case d of
|
||||
CIFSConfig{} -> CIFSHeader
|
||||
SSHFSConfig{} -> SSHFSHeader
|
||||
VeracryptConfig{} -> VeracryptHeader
|
||||
CIFSConfig {} -> CIFSHeader
|
||||
SSHFSConfig {} -> SSHFSHeader
|
||||
VeracryptConfig {} -> VeracryptHeader
|
||||
|
||||
configToTree' :: M.Map String TreeConfig -> [StaticConfigTree]
|
||||
configToTree' devMap = configToTree devMap <$> M.elems devMap
|
||||
|
||||
configToTree :: M.Map String TreeConfig -> TreeConfig -> StaticConfigTree
|
||||
configToTree devMap TreeConfig{ tcParent = p, tcChildren = c } =
|
||||
configToTree devMap TreeConfig {tcParent = p, tcChildren = c} =
|
||||
Tree p $ fmap go V.toList c
|
||||
where
|
||||
go ds = configToTree devMap <$> mapMaybe (`M.lookup` devMap) ds
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Static devices
|
||||
-- Static devices
|
||||
--
|
||||
-- This is complex because there may be multiple classes of static devices
|
||||
-- in the config file, and each device may depend on another device that is
|
||||
|
@ -410,50 +455,56 @@ configToTree devMap TreeConfig{ tcParent = p, tcChildren = c } =
|
|||
-- outside of these needs to be aware of these different classes.
|
||||
|
||||
instance Mountable DeviceConfig where
|
||||
mount DeviceConfig{ deviceMount = m, deviceData = devData} False = do
|
||||
mount DeviceConfig {deviceMount = m, deviceData = devData} False = do
|
||||
m' <- getAbsMountpoint m
|
||||
withTmpMountDir m'
|
||||
$ io
|
||||
$ case devData of
|
||||
SSHFSConfig (SSHFSData { sshfsRemote = r, sshfsPassword = p }) ->
|
||||
withTmpMountDir m' $
|
||||
io $
|
||||
case devData of
|
||||
SSHFSConfig (SSHFSData {sshfsRemote = r, sshfsPassword = p}) ->
|
||||
mountSSHFS m' p r
|
||||
CIFSConfig (CIFSData
|
||||
CIFSConfig
|
||||
( CIFSData
|
||||
{ cifsRemote = r
|
||||
, cifsSudo = s
|
||||
, cifsPassword = p
|
||||
, cifsOpts = o
|
||||
}) ->
|
||||
}
|
||||
) ->
|
||||
mountCIFS s r m' o p
|
||||
VeracryptConfig (VeracryptData
|
||||
VeracryptConfig
|
||||
( VeracryptData
|
||||
{ vcPassword = p
|
||||
, vcVolume = v
|
||||
}) ->
|
||||
}
|
||||
) ->
|
||||
mountVeracrypt m' p v
|
||||
|
||||
mount DeviceConfig{ deviceMount = m, deviceData = d } True = do
|
||||
mount DeviceConfig {deviceMount = m, deviceData = d} True = do
|
||||
m' <- getAbsMountpoint m
|
||||
runAndRemoveDir m' $ io $ case d of
|
||||
CIFSConfig (CIFSData { cifsSudo = s }) -> runMountSudoMaybe s "umount" [m']
|
||||
CIFSConfig (CIFSData {cifsSudo = s}) -> runMountSudoMaybe s "umount" [m']
|
||||
VeracryptConfig _ -> runVeraCrypt ["-d", m'] ""
|
||||
_ -> runMount "umount" [m'] ""
|
||||
|
||||
allInstalled DeviceConfig{ deviceData = devData } = io $ isJust
|
||||
allInstalled DeviceConfig {deviceData = devData} =
|
||||
io $
|
||||
isJust
|
||||
<$> findExecutable (exe devData)
|
||||
where
|
||||
exe SSHFSConfig{} = "sshfs"
|
||||
exe CIFSConfig{} = "mount.cifs"
|
||||
exe VeracryptConfig{} = "veracrypt"
|
||||
exe SSHFSConfig {} = "sshfs"
|
||||
exe CIFSConfig {} = "mount.cifs"
|
||||
exe VeracryptConfig {} = "veracrypt"
|
||||
|
||||
mountState DeviceConfig{ deviceMount = m, deviceData = d } = do
|
||||
mountState DeviceConfig {deviceMount = m, deviceData = d} = do
|
||||
-- mountState DeviceConfig{ deviceMount = m } = do
|
||||
case d of
|
||||
VeracryptConfig{} -> veracryptMountState m
|
||||
VeracryptConfig {} -> veracryptMountState m
|
||||
_ -> do
|
||||
b <- (io . isDirMounted) =<< getAbsMountpoint m
|
||||
return $ if b then Mounted else Unmounted
|
||||
|
||||
getLabel DeviceConfig
|
||||
{ deviceMount = MountConfig { mpPath = p, mpLabel = l }
|
||||
getLabel
|
||||
DeviceConfig
|
||||
{ deviceMount = MountConfig {mpPath = p, mpLabel = l}
|
||||
} = fromMaybe (takeFileName p) l
|
||||
|
||||
mountSSHFS :: FilePath -> Maybe PasswordConfig -> String -> IO MountResult
|
||||
|
@ -462,8 +513,13 @@ mountSSHFS mountpoint pwdConfig remote =
|
|||
where
|
||||
run other = runMount "sshfs" (other ++ [remote, mountpoint])
|
||||
|
||||
mountCIFS :: Bool -> String -> FilePath -> Maybe CIFSOpts
|
||||
-> Maybe PasswordConfig -> IO MountResult
|
||||
mountCIFS
|
||||
:: Bool
|
||||
-> String
|
||||
-> FilePath
|
||||
-> Maybe CIFSOpts
|
||||
-> Maybe PasswordConfig
|
||||
-> IO MountResult
|
||||
mountCIFS useSudo remote mountpoint opts pwdConfig =
|
||||
withPasswordGetter pwdConfig runPwd run
|
||||
where
|
||||
|
@ -474,7 +530,8 @@ mountCIFS useSudo remote mountpoint opts pwdConfig =
|
|||
fromCIFSOpts :: CIFSOpts -> String
|
||||
fromCIFSOpts o = intercalate "," $ mapMaybe concatMaybe fs
|
||||
where
|
||||
fs = [ ("username", cifsoptsUsername)
|
||||
fs =
|
||||
[ ("username", cifsoptsUsername)
|
||||
, ("workgroup", cifsoptsWorkgroup)
|
||||
, ("uid", fmap show . cifsoptsUID)
|
||||
, ("gid", fmap show . cifsoptsGID)
|
||||
|
@ -484,8 +541,8 @@ fromCIFSOpts o = intercalate "," $ mapMaybe concatMaybe fs
|
|||
|
||||
mountVeracrypt :: FilePath -> Maybe PasswordConfig -> String -> IO MountResult
|
||||
mountVeracrypt mountpoint pwdConfig volume =
|
||||
withPasswordGetter pwdConfig (runVeraCrypt (args ++ ["--stdin"]))
|
||||
$ runVeraCrypt args ""
|
||||
withPasswordGetter pwdConfig (runVeraCrypt (args ++ ["--stdin"])) $
|
||||
runVeraCrypt args ""
|
||||
where
|
||||
args = [volume, mountpoint]
|
||||
|
||||
|
@ -510,35 +567,36 @@ veracryptMountState mc = do
|
|||
auxPath = fmap (\i -> "/tmp/.veracrypt_aux_mnt" ++ [i]) . vcIndex
|
||||
vcIndex spec = case reverse spec of
|
||||
-- TODO what if I have more than one digit?
|
||||
(i:_) -> if i `elem` ['0'..'9'] then Just i else Nothing
|
||||
(i : _) -> if i `elem` ['0' .. '9'] then Just i else Nothing
|
||||
_ -> Nothing
|
||||
|
||||
getAbsMountpoint :: MountConfig -> RofiMountIO FilePath
|
||||
getAbsMountpoint MountConfig{ mpPath = m } =
|
||||
getAbsMountpoint MountConfig {mpPath = m} =
|
||||
asks $ flip appendRoot m . mountconfVolatilePath
|
||||
|
||||
getStaticActions :: RofiMountIO [(Header, ProtoAction [String])]
|
||||
getStaticActions = mountableToAction $ asks $ configToTree' . mountconfStaticDevs
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Password-getting functions for static devices
|
||||
-- Password-getting functions for static devices
|
||||
|
||||
type PasswordGetter = IO (Maybe String)
|
||||
|
||||
runSecret :: M.Map String String -> PasswordGetter
|
||||
runSecret kvs = readCmdSuccess "secret-tool" ("lookup":kvs') ""
|
||||
runSecret kvs = readCmdSuccess "secret-tool" ("lookup" : kvs') ""
|
||||
where
|
||||
kvs' = concatMap (\(k, v) -> [k, v]) $ M.toList kvs
|
||||
|
||||
runBitwarden :: String -> PasswordGetter
|
||||
runBitwarden pname = ((password . login) <=< find (\i -> name i == pname))
|
||||
runBitwarden pname =
|
||||
((password . login) <=< find (\i -> name i == pname))
|
||||
<$> getItems
|
||||
|
||||
runPromptLoop :: Integer -> PasswordGetter -> PasswordGetter
|
||||
runPromptLoop n pwd = do
|
||||
res <- pwd
|
||||
if isNothing res then
|
||||
if n <= 0 then return Nothing else runPromptLoop (n-1) pwd
|
||||
if isNothing res
|
||||
then if n <= 0 then return Nothing else runPromptLoop (n - 1) pwd
|
||||
else return res
|
||||
|
||||
-- configToPwd :: PasswordConfig -> PasswordGetter
|
||||
|
@ -557,20 +615,23 @@ runPromptLoop n pwd = do
|
|||
-- runMaybe x y = (\r -> if isNothing r then y else return r) =<< x
|
||||
|
||||
configToPwd :: PasswordConfig -> PasswordGetter
|
||||
configToPwd (PwdBW (BitwardenConfig { bwKey = k, bwTries = n })) =
|
||||
configToPwd (PwdBW (BitwardenConfig {bwKey = k, bwTries = n})) =
|
||||
runPromptLoop n $ runBitwarden k
|
||||
configToPwd (PwdLS s) = runSecret $ secretAttributes s
|
||||
configToPwd (PwdPr p) = flip runPromptLoop readPassword $ promptTries p
|
||||
|
||||
withPasswordGetter :: Maybe PasswordConfig -> (String -> IO MountResult)
|
||||
-> IO MountResult -> IO MountResult
|
||||
withPasswordGetter
|
||||
:: Maybe PasswordConfig
|
||||
-> (String -> IO MountResult)
|
||||
-> IO MountResult
|
||||
-> IO MountResult
|
||||
withPasswordGetter (Just pwdConfig) runPwd _ =
|
||||
maybe (return $ MountError "Password could not be obtained") runPwd
|
||||
=<< configToPwd pwdConfig
|
||||
withPasswordGetter Nothing _ run = run
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Removable devices
|
||||
-- Removable devices
|
||||
--
|
||||
-- A device which can be removed (such as a flash drive). These are distinct
|
||||
-- from any device in the static configuration in that they only have device
|
||||
|
@ -583,21 +644,21 @@ data Removable = Removable
|
|||
deriving (Eq, Show)
|
||||
|
||||
instance Mountable Removable where
|
||||
mount Removable { removablePath = d } m =
|
||||
mount Removable {removablePath = d} m =
|
||||
io $ runMount "udisksctl" [c, "-b", d] ""
|
||||
where
|
||||
c = if m then "unmount" else "mount"
|
||||
|
||||
allInstalled _ = fmap isJust $ io $ findExecutable "udisksctl"
|
||||
|
||||
mountState Removable { removablePath = d } = do
|
||||
mountState Removable {removablePath = d} = do
|
||||
s <- elem d <$> io curDeviceSpecs
|
||||
return $ if s then Mounted else Unmounted
|
||||
|
||||
getLabel Removable { removableLabel = l } = l
|
||||
getLabel Removable {removableLabel = l} = l
|
||||
|
||||
instance Actionable Removable where
|
||||
fmtEntry Removable { removablePath = d, removableLabel = l } = [l, d]
|
||||
fmtEntry Removable {removablePath = d, removableLabel = l} = [l, d]
|
||||
|
||||
groupHeader _ = RemovableHeader
|
||||
|
||||
|
@ -607,23 +668,24 @@ instance Actionable Removable where
|
|||
-- label shown on the prompt will be 'SIZE Volume' where size is the size of
|
||||
-- the device
|
||||
getRemovableDevices :: RofiConf c => RofiIO c [Removable]
|
||||
getRemovableDevices = fromLines toDev . lines
|
||||
getRemovableDevices =
|
||||
fromLines toDev . lines
|
||||
<$> io (readProcess "lsblk" ["-n", "-r", "-o", columns] "")
|
||||
where
|
||||
columns = "FSTYPE,HOTPLUG,PATH,LABEL,SIZE"
|
||||
-- can't use 'words' here since it will drop spaces in the front
|
||||
toDev line = case splitBy ' ' line of
|
||||
("":_) -> Nothing
|
||||
("" : _) -> Nothing
|
||||
[_, "1", d, "", s] -> mk d $ s ++ " Volume"
|
||||
[_, "1", d, l, _] -> mk d l
|
||||
_ -> Nothing
|
||||
mk d l = Just $ Removable { removablePath = d, removableLabel = l }
|
||||
mk d l = Just $ Removable {removablePath = d, removableLabel = l}
|
||||
|
||||
getRemovableActions :: RofiMountIO [(Header, ProtoAction [String])]
|
||||
getRemovableActions = mountableToAction getRemovableDevices
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | MTP devices
|
||||
-- MTP devices
|
||||
|
||||
mtpExe :: String
|
||||
mtpExe = "jmtpfs"
|
||||
|
@ -637,18 +699,17 @@ data MTPFS = MTPFS
|
|||
deriving (Eq, Show)
|
||||
|
||||
instance Mountable MTPFS where
|
||||
mount MTPFS { mtpfsBus = b, mtpfsDevice = n, mtpfsMountpoint = m } False = do
|
||||
mount MTPFS {mtpfsBus = b, mtpfsDevice = n, mtpfsMountpoint = m} False = do
|
||||
-- TODO add autodismount to options
|
||||
let dev = "-device=" ++ b ++ "," ++ n
|
||||
withTmpMountDir m $ io $ runMount mtpExe [dev, m] ""
|
||||
|
||||
mount MTPFS { mtpfsMountpoint = m } True =
|
||||
mount MTPFS {mtpfsMountpoint = m} True =
|
||||
runAndRemoveDir m $ io $ runMount "umount" [m] ""
|
||||
|
||||
-- | return True always since the list won't even show without jmtpfs
|
||||
-- \| return True always since the list won't even show without jmtpfs
|
||||
allInstalled _ = return True
|
||||
|
||||
mountState MTPFS { mtpfsMountpoint = m } = do
|
||||
mountState MTPFS {mtpfsMountpoint = m} = do
|
||||
s <- io $ isDirMounted m
|
||||
return $ if s then Mounted else Unmounted
|
||||
|
||||
|
@ -664,13 +725,16 @@ getMTPDevices = do
|
|||
dir <- asks mountconfVolatilePath
|
||||
res <- io $ readProcess mtpExe ["-l"] ""
|
||||
return $ fromLines (toDev dir) $ toDevList res
|
||||
toDevList = reverse
|
||||
toDevList =
|
||||
reverse
|
||||
. takeWhile (not . isPrefixOf "Available devices")
|
||||
. reverse
|
||||
. lines
|
||||
toDev dir s = case splitOn ", " s of
|
||||
[busNum, devNum, _, _, desc, vendor] -> let d = unwords [vendor, desc]
|
||||
in Just $ MTPFS
|
||||
[busNum, devNum, _, _, desc, vendor] ->
|
||||
let d = unwords [vendor, desc]
|
||||
in Just $
|
||||
MTPFS
|
||||
{ mtpfsBus = busNum
|
||||
, mtpfsDevice = devNum
|
||||
, mtpfsMountpoint = dir </> canonicalize d
|
||||
|
@ -695,7 +759,7 @@ instance Actionable MTPFS where
|
|||
groupHeader _ = MTPFSHeader
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Notifications
|
||||
-- Notifications
|
||||
|
||||
data NotifyIcon = IconError | IconInfo
|
||||
|
||||
|
@ -711,13 +775,15 @@ notifyMountResult mounted label result = case result of
|
|||
verb = if mounted then "unmount" else "mount" :: String
|
||||
|
||||
notify :: NotifyIcon -> String -> Maybe String -> IO ()
|
||||
notify icon summary body = void $ spawnProcess "notify-send"
|
||||
$ maybe args (\b -> args ++ [b]) body
|
||||
notify icon summary body =
|
||||
void $
|
||||
spawnProcess "notify-send" $
|
||||
maybe args (\b -> args ++ [b]) body
|
||||
where
|
||||
args = ["-i", show icon, summary]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Mount commands
|
||||
-- Mount commands
|
||||
|
||||
data MountResult = MountSuccess | MountError String deriving (Show, Eq)
|
||||
|
||||
|
@ -725,7 +791,8 @@ runMount :: String -> [String] -> String -> IO MountResult
|
|||
runMount cmd args stdin = eitherToMountResult <$> readCmdEither cmd args stdin
|
||||
|
||||
runMount' :: String -> [String] -> String -> [(String, String)] -> IO MountResult
|
||||
runMount' cmd args stdin environ = eitherToMountResult
|
||||
runMount' cmd args stdin environ =
|
||||
eitherToMountResult
|
||||
<$> readCmdEither' cmd args stdin environ
|
||||
|
||||
runMountSudoMaybe :: Bool -> String -> [String] -> IO MountResult
|
||||
|
@ -733,7 +800,8 @@ runMountSudoMaybe useSudo cmd args =
|
|||
runMountSudoMaybe' useSudo cmd args []
|
||||
|
||||
runMountSudoMaybe' :: Bool -> String -> [String] -> [(String, String)] -> IO MountResult
|
||||
runMountSudoMaybe' useSudo cmd args environ = maybe
|
||||
runMountSudoMaybe' useSudo cmd args environ =
|
||||
maybe
|
||||
(runMount' cmd args "" environ)
|
||||
(\r -> runSudoMount' r cmd args environ)
|
||||
=<< if useSudo then readPassword' "Sudo Password" else return Nothing
|
||||
|
@ -753,7 +821,7 @@ eitherToMountResult (Right _) = MountSuccess
|
|||
eitherToMountResult (Left (_, _, e)) = MountError e
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Low-level mount functions
|
||||
-- Low-level mount functions
|
||||
|
||||
mountMap :: IO (M.Map FilePath String)
|
||||
mountMap = do
|
||||
|
@ -789,7 +857,8 @@ rmDirOnMountError d f = do
|
|||
-- | Run a mount command and create the mountpoint if it does not exist, and
|
||||
-- remove the mountpoint if a mount error occurs
|
||||
withTmpMountDir :: FilePath -> RofiMountIO MountResult -> RofiMountIO MountResult
|
||||
withTmpMountDir m = rmDirOnMountError m
|
||||
withTmpMountDir m =
|
||||
rmDirOnMountError m
|
||||
. bracketOnError_ (mkDirMaybe m) (rmDirMaybe m)
|
||||
|
||||
-- | Run an unmount command and remove the mountpoint if no errors occur
|
||||
|
@ -803,8 +872,10 @@ mkDirMaybe :: FilePath -> RofiMountIO ()
|
|||
mkDirMaybe fp = whenInMountDir fp $ io $ createDirectoryIfMissing True fp
|
||||
|
||||
rmDirMaybe :: FilePath -> RofiMountIO ()
|
||||
rmDirMaybe fp = whenInMountDir fp $ unlessMountpoint fp
|
||||
$ asks mountconfVolatilePath >>= io . rmUntil fp
|
||||
rmDirMaybe fp =
|
||||
whenInMountDir fp $
|
||||
unlessMountpoint fp $
|
||||
asks mountconfVolatilePath >>= io . rmUntil fp
|
||||
where
|
||||
rmUntil cur target = unless (target == cur) $ do
|
||||
removePathForcibly cur
|
||||
|
@ -824,7 +895,7 @@ isDirMounted :: FilePath -> IO Bool
|
|||
isDirMounted fp = elem fp <$> curMountpoints
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Other functions
|
||||
-- Other functions
|
||||
|
||||
fromLines :: (String -> Maybe a) -> [String] -> [a]
|
||||
fromLines f = mapMaybe (f . stripWS)
|
||||
|
@ -834,8 +905,9 @@ splitBy :: Char -> String -> [String]
|
|||
splitBy delimiter = foldr f [[]]
|
||||
where
|
||||
f _ [] = []
|
||||
f c l@(x:xs) | c == delimiter = []:l
|
||||
| otherwise = (c:x):xs
|
||||
f c l@(x : xs)
|
||||
| c == delimiter = [] : l
|
||||
| otherwise = (c : x) : xs
|
||||
|
||||
appendRoot :: FilePath -> FilePath -> FilePath
|
||||
appendRoot root path = if isRelative path then root </> path else path
|
||||
|
|
Loading…
Reference in New Issue