Compare commits
3 Commits
3e9b08db08
...
cfe0607e2e
Author | SHA1 | Date |
---|---|---|
Nathan Dwarshuis | cfe0607e2e | |
Nathan Dwarshuis | 7094dac44e | |
Nathan Dwarshuis | e13e4150fd |
|
@ -1,357 +0,0 @@
|
||||||
# stylish-haskell configuration file
|
|
||||||
# ==================================
|
|
||||||
|
|
||||||
# The stylish-haskell tool is mainly configured by specifying steps. These steps
|
|
||||||
# are a list, so they have an order, and one specific step may appear more than
|
|
||||||
# once (if needed). Each file is processed by these steps in the given order.
|
|
||||||
steps:
|
|
||||||
# Convert some ASCII sequences to their Unicode equivalents. This is disabled
|
|
||||||
# by default.
|
|
||||||
# - unicode_syntax:
|
|
||||||
# # In order to make this work, we also need to insert the UnicodeSyntax
|
|
||||||
# # language pragma. If this flag is set to true, we insert it when it's
|
|
||||||
# # not already present. You may want to disable it if you configure
|
|
||||||
# # language extensions using some other method than pragmas. Default:
|
|
||||||
# # true.
|
|
||||||
# add_language_pragma: true
|
|
||||||
|
|
||||||
# Format module header
|
|
||||||
#
|
|
||||||
# Currently, this option is not configurable and will format all exports and
|
|
||||||
# module declarations to minimize diffs
|
|
||||||
#
|
|
||||||
# - module_header:
|
|
||||||
# # How many spaces use for indentation in the module header.
|
|
||||||
# indent: 4
|
|
||||||
#
|
|
||||||
# # Should export lists be sorted? Sorting is only performed within the
|
|
||||||
# # export section, as delineated by Haddock comments.
|
|
||||||
# sort: true
|
|
||||||
#
|
|
||||||
# # See `separate_lists` for the `imports` step.
|
|
||||||
# separate_lists: true
|
|
||||||
|
|
||||||
# Format record definitions. This is disabled by default.
|
|
||||||
#
|
|
||||||
# You can control the layout of record fields. The only rules that can't be configured
|
|
||||||
# are these:
|
|
||||||
#
|
|
||||||
# - "|" is always aligned with "="
|
|
||||||
# - "," in fields is always aligned with "{"
|
|
||||||
# - "}" is likewise always aligned with "{"
|
|
||||||
#
|
|
||||||
# - records:
|
|
||||||
# # How to format equals sign between type constructor and data constructor.
|
|
||||||
# # Possible values:
|
|
||||||
# # - "same_line" -- leave "=" AND data constructor on the same line as the type constructor.
|
|
||||||
# # - "indent N" -- insert a new line and N spaces from the beginning of the next line.
|
|
||||||
# equals: "indent 2"
|
|
||||||
#
|
|
||||||
# # How to format first field of each record constructor.
|
|
||||||
# # Possible values:
|
|
||||||
# # - "same_line" -- "{" and first field goes on the same line as the data constructor.
|
|
||||||
# # - "indent N" -- insert a new line and N spaces from the beginning of the data constructor
|
|
||||||
# first_field: "indent 2"
|
|
||||||
#
|
|
||||||
# # How many spaces to insert between the column with "," and the beginning of the comment in the next line.
|
|
||||||
# field_comment: 2
|
|
||||||
#
|
|
||||||
# # How many spaces to insert before "deriving" clause. Deriving clauses are always on separate lines.
|
|
||||||
# deriving: 2
|
|
||||||
#
|
|
||||||
# # How many spaces to insert before "via" clause counted from indentation of deriving clause
|
|
||||||
# # Possible values:
|
|
||||||
# # - "same_line" -- "via" part goes on the same line as "deriving" keyword.
|
|
||||||
# # - "indent N" -- insert a new line and N spaces from the beginning of "deriving" keyword.
|
|
||||||
# via: "indent 2"
|
|
||||||
#
|
|
||||||
# # Sort typeclass names in the "deriving" list alphabetically.
|
|
||||||
# sort_deriving: true
|
|
||||||
#
|
|
||||||
# # Wheter or not to break enums onto several lines
|
|
||||||
# #
|
|
||||||
# # Default: false
|
|
||||||
# break_enums: false
|
|
||||||
#
|
|
||||||
# # Whether or not to break single constructor data types before `=` sign
|
|
||||||
# #
|
|
||||||
# # Default: true
|
|
||||||
# break_single_constructors: true
|
|
||||||
#
|
|
||||||
# # Whether or not to curry constraints on function.
|
|
||||||
# #
|
|
||||||
# # E.g: @allValues :: Enum a => Bounded a => Proxy a -> [a]@
|
|
||||||
# #
|
|
||||||
# # Instead of @allValues :: (Enum a, Bounded a) => Proxy a -> [a]@
|
|
||||||
# #
|
|
||||||
# # Default: false
|
|
||||||
# curried_context: false
|
|
||||||
|
|
||||||
# Align the right hand side of some elements. This is quite conservative
|
|
||||||
# and only applies to statements where each element occupies a single
|
|
||||||
# line.
|
|
||||||
# Possible values:
|
|
||||||
# - always - Always align statements.
|
|
||||||
# - adjacent - Align statements that are on adjacent lines in groups.
|
|
||||||
# - never - Never align statements.
|
|
||||||
# All default to always.
|
|
||||||
- simple_align:
|
|
||||||
cases: always
|
|
||||||
top_level_patterns: always
|
|
||||||
records: always
|
|
||||||
multi_way_if: always
|
|
||||||
|
|
||||||
# Import cleanup
|
|
||||||
- imports:
|
|
||||||
# There are different ways we can align names and lists.
|
|
||||||
#
|
|
||||||
# - global: Align the import names and import list throughout the entire
|
|
||||||
# file.
|
|
||||||
#
|
|
||||||
# - file: Like global, but don't add padding when there are no qualified
|
|
||||||
# imports in the file.
|
|
||||||
#
|
|
||||||
# - group: Only align the imports per group (a group is formed by adjacent
|
|
||||||
# import lines).
|
|
||||||
#
|
|
||||||
# - none: Do not perform any alignment.
|
|
||||||
#
|
|
||||||
# Default: global.
|
|
||||||
align: global
|
|
||||||
|
|
||||||
# The following options affect only import list alignment.
|
|
||||||
#
|
|
||||||
# List align has following options:
|
|
||||||
#
|
|
||||||
# - after_alias: Import list is aligned with end of import including
|
|
||||||
# 'as' and 'hiding' keywords.
|
|
||||||
#
|
|
||||||
# > import qualified Data.List as List (concat, foldl, foldr, head,
|
|
||||||
# > init, last, length)
|
|
||||||
#
|
|
||||||
# - with_alias: Import list is aligned with start of alias or hiding.
|
|
||||||
#
|
|
||||||
# > import qualified Data.List as List (concat, foldl, foldr, head,
|
|
||||||
# > init, last, length)
|
|
||||||
#
|
|
||||||
# - with_module_name: Import list is aligned `list_padding` spaces after
|
|
||||||
# the module name.
|
|
||||||
#
|
|
||||||
# > import qualified Data.List as List (concat, foldl, foldr, head,
|
|
||||||
# init, last, length)
|
|
||||||
#
|
|
||||||
# This is mainly intended for use with `pad_module_names: false`.
|
|
||||||
#
|
|
||||||
# > import qualified Data.List as List (concat, foldl, foldr, head,
|
|
||||||
# init, last, length, scanl, scanr, take, drop,
|
|
||||||
# sort, nub)
|
|
||||||
#
|
|
||||||
# - new_line: Import list starts always on new line.
|
|
||||||
#
|
|
||||||
# > import qualified Data.List as List
|
|
||||||
# > (concat, foldl, foldr, head, init, last, length)
|
|
||||||
#
|
|
||||||
# - repeat: Repeat the module name to align the import list.
|
|
||||||
#
|
|
||||||
# > import qualified Data.List as List (concat, foldl, foldr, head)
|
|
||||||
# > import qualified Data.List as List (init, last, length)
|
|
||||||
#
|
|
||||||
# Default: after_alias
|
|
||||||
list_align: after_alias
|
|
||||||
|
|
||||||
# Right-pad the module names to align imports in a group:
|
|
||||||
#
|
|
||||||
# - true: a little more readable
|
|
||||||
#
|
|
||||||
# > import qualified Data.List as List (concat, foldl, foldr,
|
|
||||||
# > init, last, length)
|
|
||||||
# > import qualified Data.List.Extra as List (concat, foldl, foldr,
|
|
||||||
# > init, last, length)
|
|
||||||
#
|
|
||||||
# - false: diff-safe
|
|
||||||
#
|
|
||||||
# > import qualified Data.List as List (concat, foldl, foldr, init,
|
|
||||||
# > last, length)
|
|
||||||
# > import qualified Data.List.Extra as List (concat, foldl, foldr,
|
|
||||||
# > init, last, length)
|
|
||||||
#
|
|
||||||
# Default: true
|
|
||||||
pad_module_names: true
|
|
||||||
|
|
||||||
# Long list align style takes effect when import is too long. This is
|
|
||||||
# determined by 'columns' setting.
|
|
||||||
#
|
|
||||||
# - inline: This option will put as much specs on same line as possible.
|
|
||||||
#
|
|
||||||
# - new_line: Import list will start on new line.
|
|
||||||
#
|
|
||||||
# - new_line_multiline: Import list will start on new line when it's
|
|
||||||
# short enough to fit to single line. Otherwise it'll be multiline.
|
|
||||||
#
|
|
||||||
# - multiline: One line per import list entry.
|
|
||||||
# Type with constructor list acts like single import.
|
|
||||||
#
|
|
||||||
# > import qualified Data.Map as M
|
|
||||||
# > ( empty
|
|
||||||
# > , singleton
|
|
||||||
# > , ...
|
|
||||||
# > , delete
|
|
||||||
# > )
|
|
||||||
#
|
|
||||||
# Default: inline
|
|
||||||
long_list_align: multiline
|
|
||||||
|
|
||||||
# Align empty list (importing instances)
|
|
||||||
#
|
|
||||||
# Empty list align has following options
|
|
||||||
#
|
|
||||||
# - inherit: inherit list_align setting
|
|
||||||
#
|
|
||||||
# - right_after: () is right after the module name:
|
|
||||||
#
|
|
||||||
# > import Vector.Instances ()
|
|
||||||
#
|
|
||||||
# Default: inherit
|
|
||||||
empty_list_align: inherit
|
|
||||||
|
|
||||||
# List padding determines indentation of import list on lines after import.
|
|
||||||
# This option affects 'long_list_align'.
|
|
||||||
#
|
|
||||||
# - <integer>: constant value
|
|
||||||
#
|
|
||||||
# - module_name: align under start of module name.
|
|
||||||
# Useful for 'file' and 'group' align settings.
|
|
||||||
#
|
|
||||||
# Default: 4
|
|
||||||
list_padding: 4
|
|
||||||
|
|
||||||
# Separate lists option affects formatting of import list for type
|
|
||||||
# or class. The only difference is single space between type and list
|
|
||||||
# of constructors, selectors and class functions.
|
|
||||||
#
|
|
||||||
# - true: There is single space between Foldable type and list of it's
|
|
||||||
# functions.
|
|
||||||
#
|
|
||||||
# > import Data.Foldable (Foldable (fold, foldl, foldMap))
|
|
||||||
#
|
|
||||||
# - false: There is no space between Foldable type and list of it's
|
|
||||||
# functions.
|
|
||||||
#
|
|
||||||
# > import Data.Foldable (Foldable(fold, foldl, foldMap))
|
|
||||||
#
|
|
||||||
# Default: true
|
|
||||||
separate_lists: true
|
|
||||||
|
|
||||||
# Space surround option affects formatting of import lists on a single
|
|
||||||
# line. The only difference is single space after the initial
|
|
||||||
# parenthesis and a single space before the terminal parenthesis.
|
|
||||||
#
|
|
||||||
# - true: There is single space associated with the enclosing
|
|
||||||
# parenthesis.
|
|
||||||
#
|
|
||||||
# > import Data.Foo ( foo )
|
|
||||||
#
|
|
||||||
# - false: There is no space associated with the enclosing parenthesis
|
|
||||||
#
|
|
||||||
# > import Data.Foo (foo)
|
|
||||||
#
|
|
||||||
# Default: false
|
|
||||||
space_surround: false
|
|
||||||
|
|
||||||
# Enabling this argument will use the new GHC lib parse to format imports.
|
|
||||||
#
|
|
||||||
# This currently assumes a few things, it will assume that you want post
|
|
||||||
# qualified imports. It is also not as feature complete as the old
|
|
||||||
# imports formatting.
|
|
||||||
#
|
|
||||||
# It does not remove redundant lines or merge lines. As such, the full
|
|
||||||
# feature scope is still pending.
|
|
||||||
#
|
|
||||||
# It _is_ however, a fine alternative if you are using features that are
|
|
||||||
# not parseable by haskell src extensions and you're comfortable with the
|
|
||||||
# presets.
|
|
||||||
#
|
|
||||||
# Default: false
|
|
||||||
ghc_lib_parser: false
|
|
||||||
|
|
||||||
# Language pragmas
|
|
||||||
- language_pragmas:
|
|
||||||
# We can generate different styles of language pragma lists.
|
|
||||||
#
|
|
||||||
# - vertical: Vertical-spaced language pragmas, one per line.
|
|
||||||
#
|
|
||||||
# - compact: A more compact style.
|
|
||||||
#
|
|
||||||
# - compact_line: Similar to compact, but wrap each line with
|
|
||||||
# `{-#LANGUAGE #-}'.
|
|
||||||
#
|
|
||||||
# Default: vertical.
|
|
||||||
style: vertical
|
|
||||||
|
|
||||||
# Align affects alignment of closing pragma brackets.
|
|
||||||
#
|
|
||||||
# - true: Brackets are aligned in same column.
|
|
||||||
#
|
|
||||||
# - false: Brackets are not aligned together. There is only one space
|
|
||||||
# between actual import and closing bracket.
|
|
||||||
#
|
|
||||||
# Default: true
|
|
||||||
align: true
|
|
||||||
|
|
||||||
# stylish-haskell can detect redundancy of some language pragmas. If this
|
|
||||||
# is set to true, it will remove those redundant pragmas. Default: true.
|
|
||||||
remove_redundant: true
|
|
||||||
|
|
||||||
# Language prefix to be used for pragma declaration, this allows you to
|
|
||||||
# use other options non case-sensitive like "language" or "Language".
|
|
||||||
# If a non correct String is provided, it will default to: LANGUAGE.
|
|
||||||
language_prefix: LANGUAGE
|
|
||||||
|
|
||||||
# Replace tabs by spaces. This is disabled by default.
|
|
||||||
# - tabs:
|
|
||||||
# # Number of spaces to use for each tab. Default: 8, as specified by the
|
|
||||||
# # Haskell report.
|
|
||||||
# spaces: 8
|
|
||||||
|
|
||||||
# Remove trailing whitespace
|
|
||||||
- trailing_whitespace: {}
|
|
||||||
|
|
||||||
# Squash multiple spaces between the left and right hand sides of some
|
|
||||||
# elements into single spaces. Basically, this undoes the effect of
|
|
||||||
# simple_align but is a bit less conservative.
|
|
||||||
# - squash: {}
|
|
||||||
|
|
||||||
# A common setting is the number of columns (parts of) code will be wrapped
|
|
||||||
# to. Different steps take this into account.
|
|
||||||
#
|
|
||||||
# Set this to null to disable all line wrapping.
|
|
||||||
#
|
|
||||||
# Default: 80.
|
|
||||||
columns: 80
|
|
||||||
|
|
||||||
# By default, line endings are converted according to the OS. You can override
|
|
||||||
# preferred format here.
|
|
||||||
#
|
|
||||||
# - native: Native newline format. CRLF on Windows, LF on other OSes.
|
|
||||||
#
|
|
||||||
# - lf: Convert to LF ("\n").
|
|
||||||
#
|
|
||||||
# - crlf: Convert to CRLF ("\r\n").
|
|
||||||
#
|
|
||||||
# Default: native.
|
|
||||||
newline: native
|
|
||||||
|
|
||||||
# Sometimes, language extensions are specified in a cabal file or from the
|
|
||||||
# command line instead of using language pragmas in the file. stylish-haskell
|
|
||||||
# needs to be aware of these, so it can parse the file correctly.
|
|
||||||
#
|
|
||||||
# No language extensions are enabled by default.
|
|
||||||
# language_extensions:
|
|
||||||
# - TemplateHaskell
|
|
||||||
# - QuasiQuotes
|
|
||||||
|
|
||||||
# Attempt to find the cabal file in ancestors of the current directory, and
|
|
||||||
# parse options (currently only language extensions) from that.
|
|
||||||
#
|
|
||||||
# Default: true
|
|
||||||
cabal: true
|
|
330
app/rofi-dev.hs
330
app/rofi-dev.hs
|
@ -5,7 +5,7 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# 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
|
-- 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
|
-- the mount things from the command line. It also creates/destroys mountpoint
|
||||||
|
@ -14,11 +14,9 @@
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
|
|
||||||
import Bitwarden.Internal
|
import Bitwarden.Internal
|
||||||
|
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
|
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.List.Split (splitOn)
|
import Data.List.Split (splitOn)
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
@ -26,21 +24,16 @@ import Data.Maybe
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Typeable
|
import Data.Typeable
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
|
|
||||||
import Dhall hiding (maybe, sequence, void)
|
import Dhall hiding (maybe, sequence, void)
|
||||||
import qualified Dhall.Map as DM
|
import qualified Dhall.Map as DM
|
||||||
|
|
||||||
import Rofi.Command
|
import Rofi.Command
|
||||||
|
|
||||||
import Text.Printf
|
|
||||||
|
|
||||||
import System.Console.GetOpt
|
import System.Console.GetOpt
|
||||||
import System.Directory
|
import System.Directory
|
||||||
import System.Environment
|
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 Text.Printf
|
||||||
import UnliftIO.Exception
|
import UnliftIO.Exception
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
|
@ -52,7 +45,8 @@ parse args = case getOpt Permute options args of
|
||||||
(_, _, errs) -> ioError $ userError $ concat errs ++ usageInfo h options
|
(_, _, errs) -> ioError $ userError $ concat errs ++ usageInfo h options
|
||||||
where
|
where
|
||||||
h = "Usage: rofi-dev [OPTIONS] [-- ROFI-OPTIONS]"
|
h = "Usage: rofi-dev [OPTIONS] [-- ROFI-OPTIONS]"
|
||||||
defaultOpts r = Opts
|
defaultOpts r =
|
||||||
|
Opts
|
||||||
{ optsConfig = Nothing
|
{ optsConfig = Nothing
|
||||||
, optsAlias = Nothing
|
, optsAlias = Nothing
|
||||||
, optsUnmount = False
|
, optsUnmount = False
|
||||||
|
@ -61,13 +55,20 @@ parse args = case getOpt Permute options args of
|
||||||
|
|
||||||
options :: [OptDescr (Opts -> Opts)]
|
options :: [OptDescr (Opts -> Opts)]
|
||||||
options =
|
options =
|
||||||
[ Option ['c'] ["config"]
|
[ Option
|
||||||
(ReqArg (\s m -> m { optsConfig = Just s } ) "CONF")
|
['c']
|
||||||
|
["config"]
|
||||||
|
(ReqArg (\s m -> m {optsConfig = Just s}) "CONF")
|
||||||
"The path to the config file"
|
"The path to the config file"
|
||||||
, Option ['m'] ["mount"]
|
, Option
|
||||||
(ReqArg (\s m -> m { optsAlias = Just s } ) "ALIAS")
|
['m']
|
||||||
|
["mount"]
|
||||||
|
(ReqArg (\s m -> m {optsAlias = Just s}) "ALIAS")
|
||||||
"Mount the device specified by ALIAS directly"
|
"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."
|
"Unmount the device specified by ALIAS instead of mounting it."
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -76,10 +77,11 @@ data Opts = Opts
|
||||||
, optsAlias :: Maybe String
|
, optsAlias :: Maybe String
|
||||||
, optsUnmount :: Bool
|
, optsUnmount :: Bool
|
||||||
, optsRofiArgs :: [String]
|
, optsRofiArgs :: [String]
|
||||||
} deriving Show
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- | Main prompt
|
-- Main prompt
|
||||||
--
|
--
|
||||||
-- This command will have one Rofi prompt and will display all available
|
-- This command will have one Rofi prompt and will display all available
|
||||||
-- mounts grouped by device type (eg removable, sshfs, cifs, etc). I like
|
-- 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 tmpPath = fromMaybe defaultTmpPath $ scTmpPath =<< static
|
||||||
let staticDevs = maybe M.empty scDevices static
|
let staticDevs = maybe M.empty scDevices static
|
||||||
let verbose = fromMaybe False $ scVerbose =<< static
|
let verbose = fromMaybe False $ scVerbose =<< static
|
||||||
let mountconf = MountConf
|
let mountconf =
|
||||||
|
MountConf
|
||||||
{ mountconfVolatilePath = tmpPath
|
{ mountconfVolatilePath = tmpPath
|
||||||
, mountconfRofiArgs = optsRofiArgs opts
|
, mountconfRofiArgs = optsRofiArgs opts
|
||||||
, mountconfStaticDevs = staticDevs
|
, mountconfStaticDevs = staticDevs
|
||||||
|
@ -110,7 +113,9 @@ parseStaticConfig p = do
|
||||||
Right c -> return $ Just (c :: StaticConfig)
|
Right c -> return $ Just (c :: StaticConfig)
|
||||||
where
|
where
|
||||||
es = over substitutions (DM.union vars) defaultEvaluateSettings
|
es = over substitutions (DM.union vars) defaultEvaluateSettings
|
||||||
vars = DM.fromList $ catMaybes
|
vars =
|
||||||
|
DM.fromList $
|
||||||
|
catMaybes
|
||||||
[ toVar (auto :: Decoder TreeConfig)
|
[ toVar (auto :: Decoder TreeConfig)
|
||||||
, toVar (auto :: Decoder DeviceConfig)
|
, toVar (auto :: Decoder DeviceConfig)
|
||||||
, toVar (auto :: Decoder DataConfig)
|
, toVar (auto :: Decoder DataConfig)
|
||||||
|
@ -124,11 +129,17 @@ parseStaticConfig p = do
|
||||||
, toVar (auto :: Decoder BitwardenConfig)
|
, toVar (auto :: Decoder BitwardenConfig)
|
||||||
, toVar (auto :: Decoder MountConfig)
|
, toVar (auto :: Decoder MountConfig)
|
||||||
]
|
]
|
||||||
toVar a = fmap (\n -> (T.pack $ show n, maximum $ expected a))
|
toVar a =
|
||||||
$ listToMaybe $ snd $ splitTyConApp $ typeOf a
|
fmap (\n -> (T.pack $ show n, maximum $ expected a)) $
|
||||||
|
listToMaybe $
|
||||||
|
snd $
|
||||||
|
splitTyConApp $
|
||||||
|
typeOf a
|
||||||
|
|
||||||
runPrompt :: RofiConf c => [RofiGroup c] -> RofiIO c ()
|
runPrompt :: RofiConf c => [RofiGroup c] -> RofiIO c ()
|
||||||
runPrompt gs = selectAction $ emptyMenu
|
runPrompt gs =
|
||||||
|
selectAction $
|
||||||
|
emptyMenu
|
||||||
{ groups = gs
|
{ groups = gs
|
||||||
, prompt = Just "Select Device"
|
, prompt = Just "Select Device"
|
||||||
}
|
}
|
||||||
|
@ -136,10 +147,26 @@ runPrompt gs = selectAction $ emptyMenu
|
||||||
getGroups :: RofiMountIO [RofiGroup MountConf]
|
getGroups :: RofiMountIO [RofiGroup MountConf]
|
||||||
getGroups = do
|
getGroups = do
|
||||||
actions <- sequence [getStaticActions, getRemovableActions, getMTPActions]
|
actions <- sequence [getStaticActions, getRemovableActions, getMTPActions]
|
||||||
return $ mapMaybe mkGroup
|
return $
|
||||||
$ groupBy (\(hx, _) (hy, _) -> hx == hy)
|
(++ [metaActions]) $
|
||||||
$ sortBy (\(hx, _) (hy, _) -> compare hx hy)
|
mapMaybe mkGroup $
|
||||||
$ concat actions
|
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 :: Bool -> String -> RofiMountIO ()
|
||||||
mountByAlias unmountFlag alias = do
|
mountByAlias unmountFlag alias = do
|
||||||
|
@ -148,8 +175,9 @@ mountByAlias unmountFlag alias = do
|
||||||
|
|
||||||
mkGroup :: [(Header, ProtoAction [String])] -> Maybe (RofiGroup MountConf)
|
mkGroup :: [(Header, ProtoAction [String])] -> Maybe (RofiGroup MountConf)
|
||||||
mkGroup [] = Nothing
|
mkGroup [] = Nothing
|
||||||
mkGroup as = let ((h, _):_) = as in
|
mkGroup as =
|
||||||
Just $ titledGroup (show h) $ toRofiActions $ alignEntries $ fmap snd as
|
let ((h, _) : _) = as
|
||||||
|
in Just $ titledGroup (show h) $ toRofiActions $ alignEntries $ fmap snd as
|
||||||
|
|
||||||
alignSep :: String
|
alignSep :: String
|
||||||
alignSep = " | "
|
alignSep = " | "
|
||||||
|
@ -158,7 +186,8 @@ alignEntries :: [ProtoAction [String]] -> [(String, RofiMountIO ())]
|
||||||
alignEntries ps = zip (align es) as
|
alignEntries ps = zip (align es) as
|
||||||
where
|
where
|
||||||
(es, as) = unzip $ fmap (\(ProtoAction e a) -> (e, a)) ps
|
(es, as) = unzip $ fmap (\(ProtoAction e a) -> (e, a)) ps
|
||||||
align = fmap (intercalate alignSep)
|
align =
|
||||||
|
fmap (intercalate alignSep)
|
||||||
. transpose
|
. transpose
|
||||||
. mapToLast pad
|
. mapToLast pad
|
||||||
. transpose
|
. transpose
|
||||||
|
@ -166,23 +195,24 @@ alignEntries ps = zip (align es) as
|
||||||
getMax = maximum . fmap length
|
getMax = maximum . fmap length
|
||||||
mapToLast _ [] = []
|
mapToLast _ [] = []
|
||||||
mapToLast _ [x] = [x]
|
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
|
data MountConf = MountConf
|
||||||
{ mountconfVolatilePath :: FilePath
|
{ mountconfVolatilePath :: FilePath
|
||||||
, mountconfRofiArgs :: [String]
|
, mountconfRofiArgs :: [String]
|
||||||
, mountconfStaticDevs :: M.Map String TreeConfig
|
, mountconfStaticDevs :: M.Map String TreeConfig
|
||||||
, mountconfVerbose :: Bool
|
, mountconfVerbose :: Bool
|
||||||
} deriving Show
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
instance RofiConf MountConf where
|
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.
|
-- Class to provide common interface for anything that can be mounted.
|
||||||
|
|
||||||
|
@ -219,7 +249,7 @@ class Mountable a where
|
||||||
mountState :: a -> RofiMountIO MountState
|
mountState :: a -> RofiMountIO MountState
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- | Actionable typeclass
|
-- Actionable typeclass
|
||||||
--
|
--
|
||||||
-- Class to provide common interface for anything that can be presented in the
|
-- 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
|
-- 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 h = groupHeader dev
|
||||||
let action = when i $ mountMaybe dev $ mountedState m
|
let action = when i $ mountMaybe dev $ mountedState m
|
||||||
let entry = case fmtEntry dev of
|
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)
|
return (h, ProtoAction entry action)
|
||||||
where
|
where
|
||||||
|
@ -252,13 +282,17 @@ class Mountable a => Actionable a where
|
||||||
mountedPrefix Mounted True = "* "
|
mountedPrefix Mounted True = "* "
|
||||||
mountedPrefix Partial 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
|
mountableToAction ms = mapM mkAction =<< ms
|
||||||
|
|
||||||
type RofiMountIO a = RofiIO MountConf a
|
type RofiMountIO a = RofiIO MountConf a
|
||||||
|
|
||||||
-- headers appear in the order listed here (per Enum)
|
-- headers appear in the order listed here (per Enum)
|
||||||
data Header = CIFSHeader
|
data Header
|
||||||
|
= CIFSHeader
|
||||||
| SSHFSHeader
|
| SSHFSHeader
|
||||||
| VeracryptHeader
|
| VeracryptHeader
|
||||||
| RemovableHeader
|
| RemovableHeader
|
||||||
|
@ -281,27 +315,30 @@ instance Ord Header where
|
||||||
data ProtoAction a = ProtoAction a (RofiMountIO ())
|
data ProtoAction a = ProtoAction a (RofiMountIO ())
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- | Static device configuration (dhall)
|
-- Static device configuration (dhall)
|
||||||
|
|
||||||
data MountConfig = MountConfig
|
data MountConfig = MountConfig
|
||||||
{ mpPath :: FilePath
|
{ mpPath :: FilePath
|
||||||
, mpLabel :: Maybe String
|
, mpLabel :: Maybe String
|
||||||
} deriving (Show, Generic, FromDhall)
|
}
|
||||||
|
deriving (Show, Generic, FromDhall)
|
||||||
|
|
||||||
data BitwardenConfig = BitwardenConfig
|
data BitwardenConfig = BitwardenConfig
|
||||||
{ bwKey :: String
|
{ bwKey :: String
|
||||||
, bwTries :: Integer }
|
, bwTries :: Integer
|
||||||
|
}
|
||||||
deriving (Show, Generic, FromDhall)
|
deriving (Show, Generic, FromDhall)
|
||||||
|
|
||||||
newtype SecretConfig = SecretConfig
|
newtype SecretConfig = SecretConfig
|
||||||
{ secretAttributes :: M.Map String String }
|
{secretAttributes :: M.Map String String}
|
||||||
deriving (Show, Generic, FromDhall)
|
deriving (Show, Generic, FromDhall)
|
||||||
|
|
||||||
newtype PromptConfig = PromptConfig
|
newtype PromptConfig = PromptConfig
|
||||||
{ promptTries :: Integer }
|
{promptTries :: Integer}
|
||||||
deriving (Show, Generic, FromDhall)
|
deriving (Show, Generic, FromDhall)
|
||||||
|
|
||||||
data PasswordConfig = PwdBW BitwardenConfig
|
data PasswordConfig
|
||||||
|
= PwdBW BitwardenConfig
|
||||||
| PwdLS SecretConfig
|
| PwdLS SecretConfig
|
||||||
| PwdPr PromptConfig
|
| PwdPr PromptConfig
|
||||||
deriving (Show, Generic, FromDhall)
|
deriving (Show, Generic, FromDhall)
|
||||||
|
@ -312,9 +349,11 @@ data CIFSOpts = CIFSOpts
|
||||||
, cifsoptsUID :: Maybe Integer
|
, cifsoptsUID :: Maybe Integer
|
||||||
, cifsoptsGID :: Maybe Integer
|
, cifsoptsGID :: Maybe Integer
|
||||||
, cifsoptsIocharset :: Maybe String
|
, cifsoptsIocharset :: Maybe String
|
||||||
} deriving (Show, Generic, FromDhall)
|
}
|
||||||
|
deriving (Show, Generic, FromDhall)
|
||||||
|
|
||||||
data DataConfig = VeracryptConfig VeracryptData
|
data DataConfig
|
||||||
|
= VeracryptConfig VeracryptData
|
||||||
| SSHFSConfig SSHFSData
|
| SSHFSConfig SSHFSData
|
||||||
| CIFSConfig CIFSData
|
| CIFSConfig CIFSData
|
||||||
deriving (Show, Generic, FromDhall)
|
deriving (Show, Generic, FromDhall)
|
||||||
|
@ -322,39 +361,45 @@ data DataConfig = VeracryptConfig VeracryptData
|
||||||
data VeracryptData = VeracryptData
|
data VeracryptData = VeracryptData
|
||||||
{ vcVolume :: String
|
{ vcVolume :: String
|
||||||
, vcPassword :: Maybe PasswordConfig
|
, vcPassword :: Maybe PasswordConfig
|
||||||
} deriving (Show, Generic, FromDhall)
|
}
|
||||||
|
deriving (Show, Generic, FromDhall)
|
||||||
|
|
||||||
data SSHFSData = SSHFSData
|
data SSHFSData = SSHFSData
|
||||||
{ sshfsRemote :: String
|
{ sshfsRemote :: String
|
||||||
, sshfsPassword :: Maybe PasswordConfig
|
, sshfsPassword :: Maybe PasswordConfig
|
||||||
} deriving (Show, Generic, FromDhall)
|
}
|
||||||
|
deriving (Show, Generic, FromDhall)
|
||||||
|
|
||||||
data CIFSData = CIFSData
|
data CIFSData = CIFSData
|
||||||
{ cifsRemote :: String
|
{ cifsRemote :: String
|
||||||
, cifsSudo :: Bool
|
, cifsSudo :: Bool
|
||||||
, cifsPassword :: Maybe PasswordConfig
|
, cifsPassword :: Maybe PasswordConfig
|
||||||
, cifsOpts :: Maybe CIFSOpts
|
, cifsOpts :: Maybe CIFSOpts
|
||||||
} deriving (Show, Generic, FromDhall)
|
}
|
||||||
|
deriving (Show, Generic, FromDhall)
|
||||||
|
|
||||||
data DeviceConfig = DeviceConfig
|
data DeviceConfig = DeviceConfig
|
||||||
{ deviceMount :: MountConfig
|
{ deviceMount :: MountConfig
|
||||||
, deviceData :: DataConfig
|
, deviceData :: DataConfig
|
||||||
} deriving (Show, Generic, FromDhall)
|
}
|
||||||
|
deriving (Show, Generic, FromDhall)
|
||||||
|
|
||||||
data TreeConfig = TreeConfig
|
data TreeConfig = TreeConfig
|
||||||
{ tcParent :: DeviceConfig
|
{ tcParent :: DeviceConfig
|
||||||
, tcChildren :: V.Vector String
|
, tcChildren :: V.Vector String
|
||||||
} deriving (Show, Generic, FromDhall)
|
}
|
||||||
|
deriving (Show, Generic, FromDhall)
|
||||||
|
|
||||||
data StaticConfig = StaticConfig
|
data StaticConfig = StaticConfig
|
||||||
{ scTmpPath :: Maybe String
|
{ scTmpPath :: Maybe String
|
||||||
, scVerbose :: Maybe Bool
|
, scVerbose :: Maybe Bool
|
||||||
, scDevices :: M.Map String TreeConfig
|
, 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
|
-- 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
|
-- 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
|
-- 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
|
getLabel (Tree p _) = getLabel p
|
||||||
|
|
||||||
instance Actionable (Tree DeviceConfig) where
|
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
|
where
|
||||||
target (CIFSConfig (CIFSData { cifsRemote = r })) = r
|
target (CIFSConfig (CIFSData {cifsRemote = r})) = r
|
||||||
target (SSHFSConfig (SSHFSData { sshfsRemote = r })) = r
|
target (SSHFSConfig (SSHFSData {sshfsRemote = r})) = r
|
||||||
target (VeracryptConfig (VeracryptData { vcVolume = v })) = v
|
target (VeracryptConfig (VeracryptData {vcVolume = v})) = v
|
||||||
|
|
||||||
groupHeader (Tree DeviceConfig{ deviceData = d } _) =
|
groupHeader (Tree DeviceConfig {deviceData = d} _) =
|
||||||
case d of
|
case d of
|
||||||
CIFSConfig{} -> CIFSHeader
|
CIFSConfig {} -> CIFSHeader
|
||||||
SSHFSConfig{} -> SSHFSHeader
|
SSHFSConfig {} -> SSHFSHeader
|
||||||
VeracryptConfig{} -> VeracryptHeader
|
VeracryptConfig {} -> VeracryptHeader
|
||||||
|
|
||||||
configToTree' :: M.Map String TreeConfig -> [StaticConfigTree]
|
configToTree' :: M.Map String TreeConfig -> [StaticConfigTree]
|
||||||
configToTree' devMap = configToTree devMap <$> M.elems devMap
|
configToTree' devMap = configToTree devMap <$> M.elems devMap
|
||||||
|
|
||||||
configToTree :: M.Map String TreeConfig -> TreeConfig -> StaticConfigTree
|
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
|
Tree p $ fmap go V.toList c
|
||||||
where
|
where
|
||||||
go ds = configToTree devMap <$> mapMaybe (`M.lookup` devMap) ds
|
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
|
-- 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
|
-- 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.
|
-- outside of these needs to be aware of these different classes.
|
||||||
|
|
||||||
instance Mountable DeviceConfig where
|
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
|
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
|
||||||
CIFSConfig (CIFSData
|
CIFSConfig
|
||||||
|
( CIFSData
|
||||||
{ cifsRemote = r
|
{ cifsRemote = r
|
||||||
, cifsSudo = s
|
, cifsSudo = s
|
||||||
, cifsPassword = p
|
, cifsPassword = p
|
||||||
, cifsOpts = o
|
, cifsOpts = o
|
||||||
}) ->
|
}
|
||||||
|
) ->
|
||||||
mountCIFS s r m' o p
|
mountCIFS s r m' o p
|
||||||
VeracryptConfig (VeracryptData
|
VeracryptConfig
|
||||||
|
( VeracryptData
|
||||||
{ vcPassword = p
|
{ vcPassword = p
|
||||||
, vcVolume = v
|
, vcVolume = v
|
||||||
}) ->
|
}
|
||||||
|
) ->
|
||||||
mountVeracrypt m' p v
|
mountVeracrypt m' p v
|
||||||
|
mount DeviceConfig {deviceMount = m, deviceData = d} True = do
|
||||||
mount DeviceConfig{ deviceMount = m, deviceData = d } True = do
|
|
||||||
m' <- getAbsMountpoint m
|
m' <- getAbsMountpoint m
|
||||||
runAndRemoveDir m' $ io $ case d of
|
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'] ""
|
VeracryptConfig _ -> runVeraCrypt ["-d", m'] ""
|
||||||
_ -> runMount "umount" [m'] ""
|
_ -> runMount "umount" [m'] ""
|
||||||
|
|
||||||
allInstalled DeviceConfig{ deviceData = devData } = io $ isJust
|
allInstalled DeviceConfig {deviceData = devData} =
|
||||||
|
io $
|
||||||
|
isJust
|
||||||
<$> findExecutable (exe devData)
|
<$> findExecutable (exe devData)
|
||||||
where
|
where
|
||||||
exe SSHFSConfig{} = "sshfs"
|
exe SSHFSConfig {} = "sshfs"
|
||||||
exe CIFSConfig{} = "mount.cifs"
|
exe CIFSConfig {} = "mount.cifs"
|
||||||
exe VeracryptConfig{} = "veracrypt"
|
exe VeracryptConfig {} = "veracrypt"
|
||||||
|
|
||||||
mountState DeviceConfig{ deviceMount = m, deviceData = d } = do
|
mountState DeviceConfig {deviceMount = m, deviceData = d} = do
|
||||||
-- mountState DeviceConfig{ deviceMount = m } = do
|
-- mountState DeviceConfig{ deviceMount = m } = do
|
||||||
case d of
|
case d of
|
||||||
VeracryptConfig{} -> veracryptMountState m
|
VeracryptConfig {} -> veracryptMountState m
|
||||||
_ -> do
|
_ -> do
|
||||||
b <- (io . isDirMounted) =<< getAbsMountpoint m
|
b <- (io . isDirMounted) =<< getAbsMountpoint m
|
||||||
return $ if b then Mounted else Unmounted
|
return $ if b then Mounted else Unmounted
|
||||||
|
|
||||||
getLabel DeviceConfig
|
getLabel
|
||||||
{ deviceMount = MountConfig { mpPath = p, mpLabel = l }
|
DeviceConfig
|
||||||
|
{ deviceMount = MountConfig {mpPath = p, mpLabel = l}
|
||||||
} = fromMaybe (takeFileName p) l
|
} = fromMaybe (takeFileName p) l
|
||||||
|
|
||||||
mountSSHFS :: FilePath -> Maybe PasswordConfig -> String -> IO MountResult
|
mountSSHFS :: FilePath -> Maybe PasswordConfig -> String -> IO MountResult
|
||||||
|
@ -462,8 +513,13 @@ mountSSHFS mountpoint pwdConfig remote =
|
||||||
where
|
where
|
||||||
run other = runMount "sshfs" (other ++ [remote, mountpoint])
|
run other = runMount "sshfs" (other ++ [remote, mountpoint])
|
||||||
|
|
||||||
mountCIFS :: Bool -> String -> FilePath -> Maybe CIFSOpts
|
mountCIFS
|
||||||
-> Maybe PasswordConfig -> IO MountResult
|
:: Bool
|
||||||
|
-> String
|
||||||
|
-> FilePath
|
||||||
|
-> Maybe CIFSOpts
|
||||||
|
-> Maybe PasswordConfig
|
||||||
|
-> IO MountResult
|
||||||
mountCIFS useSudo remote mountpoint opts pwdConfig =
|
mountCIFS useSudo remote mountpoint opts pwdConfig =
|
||||||
withPasswordGetter pwdConfig runPwd run
|
withPasswordGetter pwdConfig runPwd run
|
||||||
where
|
where
|
||||||
|
@ -474,7 +530,8 @@ mountCIFS useSudo remote mountpoint opts pwdConfig =
|
||||||
fromCIFSOpts :: CIFSOpts -> String
|
fromCIFSOpts :: CIFSOpts -> String
|
||||||
fromCIFSOpts o = intercalate "," $ mapMaybe concatMaybe fs
|
fromCIFSOpts o = intercalate "," $ mapMaybe concatMaybe fs
|
||||||
where
|
where
|
||||||
fs = [ ("username", cifsoptsUsername)
|
fs =
|
||||||
|
[ ("username", cifsoptsUsername)
|
||||||
, ("workgroup", cifsoptsWorkgroup)
|
, ("workgroup", cifsoptsWorkgroup)
|
||||||
, ("uid", fmap show . cifsoptsUID)
|
, ("uid", fmap show . cifsoptsUID)
|
||||||
, ("gid", fmap show . cifsoptsGID)
|
, ("gid", fmap show . cifsoptsGID)
|
||||||
|
@ -484,8 +541,8 @@ fromCIFSOpts o = intercalate "," $ mapMaybe concatMaybe fs
|
||||||
|
|
||||||
mountVeracrypt :: FilePath -> Maybe PasswordConfig -> String -> IO MountResult
|
mountVeracrypt :: FilePath -> Maybe PasswordConfig -> String -> IO MountResult
|
||||||
mountVeracrypt mountpoint pwdConfig volume =
|
mountVeracrypt mountpoint pwdConfig volume =
|
||||||
withPasswordGetter pwdConfig (runVeraCrypt (args ++ ["--stdin"]))
|
withPasswordGetter pwdConfig (runVeraCrypt (args ++ ["--stdin"])) $
|
||||||
$ runVeraCrypt args ""
|
runVeraCrypt args ""
|
||||||
where
|
where
|
||||||
args = [volume, mountpoint]
|
args = [volume, mountpoint]
|
||||||
|
|
||||||
|
@ -510,35 +567,36 @@ veracryptMountState mc = do
|
||||||
auxPath = fmap (\i -> "/tmp/.veracrypt_aux_mnt" ++ [i]) . vcIndex
|
auxPath = fmap (\i -> "/tmp/.veracrypt_aux_mnt" ++ [i]) . vcIndex
|
||||||
vcIndex spec = case reverse spec of
|
vcIndex spec = case reverse spec of
|
||||||
-- TODO what if I have more than one digit?
|
-- 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
|
_ -> Nothing
|
||||||
|
|
||||||
getAbsMountpoint :: MountConfig -> RofiMountIO FilePath
|
getAbsMountpoint :: MountConfig -> RofiMountIO FilePath
|
||||||
getAbsMountpoint MountConfig{ mpPath = m } =
|
getAbsMountpoint MountConfig {mpPath = m} =
|
||||||
asks $ flip appendRoot m . mountconfVolatilePath
|
asks $ flip appendRoot m . mountconfVolatilePath
|
||||||
|
|
||||||
getStaticActions :: RofiMountIO [(Header, ProtoAction [String])]
|
getStaticActions :: RofiMountIO [(Header, ProtoAction [String])]
|
||||||
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 String)
|
type PasswordGetter = IO (Maybe String)
|
||||||
|
|
||||||
runSecret :: M.Map String String -> PasswordGetter
|
runSecret :: M.Map String String -> PasswordGetter
|
||||||
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 :: String -> PasswordGetter
|
runBitwarden :: String -> PasswordGetter
|
||||||
runBitwarden pname = ((password . login) <=< find (\i -> name i == pname))
|
runBitwarden pname =
|
||||||
|
((password . login) <=< find (\i -> name i == pname))
|
||||||
<$> getItems
|
<$> getItems
|
||||||
|
|
||||||
runPromptLoop :: Integer -> PasswordGetter -> PasswordGetter
|
runPromptLoop :: Integer -> PasswordGetter -> PasswordGetter
|
||||||
runPromptLoop n pwd = do
|
runPromptLoop n pwd = do
|
||||||
res <- pwd
|
res <- pwd
|
||||||
if isNothing res then
|
if isNothing res
|
||||||
if n <= 0 then return Nothing else runPromptLoop (n-1) pwd
|
then if n <= 0 then return Nothing else runPromptLoop (n - 1) pwd
|
||||||
else return res
|
else return res
|
||||||
|
|
||||||
-- configToPwd :: PasswordConfig -> PasswordGetter
|
-- configToPwd :: PasswordConfig -> PasswordGetter
|
||||||
|
@ -557,20 +615,23 @@ runPromptLoop n pwd = do
|
||||||
-- 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 :: PasswordConfig -> PasswordGetter
|
||||||
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 $ secretAttributes s
|
configToPwd (PwdLS s) = runSecret $ secretAttributes s
|
||||||
configToPwd (PwdPr p) = flip runPromptLoop readPassword $ promptTries p
|
configToPwd (PwdPr p) = flip runPromptLoop readPassword $ promptTries p
|
||||||
|
|
||||||
withPasswordGetter :: Maybe PasswordConfig -> (String -> IO MountResult)
|
withPasswordGetter
|
||||||
-> IO MountResult -> IO MountResult
|
:: Maybe PasswordConfig
|
||||||
|
-> (String -> IO MountResult)
|
||||||
|
-> IO MountResult
|
||||||
|
-> IO 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
|
||||||
withPasswordGetter Nothing _ run = run
|
withPasswordGetter Nothing _ run = run
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- | Removable devices
|
-- Removable devices
|
||||||
--
|
--
|
||||||
-- A device which can be removed (such as a flash drive). These are distinct
|
-- 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
|
-- from any device in the static configuration in that they only have device
|
||||||
|
@ -583,21 +644,21 @@ data Removable = Removable
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
instance Mountable Removable where
|
instance Mountable Removable where
|
||||||
mount Removable { removablePath = d } m =
|
mount Removable {removablePath = d} m =
|
||||||
io $ runMount "udisksctl" [c, "-b", d] ""
|
io $ runMount "udisksctl" [c, "-b", d] ""
|
||||||
where
|
where
|
||||||
c = if m then "unmount" else "mount"
|
c = if m then "unmount" else "mount"
|
||||||
|
|
||||||
allInstalled _ = fmap isJust $ io $ findExecutable "udisksctl"
|
allInstalled _ = fmap isJust $ io $ findExecutable "udisksctl"
|
||||||
|
|
||||||
mountState Removable { removablePath = d } = do
|
mountState Removable {removablePath = d} = do
|
||||||
s <- elem d <$> io curDeviceSpecs
|
s <- elem d <$> io curDeviceSpecs
|
||||||
return $ if s then Mounted else Unmounted
|
return $ if s then Mounted else Unmounted
|
||||||
|
|
||||||
getLabel Removable { removableLabel = l } = l
|
getLabel Removable {removableLabel = l} = l
|
||||||
|
|
||||||
instance Actionable Removable where
|
instance Actionable Removable where
|
||||||
fmtEntry Removable { removablePath = d, removableLabel = l } = [l, d]
|
fmtEntry Removable {removablePath = d, removableLabel = l} = [l, d]
|
||||||
|
|
||||||
groupHeader _ = RemovableHeader
|
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
|
-- label shown on the prompt will be 'SIZE Volume' where size is the size of
|
||||||
-- the device
|
-- the device
|
||||||
getRemovableDevices :: RofiConf c => RofiIO c [Removable]
|
getRemovableDevices :: RofiConf c => RofiIO c [Removable]
|
||||||
getRemovableDevices = fromLines toDev . lines
|
getRemovableDevices =
|
||||||
|
fromLines toDev . lines
|
||||||
<$> io (readProcess "lsblk" ["-n", "-r", "-o", columns] "")
|
<$> io (readProcess "lsblk" ["-n", "-r", "-o", columns] "")
|
||||||
where
|
where
|
||||||
columns = "FSTYPE,HOTPLUG,PATH,LABEL,SIZE"
|
columns = "FSTYPE,HOTPLUG,PATH,LABEL,SIZE"
|
||||||
-- can't use 'words' here since it will drop spaces in the front
|
-- can't use 'words' here since it will drop spaces in the front
|
||||||
toDev line = case splitBy ' ' line of
|
toDev line = case splitBy ' ' line of
|
||||||
("":_) -> Nothing
|
("" : _) -> Nothing
|
||||||
[_, "1", d, "", s] -> mk d $ s ++ " Volume"
|
[_, "1", d, "", s] -> mk d $ s ++ " Volume"
|
||||||
[_, "1", d, l, _] -> mk d l
|
[_, "1", d, l, _] -> mk d l
|
||||||
_ -> Nothing
|
_ -> 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 :: RofiMountIO [(Header, ProtoAction [String])]
|
||||||
getRemovableActions = mountableToAction getRemovableDevices
|
getRemovableActions = mountableToAction getRemovableDevices
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- | MTP devices
|
-- MTP devices
|
||||||
|
|
||||||
mtpExe :: String
|
mtpExe :: String
|
||||||
mtpExe = "jmtpfs"
|
mtpExe = "jmtpfs"
|
||||||
|
@ -637,18 +699,17 @@ data MTPFS = MTPFS
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
instance Mountable MTPFS where
|
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
|
-- TODO add autodismount to options
|
||||||
let dev = "-device=" ++ b ++ "," ++ n
|
let dev = "-device=" ++ b ++ "," ++ n
|
||||||
withTmpMountDir m $ io $ runMount mtpExe [dev, m] ""
|
withTmpMountDir m $ io $ runMount mtpExe [dev, m] ""
|
||||||
|
mount MTPFS {mtpfsMountpoint = m} True =
|
||||||
mount MTPFS { mtpfsMountpoint = m } True =
|
|
||||||
runAndRemoveDir m $ io $ runMount "umount" [m] ""
|
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
|
allInstalled _ = return True
|
||||||
|
|
||||||
mountState MTPFS { mtpfsMountpoint = m } = do
|
mountState MTPFS {mtpfsMountpoint = m} = do
|
||||||
s <- io $ isDirMounted m
|
s <- io $ isDirMounted m
|
||||||
return $ if s then Mounted else Unmounted
|
return $ if s then Mounted else Unmounted
|
||||||
|
|
||||||
|
@ -664,13 +725,16 @@ getMTPDevices = do
|
||||||
dir <- asks mountconfVolatilePath
|
dir <- asks mountconfVolatilePath
|
||||||
res <- io $ readProcess mtpExe ["-l"] ""
|
res <- io $ readProcess mtpExe ["-l"] ""
|
||||||
return $ fromLines (toDev dir) $ toDevList res
|
return $ fromLines (toDev dir) $ toDevList res
|
||||||
toDevList = reverse
|
toDevList =
|
||||||
|
reverse
|
||||||
. takeWhile (not . isPrefixOf "Available devices")
|
. takeWhile (not . isPrefixOf "Available devices")
|
||||||
. reverse
|
. reverse
|
||||||
. lines
|
. lines
|
||||||
toDev dir s = case splitOn ", " s of
|
toDev dir s = case splitOn ", " s of
|
||||||
[busNum, devNum, _, _, desc, vendor] -> let d = unwords [vendor, desc]
|
[busNum, devNum, _, _, desc, vendor] ->
|
||||||
in Just $ MTPFS
|
let d = unwords [vendor, desc]
|
||||||
|
in Just $
|
||||||
|
MTPFS
|
||||||
{ mtpfsBus = busNum
|
{ mtpfsBus = busNum
|
||||||
, mtpfsDevice = devNum
|
, mtpfsDevice = devNum
|
||||||
, mtpfsMountpoint = dir </> canonicalize d
|
, mtpfsMountpoint = dir </> canonicalize d
|
||||||
|
@ -695,7 +759,7 @@ instance Actionable MTPFS where
|
||||||
groupHeader _ = MTPFSHeader
|
groupHeader _ = MTPFSHeader
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- | Notifications
|
-- Notifications
|
||||||
|
|
||||||
data NotifyIcon = IconError | IconInfo
|
data NotifyIcon = IconError | IconInfo
|
||||||
|
|
||||||
|
@ -711,13 +775,15 @@ notifyMountResult mounted label result = case result of
|
||||||
verb = if mounted then "unmount" else "mount" :: String
|
verb = if mounted then "unmount" else "mount" :: String
|
||||||
|
|
||||||
notify :: NotifyIcon -> String -> Maybe String -> IO ()
|
notify :: NotifyIcon -> String -> Maybe String -> IO ()
|
||||||
notify icon summary body = void $ spawnProcess "notify-send"
|
notify icon summary body =
|
||||||
$ maybe args (\b -> args ++ [b]) body
|
void $
|
||||||
|
spawnProcess "notify-send" $
|
||||||
|
maybe args (\b -> args ++ [b]) body
|
||||||
where
|
where
|
||||||
args = ["-i", show icon, summary]
|
args = ["-i", show icon, summary]
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- | Mount commands
|
-- Mount commands
|
||||||
|
|
||||||
data MountResult = MountSuccess | MountError String deriving (Show, Eq)
|
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 cmd args stdin = eitherToMountResult <$> readCmdEither cmd args stdin
|
||||||
|
|
||||||
runMount' :: String -> [String] -> String -> [(String, String)] -> IO MountResult
|
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
|
<$> readCmdEither' cmd args stdin environ
|
||||||
|
|
||||||
runMountSudoMaybe :: Bool -> String -> [String] -> IO MountResult
|
runMountSudoMaybe :: Bool -> String -> [String] -> IO MountResult
|
||||||
|
@ -733,7 +800,8 @@ runMountSudoMaybe useSudo cmd args =
|
||||||
runMountSudoMaybe' useSudo cmd args []
|
runMountSudoMaybe' useSudo cmd args []
|
||||||
|
|
||||||
runMountSudoMaybe' :: Bool -> String -> [String] -> [(String, String)] -> IO MountResult
|
runMountSudoMaybe' :: Bool -> String -> [String] -> [(String, String)] -> IO MountResult
|
||||||
runMountSudoMaybe' useSudo cmd args environ = maybe
|
runMountSudoMaybe' useSudo cmd args environ =
|
||||||
|
maybe
|
||||||
(runMount' cmd args "" environ)
|
(runMount' cmd args "" environ)
|
||||||
(\r -> runSudoMount' r cmd args environ)
|
(\r -> runSudoMount' r cmd args environ)
|
||||||
=<< if useSudo then readPassword' "Sudo Password" else return Nothing
|
=<< if useSudo then readPassword' "Sudo Password" else return Nothing
|
||||||
|
@ -753,7 +821,7 @@ eitherToMountResult (Right _) = MountSuccess
|
||||||
eitherToMountResult (Left (_, _, e)) = MountError e
|
eitherToMountResult (Left (_, _, e)) = MountError e
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- | Low-level mount functions
|
-- Low-level mount functions
|
||||||
|
|
||||||
mountMap :: IO (M.Map FilePath String)
|
mountMap :: IO (M.Map FilePath String)
|
||||||
mountMap = do
|
mountMap = do
|
||||||
|
@ -789,7 +857,8 @@ 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 -> RofiMountIO MountResult -> RofiMountIO MountResult
|
||||||
withTmpMountDir m = rmDirOnMountError m
|
withTmpMountDir 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
|
||||||
|
@ -803,8 +872,10 @@ mkDirMaybe :: FilePath -> RofiMountIO ()
|
||||||
mkDirMaybe fp = whenInMountDir fp $ io $ createDirectoryIfMissing True fp
|
mkDirMaybe fp = whenInMountDir fp $ io $ createDirectoryIfMissing True fp
|
||||||
|
|
||||||
rmDirMaybe :: FilePath -> RofiMountIO ()
|
rmDirMaybe :: FilePath -> RofiMountIO ()
|
||||||
rmDirMaybe fp = whenInMountDir fp $ unlessMountpoint fp
|
rmDirMaybe fp =
|
||||||
$ asks mountconfVolatilePath >>= io . rmUntil fp
|
whenInMountDir fp $
|
||||||
|
unlessMountpoint fp $
|
||||||
|
asks mountconfVolatilePath >>= io . rmUntil fp
|
||||||
where
|
where
|
||||||
rmUntil cur target = unless (target == cur) $ do
|
rmUntil cur target = unless (target == cur) $ do
|
||||||
removePathForcibly cur
|
removePathForcibly cur
|
||||||
|
@ -824,7 +895,7 @@ isDirMounted :: FilePath -> IO Bool
|
||||||
isDirMounted fp = elem fp <$> curMountpoints
|
isDirMounted fp = elem fp <$> curMountpoints
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- | Other functions
|
-- Other functions
|
||||||
|
|
||||||
fromLines :: (String -> Maybe a) -> [String] -> [a]
|
fromLines :: (String -> Maybe a) -> [String] -> [a]
|
||||||
fromLines f = mapMaybe (f . stripWS)
|
fromLines f = mapMaybe (f . stripWS)
|
||||||
|
@ -834,8 +905,9 @@ splitBy :: Char -> String -> [String]
|
||||||
splitBy delimiter = foldr f [[]]
|
splitBy delimiter = foldr f [[]]
|
||||||
where
|
where
|
||||||
f _ [] = []
|
f _ [] = []
|
||||||
f c l@(x:xs) | c == delimiter = []:l
|
f c l@(x : xs)
|
||||||
| otherwise = (c:x):xs
|
| c == delimiter = [] : l
|
||||||
|
| otherwise = (c : x) : xs
|
||||||
|
|
||||||
appendRoot :: FilePath -> FilePath -> FilePath
|
appendRoot :: FilePath -> FilePath -> FilePath
|
||||||
appendRoot root path = if isRelative path then root </> path else path
|
appendRoot root path = if isRelative path then root </> path else path
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
indentation: 2
|
||||||
|
function-arrows: leading
|
||||||
|
comma-style: leading
|
||||||
|
import-export-style: leading
|
||||||
|
indent-wheres: true
|
||||||
|
record-brace-space: true
|
||||||
|
newlines-between-decls: 1
|
||||||
|
haddock-style: single-line
|
||||||
|
haddock-style-module:
|
||||||
|
let-style: inline
|
||||||
|
in-style: right-align
|
||||||
|
respectful: false
|
||||||
|
fixities: []
|
||||||
|
unicode: never
|
Loading…
Reference in New Issue