22# Copyright (c) 2017 Benedict Dudel
33# Copyright (c) 2023 Max
44# Copyright (c) 2023 Pete-Hamlin
5+ # Copyright (c) 2025 poettig
56
67import fnmatch
78import os
9+ import shutil
10+ import subprocess
11+ from pathlib import Path
12+
813from albert import *
914
1015md_iid = "4.0"
1318md_description = "Manage passwords in pass"
1419md_license = "BSD-3"
1520md_url = "https://github.com/albertlauncher/albert-plugin-python-pass"
16- md_authors = ["@benedictdudel" , "@maxmil" , "@Pete-Hamlin" , "@okaestne" ]
21+ md_authors = ["@benedictdudel" , "@maxmil" , "@Pete-Hamlin" , "@okaestne" , "@poettig" ]
1722md_maintainers = ["@maxmil" , "@okaestne" , "@Pete-Hamlin" ]
18- md_bin_dependencies = ["pass" ]
1923
2024HOME_DIR = os .environ ["HOME" ]
2125PASS_DIR = os .environ .get ("PASSWORD_STORE_DIR" , os .path .join (HOME_DIR , ".password-store/" ))
@@ -25,12 +29,30 @@ class Plugin(PluginInstance, TriggerQueryHandler):
2529 def __init__ (self ):
2630 PluginInstance .__init__ (self )
2731 TriggerQueryHandler .__init__ (self )
32+ self ._use_gopass = self .readConfig ("use_gopass" , bool ) or False
2833 self ._use_otp = self .readConfig ("use_otp" , bool ) or False
2934 self ._otp_glob = self .readConfig ("otp_glob" , str ) or "*-otp.gpg"
35+ self ._pass_executable = "gopass" if self ._use_gopass else "pass"
3036
31- @staticmethod
32- def makeIcon ():
33- return makeThemeIcon ("dialog-password" )
37+ def makeIcon (self ):
38+ if self ._use_gopass :
39+ return makeImageIcon (Path (__file__ ).parent / "gopass.png" )
40+ else :
41+ return makeThemeIcon ("dialog-password" )
42+
43+ @property
44+ def use_gopass (self ) -> str :
45+ return self ._use_gopass
46+
47+ @use_gopass .setter
48+ def use_gopass (self , value ) -> None :
49+ print (f"Setting _use_gopass to { value } " )
50+ self ._use_gopass = value
51+ self .writeConfig ("use_gopass" , value )
52+
53+ pass_executable = "gopass" if self ._use_gopass else "pass"
54+ print (f"Setting _pass_executable to { pass_executable } " )
55+ self ._pass_executable = pass_executable
3456
3557 @property
3658 def use_otp (self ):
@@ -60,6 +82,11 @@ def synopsis(self, query):
6082
6183 def configWidget (self ):
6284 return [
85+ {
86+ "type" : "checkbox" ,
87+ "property" : "use_gopass" ,
88+ "label" : "Use GoPass instead of pass" ,
89+ },
6390 {"type" : "checkbox" , "property" : "use_otp" , "label" : "Enable pass OTP extension" },
6491 {
6592 "type" : "lineedit" ,
@@ -70,7 +97,16 @@ def configWidget(self):
7097 ]
7198
7299 def handleTriggerQuery (self , query ):
73- if query .string .strip ().startswith ("generate" ):
100+ if not shutil .which (self ._pass_executable ):
101+ query .add (
102+ StandardItem (
103+ id = "executable_not_found" ,
104+ icon_factory = lambda : Plugin .makeIcon (self ),
105+ text = f"{ self ._pass_executable } not found in $PATH" ,
106+ subtext = f"Please check if { self ._pass_executable } is properly installed."
107+ )
108+ )
109+ elif query .string .strip ().startswith ("generate" ):
74110 self .generatePassword (query )
75111 elif query .string .strip ().startswith ("otp" ) and self ._use_otp :
76112 self .showOtp (query )
@@ -83,15 +119,15 @@ def generatePassword(self, query):
83119 query .add (
84120 StandardItem (
85121 id = "generate_password" ,
86- icon_factory = Plugin .makeIcon ,
122+ icon_factory = lambda : Plugin .makeIcon ( self ) ,
87123 text = "Generate a new password" ,
88124 subtext = "The new password will be located at %s" % location ,
89125 input_action_text = "pass %s" % query .string ,
90126 actions = [
91127 Action (
92128 "generate" ,
93129 "Generate" ,
94- lambda : runDetachedProcess (["pass" , "generate" , "--clip" , location , "20" ]),
130+ lambda : runDetachedProcess ([self . _pass_executable , "generate" , "--clip" , location , "20" ]),
95131 )
96132 ],
97133 )
@@ -110,14 +146,14 @@ def showOtp(self, query):
110146 results .append (
111147 StandardItem (
112148 id = password ,
113- icon_factory = Plugin .makeIcon ,
149+ icon_factory = lambda : Plugin .makeIcon ( self ) ,
114150 text = password .split ("/" )[- 1 ],
115151 subtext = password ,
116152 actions = [
117153 Action (
118154 "copy" ,
119155 "Copy" ,
120- lambda pwd = password : runDetachedProcess (["pass" , "otp" , "--clip" , pwd ]),
156+ lambda pwd = password : runDetachedProcess ([self . _pass_executable , "otp" , "--clip" , pwd ]),
121157 ),
122158 ],
123159 ),
@@ -138,36 +174,48 @@ def showPasswords(self, query):
138174 id = password ,
139175 text = name ,
140176 subtext = password ,
141- icon_factory = Plugin .makeIcon ,
177+ icon_factory = lambda : Plugin .makeIcon ( self ) ,
142178 input_action_text = "pass %s" % password ,
143179 actions = [
144180 Action (
145181 "copy" ,
146182 "Copy" ,
147- lambda pwd = password : runDetachedProcess (["pass" , "--clip" , pwd ]),
183+ lambda pwd = password : runDetachedProcess ([self . _pass_executable , "--clip" , pwd ]),
148184 ),
149185 Action (
150186 "edit" ,
151187 "Edit" ,
152- lambda pwd = password : runDetachedProcess (["pass" , "edit" , pwd ]),
188+ lambda pwd = password : runDetachedProcess ([self . _pass_executable , "edit" , pwd ]),
153189 ),
154190 Action (
155191 "remove" ,
156192 "Remove" ,
157- lambda pwd = password : runDetachedProcess (["pass" , "rm" , "--force" , pwd ]),
193+ lambda pwd = password : runDetachedProcess ([self . _pass_executable , "rm" , "--force" , pwd ]),
158194 ),
159195 ],
160196 ),
161197 )
162198
163199 query .add (results )
164200
165- def getPasswords (self , otp = False ):
201+ def getPasswordsFromGoPass (self ) -> list :
202+ p = subprocess .run ([self ._pass_executable , "list" , "--flat" ], stdout = subprocess .PIPE , stderr = subprocess .DEVNULL , encoding = "utf-8" )
203+ return p .stdout .splitlines ()
204+
205+ def getPasswordsFromPass (self , otp = False ) -> list :
166206 passwords = []
167207 for root , dirnames , filenames in os .walk (PASS_DIR , followlinks = True ):
168208 for filename in fnmatch .filter (filenames , self ._otp_glob if otp else "*.gpg" ):
169209 passwords .append (os .path .join (root , filename .replace (".gpg" , "" )).replace (PASS_DIR , "" ))
170210
211+ return passwords
212+
213+ def getPasswords (self , otp = False ):
214+ if self .use_gopass :
215+ passwords = self .getPasswordsFromGoPass ()
216+ else :
217+ passwords = self .getPasswordsFromPass (otp )
218+
171219 return sorted (passwords , key = lambda s : s .lower ())
172220
173221 def getPasswordsFromSearch (self , otp_query , otp = False ):
0 commit comments