Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions password-store/LauncherProvider.qml
Original file line number Diff line number Diff line change
@@ -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 {}
}
}
105 changes: 105 additions & 0 deletions password-store/README.md
Original file line number Diff line number Diff line change
@@ -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 <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
```

## 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 <entry>` and `pass otp -c <entry>`, 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 <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

```
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 <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)
20 changes: 20 additions & 0 deletions password-store/manifest.json
Original file line number Diff line number Diff line change
@@ -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 <name>' to copy a password or 'po <name>' to copy a TOTP code to clipboard.",
"tags": ["Launcher", "Utility"],
"entryPoints": {
"launcherProvider": "LauncherProvider.qml"
},
"dependencies": {
"plugins": []
},
"metadata": {
"defaultSettings": {}
}
}