Ghost Assembly QuickClip

GNOME Shell extension

QuickClip

A private clipboard history in GNOME Quick Settings.

Keeps your last copies — text and images — in memory only, skips copies that KeePassXC and other password managers mark as secret, and turns JSON, base64, URLs and timestamps into what you need. Super+Shift+V opens it from the keyboard.

  • GNOME Shell 50
  • Memory only
  • GPL-3.0-or-later

Install Source on GitHub

On this page
  1. 01Overview
  2. 02Install
  3. 03Privacy
  4. 04Transforms
  5. 05Preferences
  6. 06Keyboard
  7. 07Architecture
  8. 08Testing
  9. 09Packaging
  10. 10Releasing
  11. 11Development

01

Overview

QuickClip adds one tile to Quick Settings and one popup under the keyboard. Both show the same history: your recent copies, kept in the Shell's memory and never written to disk except for snippets you explicitly pin.

One tile, one menu

Click the tile to pause or resume recording; its subtitle reads “Recording” or “Paused”. The menu holds Current (with a Transform submenu), Pinned, Recent, Clear history and Preferences.

Text and images

Plain text and PNG screenshots are recorded. File managers usually offer copied files as plain text too, so a copied file is recorded as the text of its path. A rich format with no plain-text or PNG fallback is left on the clipboard, untouched.

Protected copies show, not tell

A skipped copy leaves a row in its place — Sensitive copy skipped, Copy in an ignored app skipped, or Too large to keep — so protection stays visible without ever showing what was blocked.

Memory only

Nothing you copy reaches disk, a log or a notification. The one exception is text you pin, stored in GSettings — and only because you asked.

02

Install

Requires

  • GNOME Shell 50.
  • A password manager that sets x-kde-passwordManagerHint on what it copies, for automatic skipping. KeePassXC and other KDE-convention managers do; without one, use Ignored apps in Preferences instead.

From a release

Shell
$ curl -LO https://github.com/Ghost-Assembly/quickclip/releases/latest/download/[email protected]
$ gnome-extensions install --force [email protected]
$ gnome-extensions enable [email protected]

From a clone

Shell
$ just setup
$ just install
$ just enable

Log out and back in. A newly installed extension is only picked up when the Shell next starts. On Wayland that means a new session.

03

Privacy

Everything QuickClip keeps lives in the Shell's memory for as long as the session runs. The one exception is a snippet you pin — the only clipboard content ever written to disk, in GSettings (dconf), and only because you asked.

What's skipped

  • Password manager copies. A copy carrying the x-kde-passwordManagerHint MIME type — set by KeePassXC and other apps that follow the KDE convention — is never read, so its content never reaches QuickClip's memory. A manager that does not set this hint, such as some Electron-based ones, is not detected this way; add it to Ignored apps in Preferences instead.
  • Ignored apps. Wayland does not say which application made a copy, so QuickClip uses whichever app is focused at the moment of the copy — which makes this check approximate, not exact.
  • Pausing. Click the tile, or use the pause shortcut, to stop new copies being recorded until you resume.
  • Expiry. Set Expire after in Preferences and a copy older than that is dropped. QuickClip keeps a single timer for whichever item is due next, and none at all while the history is empty. That timer stands still while the computer sleeps, so expired copies are also dropped whenever the menu or the popup opens, and on unlock.

The lock screen

QuickClip declares the unlock-dialog session mode so its history can survive a screen lock — but only when Clear on lock is turned off. Either way, while the screen is locked there is no tile, no popup, no keybinding and nothing is recorded. With Clear on lock on (the default), the history empties the moment the screen locks; with it off, the history stays in memory across the lock and is there again on unlock.

Never logged, never notified. Log lines carry only the kind of copy or the reason it was skipped — never its content — and no notification ever quotes what you copied. Only a pin persists, and only in GSettings.

Freeing memory. Dropping an item, clearing the history, or disabling the extension only drops QuickClip's own reference to the text. JavaScript engines are free to keep that string's memory until garbage collection runs, so a very sensitive copy may stay reachable in memory slightly longer than the history shows it.

04

Transforms

Every transform is offered from Current's Transform submenu, or with Tab in the popup, whenever the copy on top qualifies. Two need no input at all and are always offered: they generate a fresh value instead of changing the copy.

