diff --git a/CHANGES.md b/CHANGES.md index ab20a99b..ee4c43a8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -145,6 +145,13 @@ - Update logout key combination (modm+shift+Q) to work with modern + * `XMonad.Prompt.Pass` + + - New function `passTypePrompt` which uses `xdotool` to type in a password + from the store, bypassing the clipboard. + - Now handles password labels with spaces and special characters inside + them. + ## 0.13 (February 10, 2017) ### Breaking Changes diff --git a/XMonad/Prompt/Pass.hs b/XMonad/Prompt/Pass.hs index 8b2a1663..6730d9d2 100644 --- a/XMonad/Prompt/Pass.hs +++ b/XMonad/Prompt/Pass.hs @@ -8,9 +8,10 @@ -- Stability : unstable -- Portability : unportable -- --- This module provides 3 to ease passwords manipulation (generate, read, remove): +-- This module provides 4 to ease password manipulation (generate, read, remove): -- --- - one to lookup passwords in the password-storage. +-- - two to lookup passwords in the password-store; one of which copies to the +-- clipboard, and the other uses @xdotool@ to type the password directly. -- -- - one to generate a password for a given password label that the user inputs. -- @@ -18,28 +19,26 @@ -- -- All those prompts benefit from the completion system provided by the module . -- --- The password store is setuped through an environment variable PASSWORD_STORE_DIR. --- If this is set, use the content of the variable. --- Otherwise, the password store is located on user's home @$HOME\/.password-store@. --- +-- The password store is setup through an environment variable PASSWORD_STORE_DIR, +-- or @$HOME\/.password-store@ if it is unset. -- -- Source: -- --- - The password storage implementation is . +-- - The password store implementation is . -- --- - Inspired from +-- - Inspired by -- ----------------------------------------------------------------------------- module XMonad.Prompt.Pass ( - -- * Usages - -- $usages + -- * Usage + -- $usage passPrompt , passGeneratePrompt , passRemovePrompt + , passTypePrompt ) where -import Control.Monad (liftM) import XMonad.Core import XMonad.Prompt ( XPrompt , showXPrompt @@ -54,7 +53,7 @@ import System.FilePath (takeExtension, dropExtension, combine) import System.Posix.Env (getEnv) import XMonad.Util.Run (runProcessWithInput) --- $usages +-- $usage -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: -- -- > import XMonad.Prompt.Pass @@ -69,17 +68,17 @@ import XMonad.Util.Run (runProcessWithInput) -- -- - editing your key bindings, see "XMonad.Doc.Extending#Editing_key_bindings". -- --- - how to setup the password storage, see +-- - how to setup the password store, see -- type Predicate = String -> String -> Bool getPassCompl :: [String] -> Predicate -> String -> IO [String] -getPassCompl compls p s = do return $ filter (p s) compls +getPassCompl compls p s = pure $ filter (p s) compls type PromptLabel = String -data Pass = Pass PromptLabel +newtype Pass = Pass PromptLabel instance XPrompt Pass where showXPrompt (Pass prompt) = prompt ++ ": " @@ -98,8 +97,8 @@ passwordStoreFolderDefault home = combine home ".password-store" passwordStoreFolder :: IO String passwordStoreFolder = getEnv "PASSWORD_STORE_DIR" >>= computePasswordStoreDir - where computePasswordStoreDir Nothing = liftM passwordStoreFolderDefault getHomeDirectory - computePasswordStoreDir (Just storeDir) = return storeDir + where computePasswordStoreDir Nothing = passwordStoreFolderDefault <$> getHomeDirectory + computePasswordStoreDir (Just storeDir) = pure storeDir -- | A pass prompt factory -- @@ -126,23 +125,40 @@ passGeneratePrompt = mkPassPrompt "Generate password" generatePassword passRemovePrompt :: XPConfig -> X () passRemovePrompt = mkPassPrompt "Remove password" removePassword +-- | A prompt to type in a password for a given entry. +-- This doesn't touch the clipboard. +-- +passTypePrompt :: XPConfig -> X () +passTypePrompt = mkPassPrompt "Type password" typePassword + -- | Select a password. -- selectPassword :: String -> X () -selectPassword passLabel = spawn $ "pass --clip " ++ passLabel +selectPassword passLabel = spawn $ "pass --clip \"" ++ escapeQuote passLabel ++ "\"" -- | Generate a 30 characters password for a given entry. -- If the entry already exists, it is updated with a new password. -- generatePassword :: String -> X () -generatePassword passLabel = spawn $ "pass generate --force " ++ passLabel ++ " 30" +generatePassword passLabel = spawn $ "pass generate --force \"" ++ escapeQuote passLabel ++ "\" 30" -- | Remove a password stored for a given entry. -- removePassword :: String -> X () -removePassword passLabel = spawn $ "pass rm --force " ++ passLabel +removePassword passLabel = spawn $ "pass rm --force \"" ++ escapeQuote passLabel ++ "\"" --- | Retrieve the list of passwords from the password storage 'passwordStoreDir +-- | Type a password stored for a given entry using xdotool. +-- +typePassword :: String -> X () +typePassword passLabel = spawn $ "xdotool type --clearmodifiers $(pass \"" ++ escapeQuote passLabel ++ "\"|head -n1)" + +escapeQuote :: String -> String +escapeQuote = concatMap escape + where escape :: Char -> String + escape '"' = ['\\', '\"'] + escape x = pure x + +-- | Retrieve the list of passwords from the password store 'passwordStoreDir getPasswords :: FilePath -> IO [String] getPasswords passwordStoreDir = do files <- runProcessWithInput "find" [ @@ -150,7 +166,7 @@ getPasswords passwordStoreDir = do "-type", "f", "-name", "*.gpg", "-printf", "%P\n"] [] - return $ map removeGpgExtension $ lines files + pure . map removeGpgExtension $ lines files removeGpgExtension :: String -> String removeGpgExtension file | takeExtension file == ".gpg" = dropExtension file