gui: floating drop target, clipboard global shortcut, theming, UI watchdog
Continues the build order past Options/Scheduler/Speed Limiter/Batch/ Grabber/tray. - DropTargetWidget: frameless always-on-top drop target (docs/03-gui-spec.md §5), position persisted, accepts a dropped http(s) URL or link text and opens FileInfoDialog directly (skipping Add URL, since the URL is already known). Shown/hidden from general.showDropTarget, live via event.settings.changed, same pattern MainWindow already used for general.minimizeToTray. - Clipboard, explicit path #2 (docs/06-risks-and-spikes.md R2): GlobalShortcut wraps org.freedesktop.portal.GlobalShortcuts (CreateSession -> BindShortcuts -> Activated), triggering the same Add URL flow. Guarded end-to-end on `if(TARGET Qt6::DBus)` / VELOX_GUI_HAVE_DBUS so a build without the component degrades to "feature skipped," not broken (gui/docs/pkg-qa-requests-m1.md R4). Best-effort by design per the risk doc: fails silent, never advertised. Verified live against the real portal (a real Wayland session, not just offscreen): `CreateSession` refuses every caller with "An app id is required" — reproduced identically via a bare `busctl` call with no Qt involved at all, so this is the portal requiring a sandboxed caller identity, not something fixable from an unconfined process. Recorded as a partial Spike S2 answer in docs/06-risks-and-spikes.md: this explicit path likely doesn't work for Velox as a traditionally-packaged app on stock GNOME, only if/when it ships confined. Also fixed a real leak this verification caught: QDBusInterface's introspection cache reads as a LeakSanitizer leak the first time anything touches D-Bus (tst_rtl went red under ASan) — switched to QDBusMessage::createMethodCall, which needs no introspection. - Theming (docs/03-gui-spec.md §7): gui/resources/qss/{idm-like,dark}.qss, each with a documented palette block up top (QSS itself has no variable syntax), applied by ThemeManager and kept live via QStyleHints::colorSchemeChanged. util/Theme.hpp gives the handful of inline C++ styles (status dot, offline banner, the eleven identical error-label styles across dialogs) named constants instead of a twelfth copy of the same hex. - UiThreadWatchdog: the M1 DoD's 200 ms debug-build watchdog. A background std::thread pings the UI thread every 50 ms via a queued invokeMethod and warns once (not per-poll) if a ping goes unanswered past 200 ms; no QThread, no Qt event loop of its own, so the watchdog itself can never be what blocks the thread it watches. No-op in a release build. Proven both ways in tst_uithreadwatchdog: fires on a genuinely blocked UI thread (synchronous sleep, no processEvents) and stays silent on a responsive one. Full non-conformance suite (55 tests across every lane, `ctest -LE conformance`) passes clean at this point, including the whole gui label under ASan+UBSan. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NSCdCWFXBTSBBK3MzWtJiC
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
// Explicit clipboard capture, path #2. Lane GUI.
|
||||
//
|
||||
// docs/06-risks-and-spikes.md R2: a Wayland client cannot passively observe clipboard
|
||||
// changes made by other applications — not a bug, a deliberate security property, and
|
||||
// the mechanism IDM's clipboard capture relies on does not exist here. The ship-regardless
|
||||
// design has three *explicit* paths instead; this is the second one — a global shortcut
|
||||
// via org.freedesktop.portal.GlobalShortcuts that reads the clipboard on demand when the
|
||||
// user presses it. (#1 is the extension's context menu, EXT's; #3 is AddUrlDialog's
|
||||
// clipboard prefill on open, already in place.)
|
||||
//
|
||||
// Best-effort by design, same as the risk doc says to treat all of this: the portal may
|
||||
// not exist on this desktop, the compositor may not implement it even if the portal
|
||||
// service does, or the user may decline the one-time "let Velox bind a global shortcut"
|
||||
// prompt. Every one of those is silent, permanent for this session, and never surfaced as
|
||||
// an error — there is nothing actionable for the user to do about a desktop that doesn't
|
||||
// have this, and the explicit paths (menu, prefill) still work regardless. Never promise
|
||||
// this in the UI before it has actually fired once.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariantMap>
|
||||
|
||||
class QDBusObjectPath;
|
||||
|
||||
namespace velox::gui {
|
||||
|
||||
class GlobalShortcut : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GlobalShortcut(QObject *parent = nullptr);
|
||||
|
||||
/// Fire-and-forget: asks the portal for a session, then to bind one shortcut. There is
|
||||
/// no synchronous "is this supported" answer — connect activated() and find out from
|
||||
/// whether it ever fires. Safe to call once at startup; safe to call on a desktop with
|
||||
/// no portal at all (logs and returns, does nothing further).
|
||||
void requestBinding();
|
||||
|
||||
signals:
|
||||
/// The bound shortcut was pressed. No payload on purpose: the receiver reads the
|
||||
/// clipboard itself at this moment (the "on demand" part of the explicit-path design),
|
||||
/// so nothing here ever touches clipboard content that wasn't asked for right now.
|
||||
void activated();
|
||||
|
||||
private slots:
|
||||
void onCreateSessionResponse(uint code, const QVariantMap &results);
|
||||
void onBindShortcutsResponse(uint code, const QVariantMap &results);
|
||||
void onPortalActivated(const QDBusObjectPath &sessionHandle, const QString &shortcutId,
|
||||
qulonglong timestamp, const QVariantMap &options);
|
||||
|
||||
private:
|
||||
void bindShortcuts();
|
||||
|
||||
QString sessionHandle_;
|
||||
bool requested_ = false;
|
||||
};
|
||||
|
||||
} // namespace velox::gui
|
||||
Reference in New Issue
Block a user