-
Notifications
You must be signed in to change notification settings - Fork 258
Add plugin: Password Store (pass) #863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| import QtQuick | ||
| import Quickshell | ||
| import Quickshell.Io | ||
| import qs.Commons | ||
| import qs.Services.UI | ||
|
|
||
| QtObject { | ||
| id: root | ||
| property var pluginApi: null | ||
|
|
||
| property Component _providerComponent: Component { | ||
| Item { | ||
| id: provider | ||
| property var launcher: null | ||
| property var pluginApi: null | ||
| property string name: "Password Store" | ||
| property bool handleSearch: true | ||
| property var passwords: [] | ||
|
|
||
| function init() { | ||
| listProcess.running = true; | ||
| } | ||
|
|
||
| function onOpened() { | ||
| if (passwords.length === 0) | ||
| listProcess.running = true; | ||
| } | ||
|
|
||
| function getResults(query) { | ||
| const isPass = query === "pass" || query.startsWith("pass "); | ||
| const isOtp = query === "po" || query.startsWith("po "); | ||
| if (!isPass && !isOtp) | ||
| return []; | ||
|
|
||
| const search = isPass | ||
| ? (query.startsWith("pass ") ? query.substring(5).trim() : "") | ||
| : (query.startsWith("po ") ? query.substring(3).trim() : ""); | ||
|
|
||
| const otp = isOtp; | ||
|
|
||
| let matches; | ||
| if (!search) { | ||
| matches = passwords.slice(0, 15).map(p => ({ target: p, score: 1.0 })); | ||
| } else { | ||
| const results = FuzzySort.go(search, passwords, { limit: 15 }); | ||
| matches = Array.from({ length: results.length }, (_, i) => results[i]); | ||
| } | ||
|
|
||
| return matches.map(function(match) { | ||
| const passName = match.target; | ||
| return { | ||
| "name": passName, | ||
| "description": otp ? "Copy OTP to clipboard" : "Copy password to clipboard", | ||
| "icon": otp ? "clock-shield" : "lock", | ||
| "isTablerIcon": true, | ||
| "isImage": false, | ||
| "_score": match.score, | ||
| "onActivate": function() { | ||
| if (provider.launcher) | ||
| provider.launcher.closeImmediately(); | ||
| Qt.callLater(function() { | ||
| Quickshell.execDetached(otp | ||
| ? ["pass", "otp", "-c", passName] | ||
| : ["pass", "-c", passName]); | ||
| }); | ||
| } | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| Process { | ||
| id: listProcess | ||
| command: [ | ||
| "sh", "-c", | ||
| "store=\"${PASSWORD_STORE_DIR:-$HOME/.password-store}\"; " + | ||
| "find \"$store\" -name '*.gpg' | sed \"s|$store/||;s|\\.gpg$||\" | sort" | ||
| ] | ||
| running: false | ||
|
|
||
| stdout: StdioCollector { | ||
| onStreamFinished: { | ||
| if (text && text.trim()) | ||
| provider.passwords = text.trim().split("\n").filter(p => p.length > 0); | ||
| } | ||
| } | ||
| stderr: StdioCollector {} | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Component.onCompleted: { | ||
| LauncherProviderRegistry.registerPluginProvider(pluginApi.pluginId, _providerComponent, {}); | ||
| } | ||
|
|
||
| Component.onDestruction: { | ||
| LauncherProviderRegistry.unregisterPluginProvider(pluginApi.pluginId); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| # Password Store | ||
|
|
||
| Search and copy passwords and OTP codes from [pass](https://www.passwordstore.org/) directly in the Noctalia launcher. | ||
|
|
||
| ## Features | ||
|
|
||
| - **Password search** — type `pass <query>` to fuzzy search your password store and copy a password to clipboard | ||
| - **OTP search** — type `po <query>` to fuzzy search your password store and copy a TOTP code to clipboard | ||
| - **Fuzzy matching** — uses Noctalia's built-in FuzzySort engine, consistent with the app launcher | ||
| - **Clipboard safety** — passwords and OTP codes are cleared from clipboard after 45 seconds (pass default) | ||
| - **XDG-aware** — respects `$PASSWORD_STORE_DIR` with fallback to `~/.password-store` | ||
|
|
||
| ## Requirements | ||
|
|
||
| - **System packages:** `pass`, `pass-otp` | ||
| - **Wayland clipboard:** `wl-clipboard` (for `wl-copy` support — recommended on Wayland) | ||
|
|
||
| ### Installing dependencies | ||
|
|
||
| ```bash | ||
| # Arch Linux / Manjaro | ||
| sudo pacman -S pass pass-otp wl-clipboard | ||
|
|
||
| # Debian / Ubuntu | ||
| sudo apt install pass pass-extension-otp wl-clipboard | ||
|
|
||
| # Fedora | ||
| sudo dnf install pass pass-otp wl-clipboard | ||
| ``` | ||
|
|
||
| ## Installation | ||
|
|
||
| ### Manual Installation | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is the only subsection in the |
||
|
|
||
| ```bash | ||
| git clone https://github.com/noctalia-dev/noctalia-plugins ~/.config/noctalia/plugins/pass | ||
| ``` | ||
|
|
||
| Then restart Noctalia: | ||
|
|
||
| ```bash | ||
| qs kill -c noctalia-shell && qs -c noctalia-shell -d | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| Open the launcher (`Alt+Space` by default) and type: | ||
|
|
||
| | Prefix | Action | | ||
| |--------|--------| | ||
| | `pass <query>` | Fuzzy search all pass entries — press Enter to copy the password | | ||
| | `po <query>` | Fuzzy search all pass entries — press Enter to copy the TOTP code | | ||
|
|
||
| ### Examples | ||
|
|
||
| ``` | ||
| pass kortechs/aws → finds kortechs/aws/bojan@kortechs.io | ||
| pass aws bojan → finds any entry matching both "aws" and "bojan" | ||
| po github → finds OTP entry for github, copies the current TOTP code | ||
| ``` | ||
|
|
||
| Selecting an entry runs `pass -c <entry>` or `pass otp -c <entry>` respectively. If your GPG key requires a passphrase, a pinentry dialog will appear. | ||
|
|
||
| ## File Structure | ||
|
|
||
| ``` | ||
| pass/ | ||
| ├── manifest.json # Plugin metadata | ||
| ├── Main.qml # Launcher provider — search and activation logic | ||
| └── README.md # This file | ||
| ``` | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| **No results appear** | ||
| - Make sure `pass` is installed and `~/.password-store` exists (or `$PASSWORD_STORE_DIR` is set) | ||
| - Restart Noctalia after installing the plugin | ||
|
|
||
| **OTP copy fails silently** | ||
| - Verify `pass-otp` is installed: `pass otp --help` | ||
| - Test manually in a terminal: `pass otp -c <entry>` | ||
|
|
||
| **Password not copied on Wayland** | ||
| - Install `wl-clipboard`: `sudo pacman -S wl-clipboard` | ||
| - `pass` detects Wayland via `$WAYLAND_DISPLAY` and uses `wl-copy` automatically | ||
|
|
||
| ## License | ||
|
|
||
| MIT | ||
|
|
||
| ## Author | ||
|
|
||
| **Bojan Jovanovic** - [github.com/virogenesis](https://github.com/virogenesis) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "id": "pass", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| "name": "Password Store", | ||
| "version": "1.0.0", | ||
| "minNoctaliaVersion": "3.6.0", | ||
| "author": "Bojan Jovanovic", | ||
| "license": "MIT", | ||
| "repository": "https://github.com/noctalia-dev/noctalia-plugins", | ||
| "description": "Search pass (password-store) from the launcher. Type 'pass <name>' to copy a password or 'po <name>' to copy a TOTP code to clipboard.", | ||
| "tags": ["Launcher", "Utility"], | ||
| "entryPoints": { | ||
| "main": "Main.qml" | ||
| }, | ||
| "dependencies": { | ||
| "plugins": [] | ||
| }, | ||
| "metadata": { | ||
| "defaultSettings": {} | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not add your launcher provider this way, create an object called
LauncherProviderand provide that in the manifest itself. The plugins aren't the ones responsible with registering and unregistering their launcher providers from / to the shell.