Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/roundtrip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ jobs:
-DRIVET_RACKET_LIBRARY="$RIVET_RACKET_LIBRARY"
cmake --build "$native_build" --config Release
"$native_build/RivetThemeTest"
dbus-run-session -- "$native_build/RivetGlobalShortcutsTest"
cp "$native_build/RivetIntegration" "$stage/RivetIntegration"
chmod +x "$stage/RivetIntegration"

Expand Down
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
state-aware tray apps no longer re-allocate the icon to relabel it and no
longer lose it the first time the shell restarts. `TrayIcon` owns an icon
copy so recovery does not impose a new lifetime requirement on callers.

- Add Wayland-safe Linux global shortcuts through the XDG Desktop Portal,
including asynchronous consent/binding results, compositor activation tokens,
runtime capability discovery, and a hermetic mock-portal integration test.
Rivet deliberately provides no X11 `XGrabKey` fallback.
- Bound and retry Ubuntu runner package-manager operations so a stalled mirror
cannot consume an entire desktop validation job without a useful failure.
- Removed the misleading `rivet-type` runtime descriptors from the pre-1.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ See [architecture](docs/architecture.md), [API stability](docs/api-stability.md)
| `new` / `doctor` / `build` / `dev` | ✅ | ✅ | ✅ |
| `package` / `verify` | ✅ dependency audit | ✅ signing/rpath/plist audit | ✅ directory + `ldd` audit |
| Production signing / installer | ✅ Authenticode + MSI | ✅ Developer ID + DMG | ✅ signed tarball + deb/rpm/AppImage |
| System services / secure storage | ✅ | ✅ | ✅ Linux adapter + StatusNotifierItem tray |
| System services / secure storage | ✅ | ✅ | ✅ Linux adapter + portal global shortcuts + StatusNotifierItem tray |
| Real embedded-runtime CI | ✅ | ✅ | ✅ |

Windows, macOS, and Linux are production release targets. Linux qualification
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Racket CS 运行在独立 runtime 线程。原生 UI 代码不会直接操作 Ra
| `new` / `doctor` / `build` / `dev` | ✅ | ✅ | ✅ |
| `package` / `verify` | ✅ DLL 依赖审计 | ✅ 签名/rpath/plist | ✅ 目录 + `ldd` 审计 |
| 生产签名 / 安装包 | ✅ Authenticode + MSI | ✅ Developer ID + DMG | ✅ 签名 tarball + deb/rpm/AppImage |
| 系统服务 / 安全存储 | ✅ | ✅ | ✅ Linux 适配器 + StatusNotifierItem 托盘 |
| 系统服务 / 安全存储 | ✅ | ✅ | ✅ Linux 适配器 + Portal 全局快捷键 + StatusNotifierItem 托盘 |
| 真实嵌入运行时 CI | ✅ | ✅ | ✅ |

Windows、macOS 与 Linux 都是生产发布目标。Linux 资格门禁使用原生
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ The Swift implementation is intentionally not the framework's specification. `do
The GTK4 host follows the Windows C++ contract but uses a Unix `socketpair` for the in-process RVT1 transport. Racket CS is linked statically, while the UI remains an ordinary GTK4 application. Backend completions are returned to GLib's main loop before widgets are touched.

Linux system integration is split by operating-system responsibility under
`platform/linux/system`: single-instance activation, notifications, tray,
autostart, secure storage, and crash/shutdown each have one implementation
`platform/linux/system`: single-instance activation, portal global shortcuts,
notifications, tray, autostart, secure storage, and crash/shutdown each have one implementation
unit. `system_services.cpp` only assembles public capability discovery and
activation arguments; `system_services_internal.*` is a private XDG, D-Bus,
and low-level I/O support layer. Native applications continue to include only
Expand Down
12 changes: 10 additions & 2 deletions docs/linux-display.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ XDG portal or compositor protocol and must report capability absence honestly.
Applications must not silently fall back to broad X11 privileges.

StatusNotifierItem tray integration remains available where a desktop hosts
it. Global hotkeys, always-on-top overlays, and other compositor-specific
features are not implied by the GTK4 host baseline.
it. Global hotkeys use `org.freedesktop.portal.GlobalShortcuts`: the runtime
reports portal availability and version, and the compositor retains consent
and final-binding authority. There is deliberately no `XGrabKey` fallback.
On a compositor without the portal, an application can expose a command such
as `--toggle` through Rivet's existing single-instance activation route and
let the user assign that command in compositor settings. Always-on-top
overlays and other compositor-specific features are not implied by the GTK4
host baseline.

## CI contract

Expand Down Expand Up @@ -73,6 +79,8 @@ resource payload, graceful SIGTERM handling, and complete metadata removal.
The Linux integration runner separately exercises single-instance forwarding,
autostart persistence, crash/shutdown hooks, and capability-conditioned
notifications, StatusNotifierItem tray, and Secret Service storage.
The GlobalShortcuts adapter is additionally exercised against a deterministic
mock portal on a private D-Bus session, including activation-token delivery.