Transform Example Offered when
Pretty-print JSON {"a":1} → the same, indented two spaces per level The copy parses as JSON
Minify JSON { "a": 1 } → {"a":1} The copy parses as JSON
Base64 encode hello → aGVsbG8= Any non-empty copy
Base64 decode aGVsbG8= → hello The copy looks like base64 and decodes to text
URL encode a b/c → a%20b%2Fc Encoding would change the copy
URL decode a%20b → a b The copy contains %XX escapes
Trim whitespace   hi   → hi Leading or trailing whitespace
Collapse whitespace a   b → a b Runs of whitespace, tabs or newlines
UPPER CASE Hi → HI The copy has lower-case letters
lower case Hi → hi The copy has upper-case letters
snake_case helloWorld → hello_world A single-line identifier under 200 characters
kebab-case helloWorld → hello-world A single-line identifier under 200 characters
Unix time → ISO date 1700000000 → 2023-11-14T22:13:20.000Z A 10- or 13-digit Unix timestamp
ISO date → Unix time 2023-11-14T22:13:20Z → 1700000000 An ISO date or date-time
New UUID generates 0b2b6b0e-…-… Always — a generator, ignores the copy
Current time (ISO) generates the current time, e.g. 2026-09-26T18:02:00.000Z Always — a generator, ignores the copy

A transform works only on a copy of up to 100,000 characters; a longer copy is offered only the generators, which ignore it. A date-time with no time zone is read as local time, the same as your system clock, not UTC.

05

Preferences

Open them from the Extensions app, or with just prefs from a clone. Recording is paused and resumed from the tile or the pause shortcut, not from this window; the state is remembered between sessions.

Setting Default Notes
History size 20 5 to 100. Oldest copies are dropped first.
Image memory (MB) 32 0 to 256. Memory copied images may use together; 0 keeps no images.
Expire after (minutes) 30 0 to 1440. 0 keeps a copy until it is pushed out by History size.
Clear on lock On Empty the history when the screen locks.
Paste on select On Paste the chosen item into the window that had focus.
Open the popup Super+Shift+V A conflicting shortcut is refused, never overwritten.
Pause or resume Not set Same conflict check as the popup shortcut.
Ignored apps None Copies made while one of these is focused are skipped.
Terminal apps Ptyxis, GNOME Console, GNOME Terminal, kitty, Alacritty, foot, WezTerm, Ghostty These paste with Ctrl+Shift+V instead of Ctrl+V.
Pinned snippets None The only clipboard content stored on disk; kept until you unpin it.

Setting a shortcut is checked against GNOME's own bindings (the window manager, the Shell, mutter, media keys and any custom shortcut) and QuickClip's other shortcut. A conflicting choice is refused, with a toast naming what already uses it — never silently overwritten. Other extensions' shortcuts are not visible to this check, so a conflict with one of those is not caught here.

06

Keyboard

Super+Shift+V opens the popup over any application window. It does nothing in the Activities overview or while the screen is locked.

Input Does
Super+Shift+V Open the popup
Typing Filter the list by matching text
↑ / ↓ Move the selection, wrapping at both ends
Enter Copy the selection and, after a short delay, paste it into the window that had focus
Tab Switch to the Transform list for the selection; Tab again returns
Esc Leave the Transform list, or close the popup — twice from Transform view

Auto-paste sends Ctrl+Shift+V to the apps listed in Terminal apps — Ctrl+V would just insert a literal “v” there — and Ctrl+V to everything else, after a short delay so the previous window has focus back first.

Clicking an item in the Quick Settings tile's menu copies it to the clipboard but never pastes it — pasting only happens from the keyboard popup, and only when Paste on select is on. Super+V is untouched: it still opens GNOME's own notification list.

07

Architecture

Every decision — what to record, what a transform does, what a shortcut may take — lives in a module that imports no GNOME API, only other modules like it, so the suite can test it whole on plain Node.

Module Job Deps
extension.js The entry point, deliberately thin: builds ClipboardSource, Paster and QuickClip on enable and tears them down on disable. Nothing runs at import time. GLib, Shell extensions base
modules/model.js History: add/dedupe, size and image-budget caps, expire(), nextExpiry(), clear, change callbacks none (pure)
modules/transforms.js The {id, label, applies(text), run(text)} list; uuid and clock injected model.js (pure)
modules/privacy.js shouldRecord({mimetypes, appId, paused, ignoredApps, imagesAllowed}) → {record, kind} or {record, reason} model.js (pure)
modules/listing.js Row text and previews, the popup filter, which text can be pinned model.js, privacy.js, transforms.js (pure)
modules/accel.js Accelerators compared as GNOME matches them; conflicts; Shift-only refusal none (pure)
modules/settings.js The key list and its wording; a watcher that releases what it connects none (pure)
modules/recorder.js Clipboard change → privacy check → read → History; drops superseded reads; the one expiry timer model.js, privacy.js, settings.js (no GNOME API)
modules/clipboard.js Meta.Selection owner-changed (CLIPBOARD) → read via St.Clipboard; write text/image GLib, Meta, Shell, St
modules/paste.js Virtual keyboard device; Ctrl+V / Ctrl+Shift+V by focused app id Clutter, GLib
modules/controller.js Builds the pieces; lock handling, keybindings and every action a click or key asks for Meta, Shell, Shell UI
modules/panel.js QuickMenuToggle: subtitle Recording/Paused; menu Current (+transform row), Pinned, Recent, Clear history, Preferences Shell UI
modules/popup.js Modal list: Pinned then Recent; type-to-filter text; ↑/↓, Enter = copy+paste, Tab = transform row, Esc Shell UI
prefs.js Adw preferences window, in its own process Adw, Gtk, Gdk, Gio

