Skip to content

Commit 4b1584e

Browse files
author
Peter Oettig
committed
feat: Allow to use gopass instead of plain pass
1 parent 3e554a1 commit 4b1584e

File tree

3 files changed

+64
-15
lines changed

3 files changed

+64
-15
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
__pycache__
2+
.idea

__init__.py

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
# Copyright (c) 2017 Benedict Dudel
33
# Copyright (c) 2023 Max
44
# Copyright (c) 2023 Pete-Hamlin
5+
# Copyright (c) 2025 poettig
56

67
import fnmatch
78
import os
9+
import shutil
10+
import subprocess
11+
from pathlib import Path
12+
813
from albert import *
914

1015
md_iid = "4.0"
@@ -13,9 +18,8 @@
1318
md_description = "Manage passwords in pass"
1419
md_license = "BSD-3"
1520
md_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"]
1722
md_maintainers = ["@maxmil", "@okaestne", "@Pete-Hamlin"]
18-
md_bin_dependencies = ["pass"]
1923

2024
HOME_DIR = os.environ["HOME"]
2125
PASS_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):

gopass.png

75.7 KB
Loading

0 commit comments

Comments
 (0)