diff --git a/CHANGES.md b/CHANGES.md index 9e7d3fc8..23698bd2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -149,6 +149,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..394298a0 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 = return $ filter (p s) compls type PromptLabel = String -data Pass = Pass PromptLabel +newtype Pass = Pass PromptLabel instance XPrompt Pass where showXPrompt (Pass prompt) = prompt ++ ": " @@ -98,7 +97,7 @@ passwordStoreFolderDefault home = combine home ".password-store" passwordStoreFolder :: IO String passwordStoreFolder = getEnv "PASSWORD_STORE_DIR" >>= computePasswordStoreDir - where computePasswordStoreDir Nothing = liftM passwordStoreFolderDefault getHomeDirectory + where computePasswordStoreDir Nothing = fmap passwordStoreFolderDefault getHomeDirectory computePasswordStoreDir (Just storeDir) = return storeDir -- | A pass prompt factory @@ -126,23 +125,41 @@ 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 $ "pass \"" ++ escapeQuote passLabel + ++ "\"|head -n1|tr -d '\n'|xdotool type --clearmodifiers --file -" + +escapeQuote :: String -> String +escapeQuote = concatMap escape + where escape :: Char -> String + escape '"' = ['\\', '\"'] + escape x = return x + +-- | Retrieve the list of passwords from the password store 'passwordStoreDir getPasswords :: FilePath -> IO [String] getPasswords passwordStoreDir = do files <- runProcessWithInput "find" [ @@ -150,7 +167,7 @@ getPasswords passwordStoreDir = do "-type", "f", "-name", "*.gpg", "-printf", "%P\n"] [] - return $ map removeGpgExtension $ lines files + return . map removeGpgExtension $ lines files removeGpgExtension :: String -> String removeGpgExtension file | takeExtension file == ".gpg" = dropExtension file