Skip to content

Add Grabit Launcher plugin - #1399

Open
snowballons wants to merge 6 commits into
noctalia-dev:mainfrom
snowballons:add-grabit-launcher
Open

snowballons wants to merge 6 commits into
noctalia-dev:mainfrom
snowballons:add-grabit-launcher

Conversation

@snowballons

Copy link
Copy Markdown
Contributor

Plugin

  • Id: snowballons/grabit
  • New plugin
  • Update to an existing plugin (version bumped in plugin.toml)

What it does

Grabit Launcher puts grabit's screenshot, screen-recording, OCR, translation, pin, and upload actions one click away in Noctalia. It ships a bar widget (left-click default action, right-click mode menu, middle-click last region, scroll for fullscreen/window), a two-column mode menu, a settings panel, a control-center shortcut, and a click-through recording indicator layer that shows "Recording"/"Paused" with a live elapsed time on every output. The service owns grabit process tracking (PID ownership, pause/resume, foreign-recording guard) and publishes state so the widget, shortcut, and layer stay in sync.

External dependencies

  • grabit — all capture, recording, pause/resume, pin, OCR/translation, and upload work; the plugin also reads and writes grabit's config through its CLI.
  • sleep — the short delay before a capture so a closing panel is not captured.
  • xdg-open — opens the grabit config directory from the settings panel.

No network calls are made by the plugin itself. Uploads happen only inside grabit when the user picks an upload action.

Testing

  • Tested on Niri
  • Tested on Hyprland
  • Tested on Sway
  • Tested on another compositor:
  • Noctalia version tested against: v5.4.1
  • Plugin API level: 42

