diff --git a/src/input.cpp b/src/input.cpp index 4db26d6ccd..a25dc776bf 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -544,7 +544,7 @@ private: { if (KWaylandServer::SurfaceInterface *s = (waylandServer()->seat()->*method)()) { if (Window *t = waylandServer()->findWindow(s)) { - return t->isLockScreen() || t->isInputMethod(); + return t->isLockScreen() || t->isInputMethod() || t->isLockScreenOverlay(); } return false; } @@ -3261,7 +3261,7 @@ Window *InputRedirection::findManagedToplevel(const QPointF &pos) continue; } if (isScreenLocked) { - if (!window->isLockScreen() && !window->isInputMethod()) { + if (!window->isLockScreen() && !window->isInputMethod() && !window->isLockScreenOverlay()) { continue; } } diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 07f68f3440..7c4b3d9e5e 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -175,6 +175,10 @@ ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml PROTOCOL ${WaylandProtocols_DATADIR}/staging/drm-lease/drm-lease-v1.xml BASENAME drm-lease-v1 ) +ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml + PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-lockscreen-overlay-v1.xml + BASENAME kde-lockscreen-overlay-v1 +) target_sources(kwin PRIVATE abstract_data_source.cpp @@ -208,6 +212,7 @@ target_sources(kwin PRIVATE keystate_interface.cpp layershell_v1_interface.cpp linuxdmabufv1clientbuffer.cpp + lockscreen_overlay_v1_interface.cpp output_interface.cpp outputdevice_v2_interface.cpp outputmanagement_v2_interface.cpp diff --git a/src/wayland/lockscreen_overlay_v1_interface.cpp b/src/wayland/lockscreen_overlay_v1_interface.cpp new file mode 100644 index 0000000000..61ad0b1d64 --- /dev/null +++ b/src/wayland/lockscreen_overlay_v1_interface.cpp @@ -0,0 +1,54 @@ +/* + SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "lockscreen_overlay_v1_interface.h" +#include "display.h" +#include "seat_interface.h" +#include "surface_interface.h" + +#include "qwayland-server-kde-lockscreen-overlay-v1.h" + +namespace KWaylandServer +{ +static constexpr int s_version = 1; + +class LockscreenOverlayV1InterfacePrivate : public QtWaylandServer::kde_lockscreen_overlay_v1 +{ +public: + LockscreenOverlayV1InterfacePrivate(Display *display, LockscreenOverlayV1Interface *q) + : QtWaylandServer::kde_lockscreen_overlay_v1(*display, s_version) + , q(q) + { + } + +protected: + void kde_lockscreen_overlay_v1_allow(Resource *resource, struct ::wl_resource *surface) override + { + auto surfaceIface = SurfaceInterface::get(surface); + if (surfaceIface->isMapped()) { + wl_resource_post_error(resource->handle, error_invalid_surface_state, "surface is already mapped"); + return; + } + Q_EMIT q->allowRequested(surfaceIface); + } + void kde_lockscreen_overlay_v1_destroy(Resource *resource) override + { + wl_resource_destroy(resource->handle); + } + +private: + LockscreenOverlayV1Interface *const q; +}; + +LockscreenOverlayV1Interface::~LockscreenOverlayV1Interface() = default; + +LockscreenOverlayV1Interface::LockscreenOverlayV1Interface(Display *display, QObject *parent) + : QObject(parent) + , d(new LockscreenOverlayV1InterfacePrivate(display, this)) +{ +} + +} diff --git a/src/wayland/lockscreen_overlay_v1_interface.h b/src/wayland/lockscreen_overlay_v1_interface.h new file mode 100644 index 0000000000..7dd3662464 --- /dev/null +++ b/src/wayland/lockscreen_overlay_v1_interface.h @@ -0,0 +1,43 @@ +/* + SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include "kwin_export.h" + +#include +#include +#include +#include + +struct wl_resource; + +namespace KWaylandServer +{ +class Display; +class SurfaceInterface; + +class LockscreenOverlayV1InterfacePrivate; + +class KWIN_EXPORT LockscreenOverlayV1Interface : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(LockscreenOverlayV1Interface) +public: + explicit LockscreenOverlayV1Interface(Display *display, QObject *parent = nullptr); + ~LockscreenOverlayV1Interface() override; + +Q_SIGNALS: + /// Notifies about the @p surface being activated + void allowRequested(SurfaceInterface *surface); + +private: + friend class LockscreenOverlayV1InterfacePrivate; + LockscreenOverlayV1Interface(LockscreenOverlayV1Interface *parent); + QScopedPointer d; +}; + +} diff --git a/src/wayland_server.cpp b/src/wayland_server.cpp index df1100e98a..003f4d75d2 100644 --- a/src/wayland_server.cpp +++ b/src/wayland_server.cpp @@ -37,6 +37,7 @@ #include "wayland/keyboard_shortcuts_inhibit_v1_interface.h" #include "wayland/keystate_interface.h" #include "wayland/linuxdmabufv1clientbuffer.h" +#include "wayland/lockscreen_overlay_v1_interface.h" #include "wayland/output_interface.h" #include "wayland/outputdevice_v2_interface.h" #include "wayland/outputmanagement_v2_interface.h" @@ -136,6 +137,7 @@ public: QByteArrayLiteral("org_kde_kwin_keystate"), QByteArrayLiteral("zkde_screencast_unstable_v1"), QByteArrayLiteral("org_kde_plasma_activation_feedback"), + QByteArrayLiteral("kde_lockscreen_overlay_v1"), }; const QSet inputmethodInterfaces = {"zwp_input_panel_v1", "zwp_input_method_v1"}; @@ -471,6 +473,15 @@ bool WaylandServer::init(InitializationFlags flags) connect(static_cast(qApp), &Application::workspaceCreated, this, init); } + auto aboveLockscreen = new KWaylandServer::LockscreenOverlayV1Interface(m_display, this); + connect(aboveLockscreen, &KWaylandServer::LockscreenOverlayV1Interface::allowRequested, this, [](SurfaceInterface *surface) { + auto w = waylandServer()->findWindow(surface); + if (!w) { + return; + } + w->setLockScreenOverlay(true); + }); + return true; } diff --git a/src/window.cpp b/src/window.cpp index e06bbf3ae2..a2a1b696cb 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -911,6 +911,9 @@ Layer Window::belongsToLayer() const if (isInputMethod()) { return UnmanagedLayer; } + if (isLockScreenOverlay() && waylandServer() && waylandServer()->isScreenLocked()) { + return UnmanagedLayer; + } if (isDesktop()) { return workspace()->showingDesktop() ? AboveLayer : DesktopLayer; } @@ -4497,6 +4500,20 @@ uint32_t Window::interactiveMoveResizeCount() const return m_interactiveMoveResize.counter; } +void Window::setLockScreenOverlay(bool allowed) +{ + if (m_lockScreenOverlay == allowed) { + return; + } + m_lockScreenOverlay = allowed; + Q_EMIT lockScreenOverlayChanged(); +} + +bool Window::isLockScreenOverlay() const +{ + return m_lockScreenOverlay; +} + } // namespace KWin #include "moc_window.cpp" diff --git a/src/window.h b/src/window.h index 092d138238..f8bd1ccdc3 100644 --- a/src/window.h +++ b/src/window.h @@ -712,6 +712,9 @@ public: bool isOnCurrentActivity() const; bool isOnAllDesktops() const; bool isOnAllActivities() const; + bool isLockScreenOverlay() const; + + void setLockScreenOverlay(bool allowed); virtual QByteArray windowRole() const; QByteArray sessionId() const; @@ -1543,6 +1546,7 @@ Q_SIGNALS: void unresponsiveChanged(bool); void decorationChanged(); void hiddenChanged(); + void lockScreenOverlayChanged(); protected: void setWindowHandles(xcb_window_t client); @@ -2011,6 +2015,7 @@ private: WindowRules m_rules; quint32 m_lastUsageSerial = 0; + bool m_lockScreenOverlay = false; }; /** diff --git a/src/windowitem.cpp b/src/windowitem.cpp index 1c2417daff..e4d3f18aba 100644 --- a/src/windowitem.cpp +++ b/src/windowitem.cpp @@ -35,6 +35,7 @@ WindowItem::WindowItem(Window *window, Item *parent) if (waylandServer()) { connect(waylandServer(), &WaylandServer::lockStateChanged, this, &WindowItem::updateVisibility); } + connect(window, &Window::lockScreenOverlayChanged, this, &WindowItem::updateVisibility); connect(window, &Window::minimizedChanged, this, &WindowItem::updateVisibility); connect(window, &Window::hiddenChanged, this, &WindowItem::updateVisibility); connect(window, &Window::activitiesChanged, this, &WindowItem::updateVisibility); @@ -127,7 +128,7 @@ void WindowItem::handleWindowClosed(Window *original, Deleted *deleted) bool WindowItem::computeVisibility() const { if (waylandServer() && waylandServer()->isScreenLocked()) { - return m_window->isLockScreen() || m_window->isInputMethod(); + return m_window->isLockScreen() || m_window->isInputMethod() || m_window->isLockScreenOverlay(); } if (m_window->isDeleted()) { if (m_forceVisibleByDeleteCount == 0) { diff --git a/src/xdgactivationv1.cpp b/src/xdgactivationv1.cpp index 9334a14233..88a42b0206 100644 --- a/src/xdgactivationv1.cpp +++ b/src/xdgactivationv1.cpp @@ -29,7 +29,7 @@ static bool isPrivilegedInWindowManagement(const ClientConnection *client) { Q_ASSERT(client); auto requestedInterfaces = client->property("requestedInterfaces").toStringList(); - return requestedInterfaces.contains(QLatin1String("org_kde_plasma_window_management")); + return requestedInterfaces.contains(QLatin1String("org_kde_plasma_window_management")) || requestedInterfaces.contains(QLatin1String("kde_lockscreen_overlay_v1")); } static const QString windowDesktopFileName(Window *window) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a03dfb5622..30cd2d126f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -48,6 +48,13 @@ if (QT_MAJOR_VERSION EQUAL "5") PROTOCOL ${WaylandProtocols_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml BASENAME xdg-activation-v1 ) + + add_executable(lockscreenoverlaytest lockscreenoverlaytest.cpp) + target_link_libraries(lockscreenoverlaytest Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate Wayland::Client KF5::WindowSystem) + ecm_add_qtwayland_client_protocol(lockscreenoverlaytest + PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-lockscreen-overlay-v1.xml + BASENAME kde-lockscreen-overlay-v1 + ) endif() if (TARGET Qt6::Gui) diff --git a/tests/lockscreenoverlaytest.cpp b/tests/lockscreenoverlaytest.cpp new file mode 100644 index 0000000000..d89b13e1d8 --- /dev/null +++ b/tests/lockscreenoverlaytest.cpp @@ -0,0 +1,89 @@ +/* + SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#include "qwayland-kde-lockscreen-overlay-v1.h" +#include +#include +#include +#include + +class WaylandAboveLockscreen : public QWaylandClientExtensionTemplate, public QtWayland::kde_lockscreen_overlay_v1 +{ +public: + WaylandAboveLockscreen() + : QWaylandClientExtensionTemplate(1) + { + QMetaObject::invokeMethod(this, "addRegistryListener"); + } + + void allowWindow(QWindow *window) + { + QPlatformNativeInterface *native = qGuiApp->platformNativeInterface(); + wl_surface *surface = reinterpret_cast(native->nativeResourceForWindow(QByteArrayLiteral("surface"), window)); + allow(surface); + } +}; + +class WidgetAllower : public QObject +{ +public: + WidgetAllower(QWidget *widget) + : QObject(widget) + , m_widget(widget) + { + widget->installEventFilter(this); + } + + bool eventFilter(QObject * /*watched*/, QEvent *event) override + { + if (auto w = m_widget->windowHandle()) { + WaylandAboveLockscreen aboveLockscreen; + Q_ASSERT(aboveLockscreen.isInitialized()); + aboveLockscreen.allowWindow(w); + deleteLater(); + } + return false; + } + + QWidget *const m_widget; +}; + +int main(int argc, char *argv[]) +{ + + QApplication app(argc, argv); + QWidget window1(nullptr, Qt::Window); + window1.setWindowTitle("Window 1"); + window1.setLayout(new QVBoxLayout); + QPushButton p("Lock && Raise the Window 2"); + window1.layout()->addWidget(&p); + window1.show(); + + QWidget window2(nullptr, Qt::Window); + window2.setWindowTitle("Window 2"); + window2.setLayout(new QVBoxLayout); + QPushButton p2("Close"); + window2.layout()->addWidget(&p2); + + new WidgetAllower(&window2); + auto raiseWindow2 = [&] { + KWindowSystem::requestXdgActivationToken(window2.windowHandle(), 0, "lockscreenoverlaytest.desktop"); + }; + QObject::connect(KWindowSystem::self(), &KWindowSystem::xdgActivationTokenArrived, &window2, [&window2](int, const QString &token) { + KWindowSystem::setCurrentXdgActivationToken(token); + KWindowSystem::activateWindow(window2.windowHandle()); + }); + + QObject::connect(&p, &QPushButton::clicked, &app, [&] { + QProcess::execute("loginctl", {"lock-session"}); + window2.showFullScreen(); + QTimer::singleShot(3000, &app, raiseWindow2); + }); + + QObject::connect(&p2, &QPushButton::clicked, &window2, &QWidget::close); + + return app.exec(); +} diff --git a/tests/lockscreenoverlaytest.desktop b/tests/lockscreenoverlaytest.desktop new file mode 100644 index 0000000000..8eafec924f --- /dev/null +++ b/tests/lockscreenoverlaytest.desktop @@ -0,0 +1,9 @@ +# copy to $(qtpaths --install-prefix)/share/applications/ +# remember to change the Exec line to your builddir path + +[Desktop Entry] +Exec=absolute/path/to/the/executable/bin/lockscreenoverlaytest +Type=Application +X-KDE-Wayland-Interfaces=kde_lockscreen_overlay_v1 +NoDisplay=true +Name=KWin LockScreen Overay Test