Research: how should DropTerm V3 build a COSMIC panel applet that toggles the terminal and shows status?

Date: 2026-10-03 Question: What does the COSMIC panel applet platform actually provide, how do first- and third-party applets structure themselves and talk to other processes, what is the industry precedent in quake-style terminals, and which parts should DropTerm adopt?

TL;DR

Ship a separate applet binary (cosmic-ext-dropterm-applet) from the same crate, and give the main app one small custom D-Bus interface on the bus name it already owns. The panel platform is mature and fully documented by source: applets are ordinary processes discovered by desktop-file stem, launched with a Wayland socket fd pointing at the panel, sized via COSMIC_PANEL_SIZE env + PanelSize helpers in libcosmic — and libcosmic at DropTerm’s pinned rev 08a09e3 ships the complete cosmic::applet module (Context, popup helpers, cosmic::applet::run, byte-identical on master as of 2026-10-02). The single-instance seam DropTerm already uses exposes org.freedesktop.DbusActivation with fire-and-forget methods and no properties or signals — which is exactly why --is-visible is stuck — but libcosmic provides a sanctioned hook (ApplicationExt::dbus_connection) documented as “Can be used to expose custom interfaces on the same owned name”. Add Toggle/Show/Hide methods plus a read-only Visible property (PropertiesChanged for free) there; the applet watches the bus name via NameOwnerChanged (the pattern cosmic-applet-status-area and the third-party now-playing applet both use) to switch between live status and a “launch” state. Left-click toggles — the unanimous quake precedent (Yakuake and Guake both default their tray icon on, wired to toggle). Note the honest alternative: a system tray does exist on COSMIC (cosmic-applet-status-area implements the full KDE StatusNotifierWatcher, installed in the default panel config), so an SNI item is a viable smaller sibling — but it cannot show COSMIC-native status styling and is second choice for the ROADMAP item as written.

Findings

1. cosmic-panel: applets are processes discovered by desktop-file stem

From pop-os/cosmic-panel @ master (f4ba4ef, 2026-10-01, cloned 2026-10-03):

2. The desktop-file contract: exact keys and the sizing mechanism

Verified from cosmic-panel wrapper_space.rs L473-514 (all keys the panel reads) and the shipped desktop files in pop-os/cosmic-applets @ master (57ace74, 2026-09-30, cloned 2026-10-03):

Key Read at Meaning
Exec wrapper_space.rs:475 required; Shlex-split, first word = executable
X-CosmicApplet=true not read by the panel marker consumed by cosmic-settings: applets list (cosmic-settings/src/pages/desktop/panel/applets_inner.rs L571) and hidden from startup apps (.../startup_apps.rs L498)
X-HostWaylandDisplay wrapper_space.rs:480 request the privileged security-context socket
X-CosmicShrinkable wrapper_space.rs:490-493 padding may shrink before overflow
X-OverflowMinSize / X-CosmicOverflowMinSize wrapper_space.rs:481-485 min size (applet units) before the panel overflows the applet into its popup
X-OverflowPriority / X-CosmicOverflowPriority wrapper_space.rs:486-489 who overflows first when the panel is full
X-CosmicHoverPopup wrapper_space.rs:509-511 panel may open the applet’s popup on hover/press; value is a placement anchor (AppletAutoClickAnchor: Auto/Left/Right/Top/Bottom/Center/Start/End, panel_space.rs L150-160)
X-MinimizeApplet wrapper_space.rs:495-507 minimize-to-dock participant (one per panel)
X-NotificationsApplet wrapper_space.rs:513-514 notification fd participant

Real example — cosmic-applet-audio/data/com.system76.CosmicAppletAudio.desktop: X-CosmicApplet=true, X-CosmicShrinkable=true, X-CosmicHoverPopup=End, X-OverflowPriority=10, NoDisplay=true, Icon=...-symbolic, StartupNotify=true.

Sizing: there is no X-CosmicApplet-Size key. Size hints flow at runtime: panel → COSMIC_PANEL_SIZE (RON PanelSize) → libcosmic applet::Context → PanelSize::get_applet_icon_size/padding/shrinkable_padding (cosmic-panel-config/src/panel_config.rs L143-227). Concrete numbers: symbolic icon sizes XS=16, S=20, M=28, L=32, XL=48 (Custom s/2); symbolic padding 8/10/14/16/16; shrinkable padding 12/14/18/20/20. Applets call core.applet.suggested_size(true) and never hardcode pixels — that is the entire size-hint mechanism.

3. libcosmic applet support at DropTerm’s pinned rev

Verified in the local checkout of [pop-os/libcosmic @ 08a09e3] (https://github.com/pop-os/libcosmic/tree/08a09e3dfa1f729d52585fcedd6e731130235363) (DropTerm pins this rev — Cargo.toml):

4. Canonical applet anatomy and the IPC patterns applets use

From pop-os/cosmic-applets @ 57ace74:

5. A system tray exists on COSMIC (2026 state)

cosmic-applet-status-area in pop-os/cosmic-applets @ 57ace74 implements the full KDE StatusNotifierItem host stack:

So SNI is not missing; it is simply not the native panel surface. A panel applet remains the only route with COSMIC-native theming, sizing, and popup behavior — but a tray icon is a legitimate low-effort complement.

6. libcosmic single-instance D-Bus API — exact surface and the sanctioned extension hook

Verified at the pinned rev 08a09e3 (byte-identical on master 5a8bd94):

Conclusion for the ROADMAP’s deferred note: correct — nothing queryable exists today; a custom interface is required, and it is explicitly supported.

7. Third-party applets: structure and cross-process control

The ecosystem hub is cosmic-utils (org + project collection, fetched 2026-10-03; ~50 applets listed). Verified examples, cloned 2026-10-03:

No third-party applet was found that embeds its target app’s UI — cross-process control is always D-Bus/launch. That matches the platform’s shape: an applet is its own libcosmic process.

8. Quake-terminal precedent: tray icons are the norm

9. GNOME ddterm: the shell-extension contrast, and the D-Bus shape DropTerm needs

ddterm/gnome-shell-extension-ddterm @ master (2026-10-01) is the maintained GNOME drop-down terminal: the GNOME Shell itself hosts the UI (JavaScript extension + a VTE app process), and control is exposed three ways (docs/CommandLine.md): a CLI tool, gapplication action … show/hide/toggle, and a dedicated D-Bus interface. The last one is the blueprint — data/com.github.amezin.ddterm.Extension.xml:

The contrast DropTerm must respect: COSMIC has no shell extensions — the sanctioned equivalent of “integrate with the shell chrome” is a panel applet (finding 1-3), and the sanctioned equivalent of the extension’s queryable control interface is a custom interface on the app’s owned bus name (finding 6). ddterm shows both halves done well.

What applies to DropTerm

  1. Same crate, second [[bin]]: cosmic-ext-dropterm-applet. Precedent: pop-os keeps every applet a separate crate in a workspace; third parties ship standalone crates; mare-player proves dual-targeting one codebase is fine — but via a compile-time feature, which cannot run app and applet concurrently. DropTerm needs both alive at once (app owns the layer surface; applet lives in the panel), so two process entry points are required. A cosmic-ext-dropterm applet subcommand would also work (the panel Shlex-splits Exec=cosmic-ext-dropterm applet), but it drags applet mode into the user-facing clap CLI whose default subcommand is toggle; a dedicated bin target is cleaner, matches the stem-matching identity scheme, and keeps logs unambiguous. The applet bin gets its own APP_ID (io.github.l1br3_io.CosmicExtDropterm.Applet), its own desktop file, and calls cosmic::applet::run — never run_single_instance, which would just forward-activate the terminal and exit, leaving a blank panel slot.
  2. Control channel: keep activate_action for commands, add one tiny custom interface for state. Toggle/show/hide/new-tab already ride org.freedesktop.DbusActivation.activate_action — the applet reuses them verbatim (same messages the CLI produces, so one code path). For status, implement in the app:
    • bus name io.github.l1br3_io.CosmicExtDropterm (already owned),
    • path /io/github/l1br3_io/CosmicExtDropterm (same object as the activation interface),
    • interface e.g. io.github.l1br3_io.CosmicExtDropterm.V1 served from the ApplicationExt::dbus_connection hook (zbus #[interface]; object_server().at() on the handed-over connection),
    • methods Toggle(), Show(), Hide(), NewTab(cwd, cmd) (mirroring the CLI — convenient for non-libcosmic callers; optional if activate_action suffices),
    • read-only properties Visible: bool, TabCount: u32, BellPending: bool — zbus emits PropertiesChanged automatically when properties mutate, which is the VisibilityChanged signal the ROADMAP sketched, for free. Effort S-M: zbus 5 is already in the tree; the hook exists; it’s one file plus property updates on show/hide/tab/bell transitions.
  3. --is-visible unblocks for free: read Visible off the proxy; if the bus name has no owner, the app isn’t running → print false / exit non-zero. Effort S.
  4. Applet runtime state: subscribe to NameOwnerChanged filtered on io.github.l1br3_io.CosmicExtDropterm (status-area unique_names.rs / now-playing media.rs pattern, both zbus 5). Not running → dimmed button, click spawns the CLI binary with an activation token obtained via applet-token (time-applet pattern) so the summoned terminal is properly activated. Running → reflect Visible; left-click calls Toggle. Optional popup (popup_container) with New Tab / Settings / Quit, right-click or a small popup button — but keep V1 minimal: an icon button that toggles, a tooltip, done.
  5. Desktop file for the applet (keys from finding 2): Exec=cosmic-ext-dropterm-applet, X-CosmicApplet=true, NoDisplay=true, Icon=io.github.l1br3_io.CosmicExtDropterm-symbolic (ship a symbolic SVG to icons/hicolor/scalable/apps/; a generic fallback exists — utilities-terminal-symbolic is in Pop’s theme, Pop/scalable/apps/, verified in the pop-os/icon-theme tree 2026-10-03). Skip X-HostWaylandDisplay (DropTerm needs no privileged socket), set X-CosmicShrinkable=true. Install desktop file to share/applications, icon to share/icons/hicolor/... (cosmic-applets justfile L37-51 pattern). Effort S.
  6. Applet build config: enable libcosmic applet + applet-token features for the applet bin only. Do not enable wgpu for the applet (mare-player’s documented llvmpipe fallback on layer-shell surfaces); tiny_skia is what stock applets use. Effort M overall for the applet UI (Context-driven sizing, popup, tooltip, state machine, tests for the pure logic).
  7. Licensing/packaging: everything verified compatible — libcosmic MPL-2.0, ksni Unlicense, applets typically GPL-3.0-only like DropTerm. No new copyleft obligations.

Status content worth showing in a ~20-32px slot: running/not (dim), visible/hidden (the Yakuake/Guake tray parity), tab count as a small badge or in the tooltip, and bell/attention state if the existing notification path exposes it — the now-playing applet proves text+icon is possible, but V1 should be icon + tooltip (applet_tooltip) with the popup carrying anything richer. Visible and running-state are the two with primary-source precedent (ddterm HasWindow; Yakuake tooltip).

Risks / open questions

Sources