diff --git a/password-store/LauncherProvider.qml b/password-store/LauncherProvider.qml new file mode 100644 index 000000000..a82152a68 --- /dev/null +++ b/password-store/LauncherProvider.qml @@ -0,0 +1,82 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +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 {} + } +} diff --git a/password-store/README.md b/password-store/README.md new file mode 100644 index 000000000..35afcf759 --- /dev/null +++ b/password-store/README.md @@ -0,0 +1,105 @@ +# 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 ` to fuzzy search your password store and copy a password to clipboard +- **OTP search** — type `po ` 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 +``` + +## gopass + +If you use [gopass](https://github.com/gopasspw/gopass) instead of pass, you can make this plugin work by symlinking `gopass` as `pass`: + +```bash +ln -s $(which gopass) ~/.local/bin/pass +``` + +The plugin calls `pass -c ` and `pass otp -c `, which gopass understands. Make sure `~/.local/bin` is on your `$PATH`. + +You also need to point the plugin at gopass's store by setting `$PASSWORD_STORE_DIR`: + +```bash +export PASSWORD_STORE_DIR="$HOME/.local/share/gopass/stores/root" +``` + +Add this to your shell profile (`.bashrc`, `.zshrc`, etc.) so it persists. + +## Installation + +Install directly from the Noctalia plugin manager in Settings, or clone manually: + +```bash +git clone https://github.com/noctalia-dev/noctalia-plugins ~/.config/noctalia/plugins/password-store +``` + +## Usage + +Open the launcher (`Alt+Space` by default) and type: + +| Prefix | Action | +|--------|--------| +| `pass ` | Fuzzy search all pass entries — press Enter to copy the password | +| `po ` | 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 ` or `pass otp -c ` respectively. If your GPG key requires a passphrase, a pinentry dialog will appear. + +## File Structure + +``` +password-store/ +├── manifest.json # Plugin metadata +├── LauncherProvider.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 ` + +**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) diff --git a/password-store/manifest.json b/password-store/manifest.json new file mode 100644 index 000000000..da0cd0fe7 --- /dev/null +++ b/password-store/manifest.json @@ -0,0 +1,20 @@ +{ + "id": "password-store", + "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 ' to copy a password or 'po ' to copy a TOTP code to clipboard.", + "tags": ["Launcher", "Utility"], + "entryPoints": { + "launcherProvider": "LauncherProvider.qml" + }, + "dependencies": { + "plugins": [] + }, + "metadata": { + "defaultSettings": {} + } +}