Fedora advances on its roughly thirteen-month upstream lifecycle; CI pins a
specific maintained release instead of using `fedora:latest`. Ubuntu LTS and
Expand Down
3 changes: 2 additions & 1 deletion docs/system-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ macOS asks the user for notification and login-item consent as required by the O
- an abstract-namespace Unix socket for the single-instance lease; secondaries forward their activation arguments to the primary over that socket;
- `ActivationArguments()` from `/proc/self/cmdline` for URL-scheme and file-association payloads registered by installers;
- `org.freedesktop.Notifications` over the session bus (GDBus), with stable notification ids for tagged replacements;
- `GlobalShortcuts` — the asynchronous `org.freedesktop.portal.GlobalShortcuts` session API. `support()` exposes the portal version, `bind(...)` reports bound, cancelled, unavailable, and failed outcomes without blocking GTK's main context, and activation callbacks preserve the compositor timestamp and activation token. The compositor owns the final shortcut and may replace or reject an application's preferred trigger. Rivet does not fall back to `XGrabKey`; applications on a session without this portal can expose their existing single-instance activation command (for example, an executable `--toggle` action) for the user to bind in compositor settings;
- `TrayIcon` — org.kde.StatusNotifierItem plus a com.canonical.dbusmenu menu, through the session bus (GDBus). This is an explicit opt-in, not an adapter default: desktop hosting is compositor-dependent (GNOME hosts it only through the AppIndicator extension, KDE natively), so the application checks `TrayIcon::available()` (or the `tray` capability) and decides. The icon, tooltip, and flat menu (labelled items, separators, enabled state) update in place; the watcher is re-registered whenever it reappears, so GNOME Shell restarts and extension reloads do not strand a running process without its icon. Methods and menu callbacks run on the thread-default main context the icon was constructed on — for GTK hosts, the main-loop thread;
- XDG autostart entries under `$XDG_CONFIG_HOME/autostart`;
- the Secret Service provider (GNOME Keyring, KWallet bridge) through libsecret for binary secrets; the capability is compiled in when libsecret is present and reported at runtime only when a provider is reachable;
Expand All @@ -59,7 +60,7 @@ break while the GTK main loop remains alive. Generated hosts and the reference
Taskboard enforce this ordering and the deb lifecycle gate sends a real
`SIGTERM` to the installed application.

The Linux integration binary self-checks the adapter (`RivetIntegration --system`): lease acquisition and activation forwarding, autostart entries, and the crash hook always run; notification, tray, and secure-storage checks skip themselves when the session lacks those services.
The Linux integration binary self-checks the adapter (`RivetIntegration --system`): lease acquisition and activation forwarding, autostart entries, and the crash hook always run; notification, tray, and secure-storage checks skip themselves when the session lacks those services. A separate hermetic test starts a private D-Bus session and mock GlobalShortcuts portal, then verifies discovery, session creation, binding results, and activation delivery without depending on a particular CI desktop.

## Settings, logs, and crashes

Expand Down
1 change: 1 addition & 0 deletions examples/taskboard/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ add_executable(RivetHost
"${RIVET_ROOT}/platform/linux/runtime/backend.cpp"
"${RIVET_ROOT}/platform/linux/system/autostart.cpp"
"${RIVET_ROOT}/platform/linux/system/crash_shutdown.cpp"
"${RIVET_ROOT}/platform/linux/system/global_shortcuts.cpp"
"${RIVET_ROOT}/platform/linux/system/notifications.cpp"
"${RIVET_ROOT}/platform/linux/system/secure_storage.cpp"
"${RIVET_ROOT}/platform/linux/system/single_instance.cpp"
Expand Down
14 changes: 14 additions & 0 deletions platform/linux/Integration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ add_executable(RivetIntegration
../runtime/backend.cpp
../system/autostart.cpp
../system/crash_shutdown.cpp
../system/global_shortcuts.cpp
../system/notifications.cpp
../system/secure_storage.cpp
../system/single_instance.cpp
Expand All @@ -49,6 +50,19 @@ target_include_directories(RivetThemeTest PRIVATE ../theme)
target_compile_options(RivetThemeTest PRIVATE -Wall -Wextra -Wpedantic)
target_link_libraries(RivetThemeTest PRIVATE PkgConfig::GTK4)

add_executable(RivetGlobalShortcutsTest
global_shortcuts_test.cpp
../system/global_shortcuts.cpp
../system/system_services_internal.cpp
)
target_compile_features(RivetGlobalShortcutsTest PRIVATE cxx_std_17)
target_include_directories(RivetGlobalShortcutsTest PRIVATE ../system)
target_compile_options(RivetGlobalShortcutsTest PRIVATE -Wall -Wextra -Wpedantic)
target_link_libraries(RivetGlobalShortcutsTest PRIVATE
Threads::Threads
PkgConfig::GLIB
)

if(LIBSECRET_FOUND)
target_compile_definitions(RivetIntegration PRIVATE RIVET_HAVE_LIBSECRET=1)
endif()
Expand Down
Loading
Loading