Exercised on a live Sway session:

  • Recording round-trip several times: start → pending → recording (PID adopted from grabit's lockfile), pause, resume, stop; the mp4 finalized each time.
  • Recording indicator: appears red with "Recording" and an advancing elapsed timer, switches to amber "Paused", and hides on stop.
  • Mode menu and settings panel render. The settings panel reads grabit's current values back; the underlying grabit set/unset calls for also_save, format, filename/filename_preset, recording.fps/recording.format/recording.crf, and translate.target were verified against the binary, including rejection of invalid values.
  • Capture and pin actions: last-region capture verified; pin-grab/pin-release spawns verified; the OCR + translate command was run directly against the binary.
  • The widget renders and consumes the same published status. The control-center shortcut and the middle-click gesture were not interactively exercised (shortcut not placed in the control center; no synthetic pointer input).
  • The pinned shared validator (99a892d, --profile community) reports Validated 1 plugin manifest(s).

Screenshots / Videos

Grabit mode menu

Grabit settings panel

Checklist

Ready-for-review requirement: Every box in this section must be checked. If any statement is not true, keep the
pull request as Draft. An explanation does not replace a required check.

Manifest descriptions and all README prose must be English; no translated sections or localized README copies.

  • The directory name matches the part of id after the / in plugin.toml exactly.
  • It ships plugin.toml, README.md, thumbnail.webp, and translations/en.json.
  • README.md follows the
    README template, documents
    every entry id and dependency, and includes exact panel IPC commands and launcher prefixes where applicable.
  • thumbnail.webp is present and relevant; for a new plugin I created it with the thumbnail generator, and for an update I regenerated it with the generator if the visual identity or user-facing appearance changed.
  • version follows semver and is bumped in this PR; plugin_api is the oldest API level this plugin requires.
  • Every non-English translation in this PR uses a locale supported by Noctalia core, and I can read, write, and
    understand that language well enough to review and maintain it (no unreviewed machine/LLM translations).
  • I did not edit catalog.toml; CI generates it.
  • This PR touches exactly one plugin directory.

Code review attestation

Plugins run as trusted, unsandboxed Luau in the user's session. Confirm:
Ready-for-review requirement: Every attestation below must be checked.

  • The code is readable and not obfuscated, minified, or generated.
  • Code identifiers and code comments are in English.
  • It does not download and execute remote code.
  • Every network call, filesystem write, and spawned process is something the description above accounts for.
  • I have the right to publish this code under the license declared in plugin.toml.

Bar widget, mode menu, settings panel, control-center shortcut, headless
service, and recording indicator layer for grabit screenshots, recording,
OCR and translation, pins, and uploads.
Removed the description section from the README.
@ItsLemmy

Copy link
Copy Markdown
Member
  1. blocking - grabit/settings.luau:170
    The settings panel writes the selected default action to grabit's configuration, but the widget and shortcut continue reading Noctalia's separate default_action setting (grabit/widget.luau:100, grabit/shortcut.luau:36). Selecting Save or Upload here therefore leaves their explicit capture mode unchanged, and reopening restores the old selection. The upload-service field has the same split configuration problem (grabit/settings.luau:181, grabit/service.luau:120). Use one authoritative configuration source consistently across settings and command construction.

  2. blocking - grabit/service.luau:239
    The foreign-recording guard does not protect the start path. Process detection runs only while the plugin is non-idle (grabit/service.luau:398), while startRecording unconditionally launches grabit --record without first checking the existing PID file. The same command is used to stop an existing recording (grabit/service.luau:270). Consequently, choosing Start recording while an externally started recording exists can stop that recording instead. Check for an existing recording before dispatching the toggle and refuse to control recordings the plugin does not own.

@ItsLemmy
ItsLemmy marked this pull request as draft November 12, 2026 03:10
…imit, keep default action and upload service in one config source and never toggle off a recording the plugin did not start - The settings panel resolved every config key at once. At 8 keys it sat

  exactly at the limit, so adding any key pushed the trailing reads onto
  rejected spawns and they rendered blank.
- The reset button unset 12 keys at once, so the trailing keys were never
  unset but still reported success.

Both paths now run one call at a time and only advance from the callback,
so the number of keys no longer matters.                                                                                        grabit's config is now the only source:

- A plain left-click sends mode default, which passes no action flag, so
  grabit applies its own default_action. The menu's explicit entries still
  pass their own flag.
- Uploads no longer append a second --<service> flag. Bare -u already uses
  grabit's service key, so captures and recordings now upload to the same
  destination.
- The panel reads and writes default_action, service, and capture.cursor
  through grabit, and the plugin settings they duplicated are gone.
- Clearing the upload service unsets the key, since grabit rejects an empty
  value.
- The select offers pin, which grabit also accepts as a default action, so
  an existing pin value is no longer shown as copy and overwritten.                                                                                                                             grabit --record toggles on the pid file: when a recording is already live it
stops that one instead of starting a new one. The foreign-recording guard did
not cover the start path, because process detection only ran from update()
while the state was non-idle. Choosing Start recording while an externally
started recording existed therefore stopped it, then reported that the
recording failed to start.

startRecording now checks for a live grabit --record before dispatching and
refuses when one is running. While idle the service owns no pid, so any live
--record at that point is foreign. If process detection is unavailable the
start proceeds as before rather than blocking recording entirely.
@snowballons
snowballons marked this pull request as ready for review November 12, 2026 05:35
@snowballons
snowballons marked this pull request as draft November 12, 2026 05:35
@snowballons
snowballons marked this pull request as ready for review November 12, 2026 05:36
@snowballons

Copy link
Copy Markdown
Contributor Author
  1. Config source: the settings panel wrote default_action/service to grabit's config while the widget and shortcut read the plugin's own settings, so the panel's selections had no effect and reverted on reopen. grabit's config is now the only source; a plain left-click passes no action flag and lets grabit apply its own default_action, and the duplicate --<service> upload flag is gone so captures and recordings use the same uploader.

  2. Recording toggle: grabit --record toggles on the pid file, so choosing "Start recording" while a recording started outside the plugin was running stopped that recording instead. The service now checks for a live grabit --record it does not own and refuses rather than toggling it.

  3. Config reads: the panel fired every grabit set read at once, but Noctalia rejects spawns past a small in-flight limit (8) and arejected spawn looks like an empty value. Reads and the reset button now run one call at a time.

@snowballons snowballons changed the title feat(grabit): add Grabit Launcher plugin Add Grabit Launcher plugin Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants