service/tray: forward Wayland activation tokens on primary clicks

This commit is contained in:
Domen Kožar authored and outfoxxed committed 2026-10-21 23:47:48 -07:00
1 parent 5c4f4c7148
commit 3c5323bcf5
11 files changed
+181 -1

No files matched your search

+4
View File
@@ -1,3 +1,7 @@
## Other Changes
- `SystemTrayItem.activate()` now forwards Wayland activation tokens to applications.
## Bug Fixes
- Fixed ScriptModel not sending role updates on changed data that compares equal by key or structure.
@@ -52,3 +52,10 @@ target_link_libraries(quickshell-service-statusnotifier PRIVATE Qt::Quick Qt::DB
target_link_libraries(quickshell PRIVATE quickshell-service-statusnotifierplugin)
qs_module_pch(quickshell-service-statusnotifier SET dbus)
if (WAYLAND)
target_compile_definitions(quickshell-service-statusnotifier PRIVATE QS_WAYLAND)
target_link_libraries(quickshell-service-statusnotifier PRIVATE
quickshell-wayland-activation
)
endif()
+38
View File
@@ -12,6 +12,7 @@
#include <qobject.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qpointer.h>
#include <qqmlengine.h>
#include <qrect.h>
#include <qsize.h>
@@ -30,6 +31,10 @@
#include "host.hpp"
#include "watcher.hpp"
#ifdef QS_WAYLAND
#include "../../wayland/activation.hpp"
#endif
using namespace qs::dbus;
using namespace qs::dbus::dbusmenu;
using namespace qs::menu::platform;
@@ -219,6 +224,39 @@ QPixmap StatusNotifierItem::createPixmap(const QSize& size) const {
}
void StatusNotifierItem::activate() {
#ifdef QS_WAYLAND
auto callback = [item = QPointer(this)](const QString& token) {
if (item) item->activateWithToken(token);
};
if (qs::wayland::activation::requestActivationToken(callback)) return;
#endif
this->activateWithoutToken();
}
void StatusNotifierItem::activateWithToken(const QString& token) {
if (token.isEmpty()) {
this->activateWithoutToken();
return;
}
auto* call = new QDBusPendingCallWatcher(this->item->ProvideXdgActivationToken(token), this);
QObject::connect(
call,
&QDBusPendingCallWatcher::finished,
this,
[this](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError() && reply.error().type() != QDBusError::UnknownMethod) {
qCWarning(logStatusNotifierItem) << "Error providing activation token:" << reply.error();
}
delete call;
this->activateWithoutToken();
}
);
}
void StatusNotifierItem::activateWithoutToken() {
auto pendingCall = this->item->Activate(0, 0);
auto* call = new QDBusPendingCallWatcher(pendingCall, this);
+2
View File
@@ -169,6 +169,8 @@ private slots:
void onGetAllFailed() const;
private:
void activateWithToken(const QString& token);
void activateWithoutToken();
void updateMenuState();
void updatePixmapIndex();
void onMenuPathChanged();
@@ -29,6 +29,9 @@
<arg type="i" direction="in" name="x"/>
<arg type="i" direction="in" name="y"/>
</method>
<method name="ProvideXdgActivationToken">
<arg type="s" direction="in" name="token"/>
</method>
<method name="Activate">
<arg type="i" direction="in" name="x"/>
<arg type="i" direction="in" name="y"/>
+10
View File
@@ -73,6 +73,16 @@ endfunction()
# -----
wl_proto(wlp-xdg-activation xdg-activation-v1 "${WAYLAND_PROTOCOLS}/staging/xdg-activation")
qt_add_library(quickshell-wayland-activation STATIC
activation.cpp
)
target_link_libraries(quickshell-wayland-activation PRIVATE
Qt::Gui Qt::WaylandClient Qt::WaylandClientPrivate wlp-xdg-activation
)
qt_add_library(quickshell-wayland STATIC
wl_proxy_safe_deref.cpp
platformmenu.cpp
+89
View File
@@ -0,0 +1,89 @@
#include "activation.hpp"
#include <functional>
#include <utility>
#include <private/qwaylanddisplay_p.h>
#include <private/qwaylandinputdevice_p.h>
#include <private/qwaylandintegration_p.h>
#include <private/qwaylandwindow_p.h>
#include <qguiapplication.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qpointer.h>
#include <qstring.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qwayland-xdg-activation-v1.h>
#include <qwaylandclientextension.h>
#include "wayland-xdg-activation-v1-client-protocol.h"
namespace qs::wayland::activation {
namespace {
class ActivationManager
: public QWaylandClientExtensionTemplate<ActivationManager>
, public QtWayland::xdg_activation_v1 {
public:
ActivationManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); }
};
class ActivationToken
: public QObject
, public QtWayland::xdg_activation_token_v1 {
public:
ActivationToken(::xdg_activation_token_v1* token, QtWaylandClient::QWaylandWindow* window)
: QObject(window)
, QtWayland::xdg_activation_token_v1(token)
, window(window) {}
~ActivationToken() override { this->destroy(); }
Q_DISABLE_COPY_MOVE(ActivationToken);
private:
void xdg_activation_token_v1_done(const QString& token) override {
this->deleteLater();
if (this->window) emit this->window->xdgActivationTokenCreated(token);
}
QPointer<QtWaylandClient::QWaylandWindow> window;
};
} // namespace
bool requestActivationToken(std::function<void(const QString&)> callback) {
if (QGuiApplication::platformName() != "wayland") return false;
auto* display = QtWaylandClient::QWaylandIntegration::instance()->display();
auto* window = display->lastInputWindow();
if (!window || !window->surface() || !display->lastInputDevice()) return false;
QObject::connect(
window,
&QtWaylandClient::QWaylandWindow::xdgActivationTokenCreated,
window,
std::move(callback),
Qt::SingleShotConnection
);
window->requestXdgActivationToken(display->lastInputSerial());
return true;
}
void requestXdgActivationToken(QtWaylandClient::QWaylandWindow* window, quint32 serial) {
auto* display = window->display();
auto* seat = display->lastInputDevice();
static auto* manager = new ActivationManager(); // NOLINT
if (!manager->isActive() || !window->surface() || !seat) {
emit window->xdgActivationTokenCreated({});
return;
}
auto* token = new ActivationToken(manager->get_activation_token(), window);
token->set_serial(serial, seat->object());
token->set_surface(window->surface());
token->commit();
}
} // namespace qs::wayland::activation
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <functional>
#include <qstring.h>
#include <qtypes.h>
namespace QtWaylandClient { // NOLINT(readability-identifier-naming)
class QWaylandWindow;
}
namespace qs::wayland::activation {
// Request a token using Qt's most recent input event. Returns false if unable to request a token.
bool requestActivationToken(std::function<void(const QString&)> callback);
// Request an activation token for a window to be delivered via QWaylandWindow::xdgActivationTokenCreated
void requestXdgActivationToken(QtWaylandClient::QWaylandWindow* window, quint32 serial);
} // namespace qs::wayland::activation
+1 -1
View File
@@ -24,7 +24,7 @@ wl_proto(wlp-xdg-shell xdg-shell "${WAYLAND_PROTOCOLS}/stable/xdg-shell")
target_link_libraries(quickshell-wayland-layershell PRIVATE
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
wlp-layer-shell wlp-xdg-shell
wlp-layer-shell wlp-xdg-shell quickshell-wayland-activation
)
qs_module_pch(quickshell-wayland-layershell SET large)
+5
View File
@@ -20,6 +20,7 @@
#include <wayland-xdg-shell-client-protocol.h>
#include "../../window/panelinterface.hpp"
#include "../activation.hpp"
#include "shell_integration.hpp"
#include "wlr_layershell.hpp"
@@ -130,6 +131,10 @@ LayerSurfaceBridge* LayerSurfaceBridge::init(QWindow* window, LayerSurfaceState
return bridge;
}
void LayerSurface::requestXdgActivationToken(quint32 serial) {
activation::requestXdgActivationToken(this->window(), serial);
}
LayerSurface::LayerSurface(LayerShellIntegration* shell, QtWaylandClient::QWaylandWindow* window)
: QtWaylandClient::QWaylandShellSurface(window) {
+2
View File
@@ -66,6 +66,8 @@ public:
void applyConfigure() override;
void setWindowGeometry(const QRect& /*geometry*/) override {}
void requestXdgActivationToken(quint32 serial) override;
void attachPopup(QtWaylandClient::QWaylandShellSurface* popup) override;
void commit();