Two files are excluded from coverage, both toolkit plumbing a unit test could only assert against a stub of the toolkit: prefs.js (Adw and Gtk widget construction — the key list, wording and shortcut rules it uses live in modules/settings.js and modules/accel.js, tested directly) and modules/clipboard.js (St.Clipboard and Meta.Selection calls, checked against a real Shell by scripts/headless-check.sh instead). See the security policy.

08

Testing

The unit suite runs on Node with the Shell's modules replaced by recording stubs. just test-live adds what CI cannot have: it boots a headless GNOME Shell, enables QuickClip, and checks that it starts listening and survives a disable and re-enable with no JavaScript errors or leaked signal or timeout sources. When a virtual clipboard can own the selection in that headless session it also checks that a plain-text copy is recorded and a password-manager copy is skipped; a headless Shell has no real seat, so those two checks are sometimes skipped there and covered by the manual checklist below instead.

Manual checklist

Run through this in a real session before a release; nothing here can run in CI, which has no GNOME Shell.

  • The tile shows “QuickClip / Recording”; clicking it shows “Paused”; a copy while paused is not listed; clicking again resumes.
  • Copy text in a GTK app → it appears under Recent and as Current.
  • Copy a password from KeePassXC (if installed) → a “Sensitive copy skipped” row, and nothing new under Recent.
  • Add the focused app to Ignored apps in Preferences, copy from it → “Copy in an ignored app skipped”.
  • Take a screenshot to the clipboard → an Image row with a thumbnail and size.
  • Super+Shift+V opens the popup; typing filters; ↑/↓ move; Enter pastes into the previous window (a GTK app takes Ctrl+V; Ptyxis takes Ctrl+Shift+V). Check Ghostty too, if it is available.
  • Tab shows transforms for {"a":1}; Enter on Pretty-print JSON pastes the pretty result.
  • Copy %E0%A4%A and choose URL decode → a notification reads “URL decode did not apply — Not valid URL encoding”, quoting nothing from the clipboard, and the clipboard is unchanged. No notification ever quotes clipboard text.
  • Pin from Recent; the snippet appears under Pinned and in Preferences; unpin works from both.
  • Set expiry to 1 minute; a copy disappears after about a minute.
  • Lock the screen: the lock screen's Quick Settings has no QuickClip tile; Super+Shift+V does nothing; unlock → history empty (Clear on lock on).
  • Turn Clear on lock off, copy something, lock, unlock → it is still listed; copies made while locked are not.
  • Super+V still opens GNOME's notification list.
  • In Preferences, choose Set… for the popup shortcut: the Shell asks whether to allow the app to inhibit shortcuts. Allow it, then press Super+V → the message tray stays shut and the shortcut is refused with a toast naming toggle-message-tray.
  • just logs shows only [quickclip] lines with kinds and reasons — no clipboard content.

09

Packaging

just build makes the zip with plain zip, because gnome-extensions ships inside the gnome-shell package and CI has no GNOME. scripts/pack-check.sh keeps the official tool as the authority, diffs the two listings and confirms every shipped icon decodes — an icon that fails to load is silent.

The zip holds metadata.json, extension.js, prefs.js, modules/, schemas/, icons/ and stylesheet.css, which styles the tile and menu beyond what the Shell already styles. The uuid is written down once, in metadata.json, and everything else reads it from there.

10

Releasing

Set the version in metadata.json and package.json, commit, tag and push. The release workflow checks the tag against both files before building.

Shell
$ git tag -a v0.1.0 -m 'release v0.1.0'
$ git push origin v0.1.0

11

Development

justfile
just              # list every recipe
just test         # unit suite
just test-docs    # this site, in Chromium and Firefox
just lint         # eslint, prettier, gschema, shellcheck
just ci           # what CI runs: lint, tests, test-docs, security, build
just test-live    # headless gnome-shell smoke test, then the packed zip
just coverage     # unit suite with a coverage report
just docs         # serve this site on localhost:8000

Every decision lives in a module that imports no GNOME API, so the unit suite tests it on plain Node; see Architecture above.