diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ # Dependencies set(REQUIRED_QT_VERSION 5.5.0) -find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Gui) +find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Concurrent Gui) find_package(Wayland 1.7 COMPONENTS Client Server) set_package_properties(Wayland PROPERTIES diff --git a/autotests/client/test_wayland_windowmanagement.cpp b/autotests/client/test_wayland_windowmanagement.cpp --- a/autotests/client/test_wayland_windowmanagement.cpp +++ b/autotests/client/test_wayland_windowmanagement.cpp @@ -66,6 +66,7 @@ void testRequestShowingDesktop(); void testParentWindow(); void testGeometry(); + void testIcon(); void cleanup(); @@ -545,5 +546,40 @@ QCOMPARE(window->geometry(), QRect(0, 0, 35, 45)); } -QTEST_GUILESS_MAIN(TestWindowManagement) +void TestWindowManagement::testIcon() +{ + using namespace KWayland::Client; + QVERIFY(m_window); + QSignalSpy iconChangedSpy(m_window, &PlasmaWindow::iconChanged); + QVERIFY(iconChangedSpy.isValid()); + m_windowInterface->setIcon(QIcon()); + // first goes from themed name to empty + QVERIFY(iconChangedSpy.wait()); + if (!QIcon::hasThemeIcon(QStringLiteral("wayland"))) { + QEXPECT_FAIL("", "no icon", Continue); + } + QCOMPARE(m_window->icon().name(), QStringLiteral("wayland")); + QVERIFY(iconChangedSpy.wait()); + if (!QIcon::hasThemeIcon(QStringLiteral("wayland"))) { + QEXPECT_FAIL("", "no icon", Continue); + } + QCOMPARE(m_window->icon().name(), QStringLiteral("wayland")); + + // create an icon with a pixmap + QPixmap p(32, 32); + p.fill(Qt::red); + m_windowInterface->setIcon(p); + QVERIFY(iconChangedSpy.wait()); + QCOMPARE(m_window->icon().pixmap(32, 32), p); + + // let's set a themed icon + m_windowInterface->setIcon(QIcon::fromTheme(QStringLiteral("xorg"))); + QVERIFY(iconChangedSpy.wait()); + if (!QIcon::hasThemeIcon(QStringLiteral("xorg"))) { + QEXPECT_FAIL("", "no icon", Continue); + } + QCOMPARE(m_window->icon().name(), QStringLiteral("xorg")); +} + +QTEST_MAIN(TestWindowManagement) #include "test_wayland_windowmanagement.moc" diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -143,6 +143,7 @@ target_link_libraries(KF5WaylandClient PUBLIC Qt5::Gui PRIVATE Wayland::Client + Qt5::Concurrent ) if(IS_ABSOLUTE "${KF5_INCLUDE_INSTALL_DIR}") diff --git a/src/client/plasmawindowmanagement.cpp b/src/client/plasmawindowmanagement.cpp --- a/src/client/plasmawindowmanagement.cpp +++ b/src/client/plasmawindowmanagement.cpp @@ -26,7 +26,10 @@ // Wayland #include +#include +#include #include +#include namespace KWayland { @@ -99,6 +102,7 @@ static void initialStateCallback(void *data, org_kde_plasma_window *window); static void parentWindowCallback(void *data, org_kde_plasma_window *window, org_kde_plasma_window *parent); static void windowGeometryCallback(void *data, org_kde_plasma_window *window, int32_t x, int32_t y, uint32_t width, uint32_t height); + static void iconChangedCallback(void *data, org_kde_plasma_window *org_kde_plasma_window); void setActive(bool set); void setMinimized(bool set); void setMaximized(bool set); @@ -334,7 +338,8 @@ unmappedCallback, initialStateCallback, parentWindowCallback, - windowGeometryCallback + windowGeometryCallback, + iconChangedCallback }; void PlasmaWindow::Private::parentWindowCallback(void *data, org_kde_plasma_window *window, org_kde_plasma_window *parent) @@ -473,6 +478,64 @@ emit p->q->iconChanged(); } +static int readData(int fd, QByteArray &data) +{ + // implementation based on QtWayland file qwaylanddataoffer.cpp + char buf[4096]; + int retryCount = 0; + int n; + while (true) { + n = QT_READ(fd, buf, sizeof buf); + if (n == -1 && (errno == EAGAIN) && ++retryCount < 1000) { + usleep(1000); + } else { + break; + } + } + if (n > 0) { + data.append(buf, n); + n = readData(fd, data); + } + return n; +} + +void PlasmaWindow::Private::iconChangedCallback(void *data, org_kde_plasma_window *window) +{ + auto p = cast(data); + Q_UNUSED(window); + int pipeFds[2]; + if (pipe2(pipeFds, O_CLOEXEC|O_NONBLOCK) != 0) { + return; + } + org_kde_plasma_window_get_icon(p->window, pipeFds[1]); + close(pipeFds[1]); + const int pipeFd = pipeFds[0]; + auto readIcon = [pipeFd] () -> QIcon { + QByteArray content; + if (readData(pipeFd, content) != 0) { + close(pipeFd); + return QIcon::fromTheme(QStringLiteral("wayland")); + } + close(pipeFd); + QDataStream ds(content); + QIcon icon; + ds >> icon; + if (icon.isNull()) { + return QIcon::fromTheme(QStringLiteral("wayland")); + } + return icon; + }; + QFutureWatcher *watcher = new QFutureWatcher(p->q); + QObject::connect(watcher, &QFutureWatcher::finished, p->q, + [p, watcher] { + watcher->deleteLater(); + p->icon = watcher->result(); + emit p->q->iconChanged(); + } + ); + watcher->setFuture(QtConcurrent::run(readIcon)); +} + void PlasmaWindow::Private::setActive(bool set) { if (active == set) { diff --git a/src/client/protocols/plasma-window-management.xml b/src/client/protocols/plasma-window-management.xml --- a/src/client/protocols/plasma-window-management.xml +++ b/src/client/protocols/plasma-window-management.xml @@ -17,7 +17,7 @@ along with this program. If not, see . ]]> - + This interface manages application windows. It provides requests to show and hide the desktop and emits @@ -83,7 +83,7 @@ - + Manages and control an application window. @@ -164,6 +164,14 @@ + + + The compositor will write the window icon into the provided file descriptor. + The data is a serialized QIcon with QDataStream. + + + + This event will be sent as soon as the window title is changed. @@ -240,5 +248,14 @@ + + + + This event will be sent whenever the icon of the window changes, but there is no themed + icon name. Common examples are Xwayland windows which have a pixmap based icon. + + The client can request the icon using get_icon. + + diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -153,7 +153,7 @@ &Registry::plasmaShellRemoved }}, {Registry::Interface::PlasmaWindowManagement, { - 6, + 7, QByteArrayLiteral("org_kde_plasma_window_management"), &org_kde_plasma_window_management_interface, &Registry::plasmaWindowManagementAnnounced, diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -145,6 +145,7 @@ PRIVATE Wayland::Server EGL::EGL + Qt5::Concurrent ) if(IS_ABSOLUTE "${KF5_INCLUDE_INSTALL_DIR}") diff --git a/src/server/plasmawindowmanagement_interface.h b/src/server/plasmawindowmanagement_interface.h --- a/src/server/plasmawindowmanagement_interface.h +++ b/src/server/plasmawindowmanagement_interface.h @@ -101,7 +101,13 @@ void setMaximizeable(bool set); void setFullscreenable(bool set); void setSkipTaskbar(bool skip); - void setThemedIconName(const QString &iconName); + /** + * @deprecated since 5.28 use setIcon + * @see setIcon + **/ +#ifndef KWAYLANDSERVER_NO_DEPRECATED + void KWAYLANDSERVER_DEPRECATED setThemedIconName(const QString &iconName); +#endif /** * @since 5.22 */ @@ -155,6 +161,19 @@ **/ void setGeometry(const QRect &geometry); + /** + * Set the icon of the PlasmaWindowInterface. + * + * In case the icon has a themed name, only the name is sent to the client. + * Otherwise the client is only informed that there is an icon and the client + * can request the icon in an asynchronous way by passing a file descriptor + * into which the icon will be serialized. + * + * @param icon The new icon + * @since 5.28 + **/ + void setIcon(const QIcon &icon); + Q_SIGNALS: void closeRequested(); /** diff --git a/src/server/plasmawindowmanagement_interface.cpp b/src/server/plasmawindowmanagement_interface.cpp --- a/src/server/plasmawindowmanagement_interface.cpp +++ b/src/server/plasmawindowmanagement_interface.cpp @@ -23,6 +23,9 @@ #include "display.h" #include "surface_interface.h" +#include +#include +#include #include #include #include @@ -70,6 +73,7 @@ void setTitle(const QString &title); void setAppId(const QString &appId); void setThemedIconName(const QString &iconName); + void setIcon(const QIcon &icon); void setVirtualDesktop(quint32 desktop); void unmap(); void setState(org_kde_plasma_window_management_state flag, bool set); @@ -97,21 +101,23 @@ static void setMinimizedGeometryCallback(wl_client *client, wl_resource *resource, wl_resource *panel, uint32_t x, uint32_t y, uint32_t width, uint32_t height); static void unsetMinimizedGeometryCallback(wl_client *client, wl_resource *resource, wl_resource *panel); static void destroyCallback(wl_client *client, wl_resource *resource); + static void getIconCallback(wl_client *client, wl_resource *resource, int32_t fd); static Private *cast(wl_resource *resource) { return reinterpret_cast(wl_resource_get_user_data(resource)); } PlasmaWindowInterface *q; QString m_title; QString m_appId; QString m_themedIconName; + QIcon m_icon; quint32 m_virtualDesktop = 0; quint32 m_state = 0; wl_listener listener; static const struct org_kde_plasma_window_interface s_interface; }; -const quint32 PlasmaWindowManagementInterface::Private::s_version = 6; +const quint32 PlasmaWindowManagementInterface::Private::s_version = 7; PlasmaWindowManagementInterface::Private::Private(PlasmaWindowManagementInterface *q, Display *d) : Global::Private(d, &org_kde_plasma_window_management_interface, s_version) @@ -267,7 +273,8 @@ closeCallback, requestMoveCallback, requestResizeCallback, - destroyCallback + destroyCallback, + getIconCallback }; #endif @@ -326,7 +333,13 @@ org_kde_plasma_window_send_title_changed(resource, m_title.toUtf8().constData()); } org_kde_plasma_window_send_state_changed(resource, m_state); - org_kde_plasma_window_send_themed_icon_name_changed(resource, m_themedIconName.toUtf8().constData()); + if (!m_themedIconName.isEmpty()) { + org_kde_plasma_window_send_themed_icon_name_changed(resource, m_themedIconName.toUtf8().constData()); + } else { + if (wl_resource_get_version(resource) >= ORG_KDE_PLASMA_WINDOW_ICON_CHANGED_SINCE_VERSION) { + org_kde_plasma_window_send_icon_changed(resource); + } + } org_kde_plasma_window_send_parent_window(resource, resourceForParent(parentWindow, resource)); @@ -368,6 +381,34 @@ } } +void PlasmaWindowInterface::Private::setIcon(const QIcon &icon) +{ + m_icon = icon; + setThemedIconName(m_icon.name()); + if (m_icon.name().isEmpty()) { + for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { + if (wl_resource_get_version(*it) >= ORG_KDE_PLASMA_WINDOW_ICON_CHANGED_SINCE_VERSION) { + org_kde_plasma_window_send_icon_changed(*it); + } + } + } +} + +void PlasmaWindowInterface::Private::getIconCallback(wl_client *client, wl_resource *resource, int32_t fd) +{ + Q_UNUSED(client) + Private *p = cast(resource); + QtConcurrent::run( + [fd] (const QIcon &icon) { + QFile file; + file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle); + QDataStream ds(&file); + ds << icon; + file.close(); + }, p->m_icon + ); +} + void PlasmaWindowInterface::Private::setTitle(const QString &title) { if (m_title == title) { @@ -699,10 +740,17 @@ d->setState(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_SKIPTASKBAR, set); } +#ifndef KWAYLANDSERVER_NO_DEPRECATED void PlasmaWindowInterface::setThemedIconName(const QString &iconName) { d->setThemedIconName(iconName); } +#endif + +void PlasmaWindowInterface::setIcon(const QIcon &icon) +{ + d->setIcon(icon); +} void PlasmaWindowInterface::setShadeable(bool set) { diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -2,14 +2,9 @@ include(ECMMarkAsTest) -find_package(Qt5Concurrent ${QT_MIN_VERSION} CONFIG QUIET) +set(scannerSRCS generator.cpp) -if (Qt5Concurrent_FOUND) - set(scannerSRCS generator.cpp) - - add_definitions(-DMAPPING_FILE="${CMAKE_CURRENT_SOURCE_DIR}/mapping.txt") - add_executable(kwaylandScanner ${scannerSRCS}) - target_link_libraries(kwaylandScanner Qt5::Core Qt5::Concurrent) - ecm_mark_as_test(kwaylandScanner) - -endif() +add_definitions(-DMAPPING_FILE="${CMAKE_CURRENT_SOURCE_DIR}/mapping.txt") +add_executable(kwaylandScanner ${scannerSRCS}) +target_link_libraries(kwaylandScanner Qt5::Core Qt5::Concurrent) +ecm_mark_as_test(kwaylandScanner) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,8 +10,7 @@ ecm_mark_as_test(testServer) find_package(Qt5Widgets ${QT_MIN_VERSION} CONFIG QUIET) -find_package(Qt5Concurrent ${QT_MIN_VERSION} CONFIG QUIET) -if (Qt5Widgets_FOUND AND Qt5Concurrent_FOUND) +if (Qt5Widgets_FOUND) set(testRenderingServer_SRCS renderingservertest.cpp ) @@ -24,11 +23,9 @@ target_link_libraries(copyClient KF5::WaylandClient) ecm_mark_as_test(copyClient) -if (Qt5Concurrent_FOUND) - add_executable(pasteClient pasteclient.cpp) - target_link_libraries(pasteClient Qt5::Concurrent KF5::WaylandClient) - ecm_mark_as_test(pasteClient) -endif() +add_executable(pasteClient pasteclient.cpp) +target_link_libraries(pasteClient Qt5::Concurrent KF5::WaylandClient) +ecm_mark_as_test(pasteClient) if (HAVE_LINUX_INPUT_H) add_executable(touchClientTest touchclienttest.cpp)