Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,6 @@
if(KWINDOWSYSTEM_X11)
add_subdirectory(xcb)
endif()
if(KWINDOWSYSTEM_WAYLAND)
add_subdirectory(wayland)
endif()
@@ -0,0 +1,70 @@
add_library(KF6WindowSystemKWaylandPlugin MODULE)
set(wayland_plugin_SRCS
plugin.cpp
shm.cpp
windoweffects.cpp
windowshadow.cpp
windowsystem.cpp
waylandxdgactivationv1.cpp
waylandxdgforeignv2.cpp
plugin.h
windoweffects.h
windowshadow.h
windowsystem.h
waylandxdgactivationv1_p.h
)
if (Qt6_VERSION VERSION_GREATER_EQUAL "6.8.0")
set(private_code_option "PRIVATE_CODE")
endif()
qt6_generate_wayland_protocol_client_sources(KF6WindowSystemKWaylandPlugin
${private_code_option}
FILES
${WaylandProtocols_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml
${WaylandProtocols_DATADIR}/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml
${PLASMA_WAYLAND_PROTOCOLS_DIR}/blur.xml
${PLASMA_WAYLAND_PROTOCOLS_DIR}/contrast.xml
${PLASMA_WAYLAND_PROTOCOLS_DIR}/slide.xml
${PLASMA_WAYLAND_PROTOCOLS_DIR}/plasma-window-management.xml
${PLASMA_WAYLAND_PROTOCOLS_DIR}/shadow.xml
${Wayland_DATADIR}/wayland.xml
)
ecm_qt_declare_logging_category(wayland_plugin_SRCS
HEADER logging.h
IDENTIFIER KWAYLAND_KWS
CATEGORY_NAME kf.windowsystem.wayland
OLD_CATEGORY_NAMES org.kde.kf5.kwindowsystem.kwayland
DEFAULT_SEVERITY Warning
DESCRIPTION "KWindowSystem Wayland Plugin"
EXPORT KWINDOWSYSTEM
)
target_sources(KF6WindowSystemKWaylandPlugin PRIVATE ${wayland_plugin_SRCS})
if(HAVE_MEMFD)
target_compile_definitions(KF6WindowSystemKWaylandPlugin PRIVATE -DHAVE_MEMFD)
endif()
target_compile_options(KF6WindowSystemKWaylandPlugin PRIVATE -UQT_NO_KEYWORDS)
pkg_check_modules(XKBCommon REQUIRED IMPORTED_TARGET xkbcommon)
target_link_libraries(KF6WindowSystemKWaylandPlugin
KF6WindowSystem
Wayland::Client
Qt::GuiPrivate
Qt::WaylandClient
PkgConfig::XKBCommon
)
install(
TARGETS
KF6WindowSystemKWaylandPlugin
DESTINATION
${KDE_INSTALL_PLUGINDIR}/kf6/kwindowsystem/
)
@@ -0,0 +1,40 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "plugin.h"
#include "windoweffects.h"
#include "windowshadow.h"
#include "windowsystem.h"
KWaylandPlugin::KWaylandPlugin(QObject *parent)
: KWindowSystemPluginInterface(parent)
{
}
KWaylandPlugin::~KWaylandPlugin()
{
}
KWindowEffectsPrivate *KWaylandPlugin::createEffects()
{
return new WindowEffects();
}
KWindowSystemPrivate *KWaylandPlugin::createWindowSystem()
{
return new WindowSystem();
}
KWindowShadowTilePrivate *KWaylandPlugin::createWindowShadowTile()
{
return new WindowShadowTile();
}
KWindowShadowPrivate *KWaylandPlugin::createWindowShadow()
{
return new WindowShadow();
}
#include "moc_plugin.cpp"
@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KWINDOWSYSTEM_KWAYLAND_PLUGIN_H
#define KWINDOWSYSTEM_KWAYLAND_PLUGIN_H
#include "kwindowsystemplugininterface_p.h"
class KWaylandPlugin : public KWindowSystemPluginInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kde.kwindowsystem.KWindowSystemPluginInterface" FILE "wayland.json")
Q_INTERFACES(KWindowSystemPluginInterface)
public:
explicit KWaylandPlugin(QObject *parent = nullptr);
~KWaylandPlugin() override;
KWindowEffectsPrivate *createEffects() override;
KWindowSystemPrivate *createWindowSystem() override;
KWindowShadowTilePrivate *createWindowShadowTile() override;
KWindowShadowPrivate *createWindowShadow() override;
};
#endif
@@ -0,0 +1,137 @@
/*
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 David Redondo <kde@david-redondo.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "shm.h"
#include <QGuiApplication>
#include <QImage>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstring>
static constexpr auto version = 1;
ShmBuffer::ShmBuffer(::wl_buffer *buffer)
: QtWayland::wl_buffer(buffer)
{
}
ShmBuffer::~ShmBuffer()
{
destroy();
}
Shm::Shm(QObject *parent)
: QWaylandClientExtensionTemplate(::version)
{
setParent(parent);
connect(this, &QWaylandClientExtension::activeChanged, this, [this] {
if (!isActive()) {
wl_shm_destroy(object());
}
});
initialize();
}
Shm *Shm::instance()
{
static Shm *instance = new Shm(qGuiApp);
return instance;
}
Shm::~Shm() noexcept
{
if (isActive()) {
wl_shm_destroy(object());
}
}
static wl_shm_format toWaylandFormat(QImage::Format format)
{
switch (format) {
case QImage::Format_ARGB32_Premultiplied:
return WL_SHM_FORMAT_ARGB8888;
case QImage::Format_RGB32:
return WL_SHM_FORMAT_XRGB8888;
case QImage::Format_ARGB32:
qCWarning(KWAYLAND_KWS()) << "Unsupported image format: " << format << ". expect slow performance. Use QImage::Format_ARGB32_Premultiplied";
return WL_SHM_FORMAT_ARGB8888;
default:
qCWarning(KWAYLAND_KWS()) << "Unsupported image format: " << format << ". expect slow performance.";
return WL_SHM_FORMAT_ARGB8888;
}
}
std::unique_ptr<ShmBuffer> Shm::createBuffer(const QImage &image)
{
if (image.isNull()) {
return {};
}
auto format = toWaylandFormat(image.format());
const int stride = image.bytesPerLine();
const int32_t byteCount = image.size().height() * stride;
int fd = -1;
#if defined HAVE_MEMFD
fd = memfd_create("kwayland-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (fd >= 0) {
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
} else
#endif
{
char templateName[] = "/tmp/kwayland-shared-XXXXXX";
fd = mkstemp(templateName);
if (fd >= 0) {
unlink(templateName);
int flags = fcntl(fd, F_GETFD);
if (flags == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
close(fd);
fd = -1;
}
}
}
if (fd == -1) {
qCDebug(KWAYLAND_KWS) << "Could not open temporary file for Shm pool";
return {};
}
if (ftruncate(fd, byteCount) < 0) {
qCDebug(KWAYLAND_KWS) << "Could not set size for Shm pool file";
close(fd);
return {};
}
auto data = mmap(nullptr, byteCount, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
qCDebug(KWAYLAND_KWS) << "Creating Shm pool failed";
close(fd);
return {};
}
auto pool = create_pool(fd, byteCount);
auto *buffer = wl_shm_pool_create_buffer(pool, 0, image.size().width(), image.size().height(), stride, format);
wl_shm_pool_destroy(pool);
const QImage &srcImage = [format, &image] {
if (format == WL_SHM_FORMAT_ARGB8888 && image.format() != QImage::Format_ARGB32_Premultiplied) {
return image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
} else {
return image;
}
}();
std::memcpy(static_cast<char *>(data), srcImage.bits(), byteCount);
munmap(data, byteCount);
close(fd);
return std::make_unique<ShmBuffer>(buffer);
}
@@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 David Redondo <kde@david-redondo.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include "logging.h"
#include <qwayland-wayland.h>
#include <QSize>
#include <QWaylandClientExtensionTemplate>
#include <memory>
class ShmBuffer : public QtWayland::wl_buffer
{
public:
ShmBuffer(::wl_buffer *buffer);
~ShmBuffer();
};
class Shm : public QWaylandClientExtensionTemplate<Shm>, public QtWayland::wl_shm
{
public:
static Shm *instance();
~Shm();
std::unique_ptr<ShmBuffer> createBuffer(const QImage &image);
private:
Shm(QObject *parent);
};
@@ -0,0 +1,28 @@
/*
SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#include <QWindow>
#include <QGuiApplication>
#include <qpa/qplatformnativeinterface.h>
struct wl_surface;
inline wl_surface *surfaceForWindow(QWindow *window)
{
if (!window) {
return nullptr;
}
QPlatformNativeInterface *native = qGuiApp->platformNativeInterface();
if (!native) {
return nullptr;
}
window->create();
return reinterpret_cast<wl_surface *>(native->nativeResourceForWindow(QByteArrayLiteral("surface"), window));
}
@@ -0,0 +1,3 @@
{
"platforms": ["wayland", "wayland-egl"]
}
@@ -0,0 +1,48 @@
/*
SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "waylandxdgactivationv1_p.h"
#include <QGuiApplication>
WaylandXdgActivationV1::WaylandXdgActivationV1()
: QWaylandClientExtensionTemplate<WaylandXdgActivationV1>(1)
{
initialize();
}
WaylandXdgActivationV1::~WaylandXdgActivationV1()
{
if (qGuiApp && isActive()) {
destroy();
}
}
WaylandXdgActivationV1 *WaylandXdgActivationV1::self()
{
static WaylandXdgActivationV1 *instance = new WaylandXdgActivationV1;
return instance;
}
WaylandXdgActivationTokenV1 *
WaylandXdgActivationV1::requestXdgActivationToken(wl_seat *seat, struct ::wl_surface *surface, uint32_t serial, const QString &app_id)
{
auto wl = get_activation_token();
auto provider = new WaylandXdgActivationTokenV1;
provider->init(wl);
if (surface)
provider->set_surface(surface);
if (!app_id.isEmpty())
provider->set_app_id(app_id);
if (seat)
provider->set_serial(serial, seat);
provider->commit();
return provider;
}
#include "moc_waylandxdgactivationv1_p.cpp"
@@ -0,0 +1,43 @@
/*
SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef WAYLANDXDGACTIVATIONV1_P_H
#define WAYLANDXDGACTIVATIONV1_P_H
#include "qwayland-xdg-activation-v1.h"
#include <QObject>
#include <QtWaylandClient/QWaylandClientExtension>
class QWaylandSurface;
class WaylandXdgActivationTokenV1 : public QObject, public QtWayland::xdg_activation_token_v1
{
Q_OBJECT
public:
void xdg_activation_token_v1_done(const QString &token) override
{
Q_EMIT done(token);
}
Q_SIGNALS:
void failed();
void done(const QString &token);
};
class WaylandXdgActivationV1 : public QWaylandClientExtensionTemplate<WaylandXdgActivationV1>, public QtWayland::xdg_activation_v1
{
public:
~WaylandXdgActivationV1() override;
static WaylandXdgActivationV1 *self();
WaylandXdgActivationTokenV1 *requestXdgActivationToken(wl_seat *seat, struct ::wl_surface *surface, uint32_t serial, const QString &app_id);
private:
WaylandXdgActivationV1();
};
#endif
@@ -0,0 +1,107 @@
/*
SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "waylandxdgforeignv2_p.h"
#include <QGuiApplication>
WaylandXdgForeignExportedV2::WaylandXdgForeignExportedV2(::zxdg_exported_v2 *object)
: QObject()
, QtWayland::zxdg_exported_v2(object)
{
}
WaylandXdgForeignExportedV2::~WaylandXdgForeignExportedV2()
{
if (qGuiApp) {
destroy();
}
}
QString WaylandXdgForeignExportedV2::handle() const
{
return m_handle;
}
void WaylandXdgForeignExportedV2::zxdg_exported_v2_handle(const QString &handle)
{
m_handle = handle;
Q_EMIT handleReceived(handle);
}
WaylandXdgForeignExporterV2::WaylandXdgForeignExporterV2()
: QWaylandClientExtensionTemplate<WaylandXdgForeignExporterV2>(1)
{
initialize();
}
WaylandXdgForeignExporterV2::~WaylandXdgForeignExporterV2()
{
if (qGuiApp && isActive()) {
destroy();
}
}
WaylandXdgForeignExporterV2 &WaylandXdgForeignExporterV2::self()
{
static WaylandXdgForeignExporterV2 s_instance;
return s_instance;
}
WaylandXdgForeignExportedV2 *WaylandXdgForeignExporterV2::exportToplevel(wl_surface *surface)
{
return new WaylandXdgForeignExportedV2(export_toplevel(surface));
}
WaylandXdgForeignImportedV2::WaylandXdgForeignImportedV2(const QString &handle, ::zxdg_imported_v2 *object)
: QObject()
, QtWayland::zxdg_imported_v2(object)
, m_handle(handle)
{
}
WaylandXdgForeignImportedV2::~WaylandXdgForeignImportedV2()
{
if (qGuiApp) {
destroy();
}
}
void WaylandXdgForeignImportedV2::zxdg_imported_v2_destroyed()
{
delete this;
}
QString WaylandXdgForeignImportedV2::handle() const
{
return m_handle;
}
WaylandXdgForeignImporterV2::WaylandXdgForeignImporterV2()
: QWaylandClientExtensionTemplate<WaylandXdgForeignImporterV2>(1)
{
initialize();
}
WaylandXdgForeignImporterV2::~WaylandXdgForeignImporterV2()
{
if (qGuiApp && isActive()) {
destroy();
}
}
WaylandXdgForeignImporterV2 &WaylandXdgForeignImporterV2::self()
{
static WaylandXdgForeignImporterV2 s_instance;
return s_instance;
}
WaylandXdgForeignImportedV2 *WaylandXdgForeignImporterV2::importToplevel(const QString &handle)
{
return new WaylandXdgForeignImportedV2(handle, import_toplevel(handle));
}
#include "moc_waylandxdgforeignv2_p.cpp"
@@ -0,0 +1,76 @@
/*
SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef WAYLANDXDGFOREIGNV2_P_H
#define WAYLANDXDGFOREIGNV2_P_H
#include "qwayland-xdg-foreign-unstable-v2.h"
#include <QObject>
#include <QtWaylandClient/QWaylandClientExtension>
class WaylandXdgForeignExportedV2 : public QObject, public QtWayland::zxdg_exported_v2
{
Q_OBJECT
public:
explicit WaylandXdgForeignExportedV2(::zxdg_exported_v2 *object);
~WaylandXdgForeignExportedV2() override;
QString handle() const;
Q_SIGNALS:
void handleReceived(const QString &handle);
protected:
void zxdg_exported_v2_handle(const QString &handle) override;
private:
QString m_handle;
};
class WaylandXdgForeignExporterV2 : public QWaylandClientExtensionTemplate<WaylandXdgForeignExporterV2>, public QtWayland::zxdg_exporter_v2
{
public:
~WaylandXdgForeignExporterV2() override;
static WaylandXdgForeignExporterV2 &self();
WaylandXdgForeignExportedV2 *exportToplevel(struct ::wl_surface *surface);
private:
WaylandXdgForeignExporterV2();
};
class WaylandXdgForeignImportedV2 : public QObject, public QtWayland::zxdg_imported_v2
{
public:
explicit WaylandXdgForeignImportedV2(const QString &handle, ::zxdg_imported_v2 *object);
~WaylandXdgForeignImportedV2() override;
QString handle() const;
protected:
void zxdg_imported_v2_destroyed() override;
private:
QString m_handle;
};
class WaylandXdgForeignImporterV2 : public QWaylandClientExtensionTemplate<WaylandXdgForeignImporterV2>, public QtWayland::zxdg_importer_v2
{
public:
~WaylandXdgForeignImporterV2() override;
static WaylandXdgForeignImporterV2 &self();
WaylandXdgForeignImportedV2 *importToplevel(const QString &handle);
private:
WaylandXdgForeignImporterV2();
};
#endif // WAYLANDXDGFOREIGNV2_P_H
@@ -0,0 +1,400 @@
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "windoweffects.h"
#include <QDebug>
#include <QExposeEvent>
#include <QGuiApplication>
#include <qpa/qplatformwindow_p.h>
#include <QWaylandClientExtensionTemplate>
#include <qwaylandclientextension.h>
#include "qwayland-blur.h"
#include "qwayland-contrast.h"
#include "qwayland-slide.h"
#include "surfacehelper.h"
#include <wayland-client-protocol.h>
static wl_region *createRegion(const QRegion &region)
{
QPlatformNativeInterface *native = qGuiApp->platformNativeInterface();
if (!native) {
return nullptr;
}
auto compositor = reinterpret_cast<wl_compositor *>(native->nativeResourceForIntegration(QByteArrayLiteral("compositor")));
if (!compositor) {
return nullptr;
}
auto wl_region = wl_compositor_create_region(compositor);
for (const auto &rect : region) {
wl_region_add(wl_region, rect.x(), rect.y(), rect.width(), rect.height());
}
return wl_region;
}
class BlurManager : public QWaylandClientExtensionTemplate<BlurManager>, public QtWayland::org_kde_kwin_blur_manager
{
public:
BlurManager()
: QWaylandClientExtensionTemplate<BlurManager>(1)
{
}
};
class Blur : public QObject, public QtWayland::org_kde_kwin_blur
{
public:
Blur(struct ::org_kde_kwin_blur *object, QObject *parent)
: QObject(parent)
, QtWayland::org_kde_kwin_blur(object)
{
}
~Blur() override
{
release();
}
};
class ContrastManager : public QWaylandClientExtensionTemplate<ContrastManager>, public QtWayland::org_kde_kwin_contrast_manager
{
public:
ContrastManager()
: QWaylandClientExtensionTemplate<ContrastManager>(2)
{
}
};
class Contrast : public QObject, public QtWayland::org_kde_kwin_contrast
{
public:
Contrast(struct ::org_kde_kwin_contrast *object, QObject *parent)
: QObject(parent)
, QtWayland::org_kde_kwin_contrast(object)
{
}
~Contrast() override
{
release();
}
};
class SlideManager : public QWaylandClientExtensionTemplate<SlideManager>, public QtWayland::org_kde_kwin_slide_manager
{
public:
SlideManager()
: QWaylandClientExtensionTemplate<SlideManager>(1)
{
}
};
class Slide : public QObject, public QtWayland::org_kde_kwin_slide
{
public:
Slide(struct ::org_kde_kwin_slide *object, QObject *parent)
: QObject(parent)
, QtWayland::org_kde_kwin_slide(object)
{
}
~Slide() override
{
release();
}
};
WindowEffects::WindowEffects()
: QObject()
, KWindowEffectsPrivate()
{
m_blurManager = new BlurManager();
m_contrastManager = new ContrastManager();
m_slideManager = new SlideManager();
// The KWindowEffects API doesn't provide any signals to notify that the particular
// effect has become unavailable. So we re-install effects when the corresponding globals
// are added.
connect(m_blurManager, &BlurManager::activeChanged, this, [this] {
for (auto it = m_blurRegions.constBegin(); it != m_blurRegions.constEnd(); ++it) {
installBlur(it.key(), m_blurManager->isActive(), *it);
}
});
connect(m_contrastManager, &ContrastManager::activeChanged, this, [this] {
for (auto it = m_backgroundConstrastRegions.constBegin(); it != m_backgroundConstrastRegions.constEnd(); ++it) {
if (m_contrastManager->isActive()) {
installContrast(it.key(), true, it->contrast, it->intensity, it->saturation, it->region);
} else {
installContrast(it.key(), false);
}
}
});
connect(m_slideManager, &SlideManager::activeChanged, this, [this] {
for (auto it = m_slideMap.constBegin(); it != m_slideMap.constEnd(); ++it) {
if (m_slideManager->isActive()) {
installSlide(it.key(), it->location, it->offset);
} else {
installSlide(it.key(), KWindowEffects::SlideFromLocation::NoEdge, 0);
}
}
});
}
WindowEffects::~WindowEffects()
{
delete m_blurManager;
delete m_contrastManager;
delete m_slideManager;
}
void WindowEffects::trackWindow(QWindow *window)
{
if (!m_windowWatchers.contains(window)) {
window->installEventFilter(this);
auto conn = connect(window, &QObject::destroyed, this, [this, window]() {
resetBlur(window);
m_blurRegions.remove(window);
resetContrast(window);
m_backgroundConstrastRegions.remove(window);
m_slideMap.remove(window);
m_windowWatchers.remove(window);
});
m_windowWatchers[window] << conn;
auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
if (waylandWindow) {
auto conn = connect(waylandWindow, &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed, this, [this, window]() {
resetBlur(window);
resetContrast(window);
});
m_windowWatchers[window] << conn;
}
}
}
void WindowEffects::releaseWindow(QWindow *window)
{
if (!m_blurRegions.contains(window) && !m_backgroundConstrastRegions.contains(window) && !m_slideMap.contains(window)) {
for (const auto &conn : m_windowWatchers[window]) {
disconnect(conn);
}
window->removeEventFilter(this);
m_windowWatchers.remove(window);
}
}
// Helper function to replace a QObject value in the map and delete the old one.
template<typename MapType>
void replaceValue(MapType &map, typename MapType::key_type key, typename MapType::mapped_type value)
{
if (auto oldValue = map.take(key)) {
oldValue->deleteLater();
}
if (value) {
map[key] = value;
}
}
void WindowEffects::resetBlur(QWindow *window, Blur *blur)
{
replaceValue(m_blurs, window, blur);
}
void WindowEffects::resetContrast(QWindow *window, Contrast *contrast)
{
replaceValue(m_contrasts, window, contrast);
}
bool WindowEffects::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Expose) {
auto window = qobject_cast<QWindow *>(watched);
if (!window || !window->isExposed()) {
return false;
}
{
auto it = m_blurRegions.constFind(window);
if (it != m_blurRegions.constEnd()) {
installBlur(window, true, *it);
}
}
{
auto it = m_backgroundConstrastRegions.constFind(window);
if (it != m_backgroundConstrastRegions.constEnd()) {
installContrast(window, true, it->contrast, it->intensity, it->saturation, it->region);
}
}
{
auto it = m_slideMap.constFind(window);
if (it != m_slideMap.constEnd()) {
installSlide(window, it->location, it->offset);
}
}
}
return false;
}
bool WindowEffects::isEffectAvailable(KWindowEffects::Effect effect)
{
switch (effect) {
case KWindowEffects::BackgroundContrast:
return m_contrastManager->isActive();
case KWindowEffects::BlurBehind:
return m_blurManager->isActive();
case KWindowEffects::Slide:
return m_slideManager->isActive();
default:
return false;
}
}
void WindowEffects::slideWindow(QWindow *window, KWindowEffects::SlideFromLocation location, int offset)
{
if (location != KWindowEffects::SlideFromLocation::NoEdge) {
m_slideMap[window] = SlideData{
.location = location,
.offset = offset,
};
trackWindow(window);
} else {
m_slideMap.remove(window);
releaseWindow(window);
}
installSlide(window, location, offset);
}
void WindowEffects::installSlide(QWindow *window, KWindowEffects::SlideFromLocation location, int offset)
{
if (!m_slideManager->isActive()) {
return;
}
wl_surface *surface = surfaceForWindow(window);
if (surface) {
if (location != KWindowEffects::SlideFromLocation::NoEdge) {
auto slide = new Slide(m_slideManager->create(surface), window);
Slide::location convertedLoc;
switch (location) {
case KWindowEffects::SlideFromLocation::TopEdge:
convertedLoc = Slide::location::location_top;
break;
case KWindowEffects::SlideFromLocation::LeftEdge:
convertedLoc = Slide::location::location_left;
break;
case KWindowEffects::SlideFromLocation::RightEdge:
convertedLoc = Slide::location::location_right;
break;
case KWindowEffects::SlideFromLocation::BottomEdge:
default:
convertedLoc = Slide::location::location_bottom;
break;
}
slide->set_location(convertedLoc);
slide->set_offset(offset);
slide->commit();
} else {
m_slideManager->unset(surface);
}
}
}
void WindowEffects::enableBlurBehind(QWindow *window, bool enable, const QRegion &region)
{
if (enable) {
trackWindow(window);
m_blurRegions[window] = region;
} else {
resetBlur(window);
m_blurRegions.remove(window);
releaseWindow(window);
}
installBlur(window, enable, region);
}
void WindowEffects::installBlur(QWindow *window, bool enable, const QRegion &region)
{
if (!m_blurManager->isActive()) {
return;
}
wl_surface *surface = surfaceForWindow(window);
if (surface) {
if (enable) {
auto wl_region = createRegion(region);
if (!wl_region) {
return;
}
auto blur = new Blur(m_blurManager->create(surface), window);
blur->set_region(wl_region);
blur->commit();
wl_region_destroy(wl_region);
resetBlur(window, blur);
} else {
resetBlur(window);
m_blurManager->unset(surface);
}
}
}
void WindowEffects::enableBackgroundContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
{
if (enable) {
trackWindow(window);
m_backgroundConstrastRegions[window].contrast = contrast;
m_backgroundConstrastRegions[window].intensity = intensity;
m_backgroundConstrastRegions[window].saturation = saturation;
m_backgroundConstrastRegions[window].region = region;
} else {
resetContrast(window);
m_backgroundConstrastRegions.remove(window);
releaseWindow(window);
}
installContrast(window, enable, contrast, intensity, saturation, region);
}
void WindowEffects::installContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
{
if (!m_contrastManager->isActive()) {
return;
}
wl_surface *surface = surfaceForWindow(window);
if (surface) {
if (enable) {
auto wl_region = createRegion(region);
if (!wl_region) {
return;
}
auto backgroundContrast = new Contrast(m_contrastManager->create(surface), window);
backgroundContrast->set_region(wl_region);
backgroundContrast->set_contrast(wl_fixed_from_double(contrast));
backgroundContrast->set_intensity(wl_fixed_from_double(intensity));
backgroundContrast->set_saturation(wl_fixed_from_double(saturation));
backgroundContrast->commit();
wl_region_destroy(wl_region);
resetContrast(window, backgroundContrast);
} else {
resetContrast(window);
m_contrastManager->unset(surface);
}
}
}
#include "moc_windoweffects.cpp"
@@ -0,0 +1,72 @@
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef WINDOWEFFECTS_H
#define WINDOWEFFECTS_H
#include "kwindoweffects_p.h"
#include <kwindowsystem_version.h>
#include <QHash>
#include <QObject>
#include <QPointer>
class BlurManager;
class Blur;
class ContrastManager;
class Contrast;
class SlideManager;
class WindowEffects : public QObject, public KWindowEffectsPrivate
{
Q_OBJECT
public:
WindowEffects();
~WindowEffects() override;
bool eventFilter(QObject *watched, QEvent *event) override;
void trackWindow(QWindow *window);
void releaseWindow(QWindow *window);
bool isEffectAvailable(KWindowEffects::Effect effect) override;
void slideWindow(QWindow *window, KWindowEffects::SlideFromLocation location, int offset) override;
void enableBlurBehind(QWindow *window, bool enable = true, const QRegion &region = QRegion()) override;
void enableBackgroundContrast(QWindow *window,
bool enable = true,
qreal contrast = 1,
qreal intensity = 1,
qreal saturation = 1,
const QRegion &region = QRegion()) override;
private:
void installContrast(QWindow *window, bool enable = true, qreal contrast = 1, qreal intensity = 1, qreal saturation = 1, const QRegion &region = QRegion());
void installBlur(QWindow *window, bool enable, const QRegion &region);
void installSlide(QWindow *window, KWindowEffects::SlideFromLocation location, int offset);
void resetBlur(QWindow *window, Blur *blur = nullptr);
void resetContrast(QWindow *window, Contrast *contrast = nullptr);
QHash<QWindow *, QList<QMetaObject::Connection>> m_windowWatchers;
QHash<QWindow *, QRegion> m_blurRegions;
struct BackgroundContrastData {
qreal contrast = 1;
qreal intensity = 1;
qreal saturation = 1;
QRegion region;
};
QHash<QWindow *, BackgroundContrastData> m_backgroundConstrastRegions;
QHash<QWindow *, QPointer<Blur>> m_blurs;
QHash<QWindow *, QPointer<Contrast>> m_contrasts;
struct SlideData {
KWindowEffects::SlideFromLocation location;
int offset;
};
QHash<QWindow *, SlideData> m_slideMap;
BlurManager *m_blurManager;
ContrastManager *m_contrastManager;
SlideManager *m_slideManager;
};
#endif
@@ -0,0 +1,214 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-FileCopyrightText: 2023 David Redondo <kde@david-redondo.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "windowshadow.h"
#include "logging.h"
#include "shm.h"
#include "surfacehelper.h"
#include <qwayland-shadow.h>
#include <QDebug>
#include <QExposeEvent>
#include <QWaylandClientExtension>
#include <qpa/qplatformwindow_p.h>
class ShadowManager : public QWaylandClientExtensionTemplate<ShadowManager>, public QtWayland::org_kde_kwin_shadow_manager
{
Q_OBJECT
static constexpr int version = 2;
explicit ShadowManager(QObject *parent = nullptr)
: QWaylandClientExtensionTemplate(version)
{
setParent(parent);
initialize();
connect(this, &QWaylandClientExtension::activeChanged, this, [this] {
if (!isActive()) {
destroy();
}
});
}
public:
~ShadowManager()
{
if (isActive()) {
destroy();
}
}
static ShadowManager *instance()
{
static ShadowManager *instance = new ShadowManager(qGuiApp);
return instance;
}
};
class Shadow : public QtWayland::org_kde_kwin_shadow
{
public:
using QtWayland::org_kde_kwin_shadow::org_kde_kwin_shadow;
~Shadow()
{
destroy();
}
};
WindowShadowTile::WindowShadowTile()
{
connect(Shm::instance(), &Shm::activeChanged, this, [this] {
if (Shm::instance()->isActive()) {
buffer.reset();
}
});
}
WindowShadowTile::~WindowShadowTile()
{
}
bool WindowShadowTile::create()
{
if (!Shm::instance()->isActive()) {
return false;
}
buffer = Shm::instance()->createBuffer(image);
return true;
}
void WindowShadowTile::destroy()
{
buffer.reset();
}
WindowShadowTile *WindowShadowTile::get(const KWindowShadowTile *tile)
{
KWindowShadowTilePrivate *d = KWindowShadowTilePrivate::get(tile);
return static_cast<WindowShadowTile *>(d);
}
static wl_buffer *bufferForTile(const KWindowShadowTile::Ptr &tile)
{
if (!tile) {
return nullptr;
}
WindowShadowTile *d = WindowShadowTile::get(tile.data());
// Our buffer has been deleted in the meantime, try to create it again
if (!d->buffer && d->isCreated) {
d->buffer = Shm::instance()->createBuffer(d->image);
}
return d->buffer ? d->buffer.get()->object() : nullptr;
}
WindowShadow::WindowShadow()
{
}
WindowShadow::~WindowShadow()
{
}
bool WindowShadow::eventFilter(QObject *watched, QEvent *event)
{
Q_UNUSED(watched)
if (event->type() == QEvent::Expose) {
if (auto window = qobject_cast<QWindow *>(watched); window && window->isExposed()) {
if (!internalCreate()) {
qCWarning(KWAYLAND_KWS) << "Failed to recreate shadow for" << window;
}
}
}
return false;
}
bool WindowShadow::internalCreate()
{
if (shadow) {
return true;
}
if (!ShadowManager::instance()->isActive()) {
return false;
}
auto surface = surfaceForWindow(window);
if (!surface) {
return false;
}
shadow = std::make_unique<Shadow>(ShadowManager::instance()->create(surface));
auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
if (waylandWindow) {
connect(waylandWindow, &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed, this, &WindowShadow::internalDestroy, Qt::UniqueConnection);
}
auto attach = [](const std::unique_ptr<Shadow> &shadow, auto attach_func, const KWindowShadowTile::Ptr &tile) {
if (auto buffer = bufferForTile(tile)) {
(*shadow.*attach_func)(buffer);
}
};
attach(shadow, &Shadow::attach_left, leftTile);
attach(shadow, &Shadow::attach_top_left, topLeftTile);
attach(shadow, &Shadow::attach_top, topTile);
attach(shadow, &Shadow::attach_top_right, topRightTile);
attach(shadow, &Shadow::attach_right, rightTile);
attach(shadow, &Shadow::attach_bottom_right, bottomRightTile);
attach(shadow, &Shadow::attach_bottom, bottomTile);
attach(shadow, &Shadow::attach_bottom_left, bottomLeftTile);
shadow->set_left_offset(wl_fixed_from_double(padding.left()));
shadow->set_top_offset(wl_fixed_from_double(padding.top()));
shadow->set_right_offset(wl_fixed_from_double(padding.right()));
shadow->set_bottom_offset(wl_fixed_from_double(padding.bottom()));
shadow->commit();
// Commit wl_surface at the next available time.
window->requestUpdate();
return true;
}
bool WindowShadow::create()
{
if (!ShadowManager::instance()->isActive()) {
return false;
}
internalCreate();
window->installEventFilter(this);
return true;
}
void WindowShadow::internalDestroy()
{
if (!shadow) {
return;
}
// Only call surfaceForWindow and unset the surface if the native window is alive.
// Otherwise window->create() might be called when the window is being destroyed, leading to a crash.
if (window && window->nativeInterface<QNativeInterface::Private::QWaylandWindow>() && ShadowManager::instance()->isActive()) {
if (auto surface = surfaceForWindow(window)) {
ShadowManager::instance()->unset(surface);
}
}
shadow.reset();
if (window && window->isVisible()) {
window->requestUpdate();
}
}
void WindowShadow::destroy()
{
if (window) {
window->removeEventFilter(this);
}
internalDestroy();
}
#include "windowshadow.moc"
@@ -0,0 +1,50 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef WINDOWSHADOW_H
#define WINDOWSHADOW_H
#include "kwindowshadow_p.h"
#include <memory>
class Shadow;
class ShmBuffer;
class Shm;
class WindowShadowTile final : public QObject, public KWindowShadowTilePrivate
{
public:
WindowShadowTile();
~WindowShadowTile();
bool create() override;
void destroy() override;
static WindowShadowTile *get(const KWindowShadowTile *tile);
std::unique_ptr<ShmBuffer> buffer;
};
class WindowShadow final : public QObject, public KWindowShadowPrivate
{
public:
WindowShadow();
~WindowShadow() override;
bool create() override;
void destroy() override;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
bool internalCreate();
void internalDestroy();
std::unique_ptr<Shadow> shadow;
};
#endif // WINDOWSHADOW_H
@@ -0,0 +1,289 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "windowsystem.h"
#include "logging.h"
#include "surfacehelper.h"
#include "waylandxdgactivationv1_p.h"
#include "waylandxdgforeignv2_p.h"
#include <KWaylandExtras>
#include <KWindowSystem>
#include "qwayland-plasma-window-management.h"
#include <QEvent>
#include <QGuiApplication>
#include <QPixmap>
#include <QPoint>
#include <QString>
#include <QTimer>
#include <QWaylandClientExtensionTemplate>
#include <QWindow>
#include <qpa/qplatformnativeinterface.h>
#include <qpa/qplatformwindow_p.h>
constexpr const char *c_kdeXdgForeignExportedProperty("_kde_xdg_foreign_exported_v2");
constexpr const char *c_kdeXdgForeignImportedProperty("_kde_xdg_foreign_imported_v2");
constexpr const char *c_kdeXdgForeignPendingHandleProperty("_kde_xdg_foreign_pending_handle");
class WindowManagement : public QWaylandClientExtensionTemplate<WindowManagement>, public QtWayland::org_kde_plasma_window_management
{
public:
WindowManagement()
: QWaylandClientExtensionTemplate<WindowManagement>(17)
{
}
void org_kde_plasma_window_management_show_desktop_changed(uint32_t state) override
{
showingDesktop = state == show_desktop_enabled;
KWindowSystem::self()->showingDesktopChanged(showingDesktop);
}
bool showingDesktop = false;
};
WindowSystem::WindowSystem()
: QObject()
, KWindowSystemPrivateV2()
, m_lastToken(qEnvironmentVariable("XDG_ACTIVATION_TOKEN"))
{
m_windowManagement = new WindowManagement;
}
WindowSystem::~WindowSystem()
{
delete m_windowManagement;
}
void WindowSystem::activateWindow(QWindow *win, long int time)
{
Q_UNUSED(time);
auto s = surfaceForWindow(win);
if (!s) {
return;
}
WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
if (!activation->isActive()) {
return;
}
activation->activate(m_lastToken, s);
}
void WindowSystem::requestToken(QWindow *window, uint32_t serial, const QString &app_id)
{
if (window) {
window->create();
}
wl_surface *wlSurface = surfaceForWindow(window);
WaylandXdgActivationV1 *activation = WaylandXdgActivationV1::self();
if (!activation->isActive()) {
// Ensure that xdgActivationTokenArrived is always emitted asynchronously
QTimer::singleShot(0, [serial] {
Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, {});
});
return;
}
auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
auto seat = waylandApp ? waylandApp->lastInputSeat() : nullptr;
auto tokenReq = activation->requestXdgActivationToken(seat, wlSurface, serial, app_id);
connect(tokenReq, &WaylandXdgActivationTokenV1::failed, KWindowSystem::self(), [serial, app_id]() {
Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, {});
});
connect(tokenReq, &WaylandXdgActivationTokenV1::done, KWindowSystem::self(), [serial](const QString &token) {
Q_EMIT KWaylandExtras::self()->xdgActivationTokenArrived(serial, token);
});
}
void WindowSystem::setCurrentToken(const QString &token)
{
m_lastToken = token;
}
quint32 WindowSystem::lastInputSerial(QWindow *window)
{
Q_UNUSED(window)
if (auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()) {
return waylandApp->lastInputSerial();
}
return 0;
}
void WindowSystem::setShowingDesktop(bool showing)
{
if (!m_windowManagement->isActive()) {
return;
}
m_windowManagement->show_desktop(showing ? WindowManagement::show_desktop_enabled : WindowManagement::show_desktop_disabled);
}
bool WindowSystem::showingDesktop()
{
if (!m_windowManagement->isActive()) {
return false;
}
return m_windowManagement->showingDesktop;
}
void WindowSystem::exportWindow(QWindow *window)
{
auto emitHandle = [window](const QString &handle) {
// Ensure that windowExported is always emitted asynchronously.
QMetaObject::invokeMethod(
window,
[window, handle] {
Q_EMIT KWaylandExtras::self()->windowExported(window, handle);
},
Qt::QueuedConnection);
};
if (!window) {
return;
}
window->create();
auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
if (!waylandWindow) {
emitHandle({});
return;
}
auto &exporter = WaylandXdgForeignExporterV2::self();
if (!exporter.isActive()) {
emitHandle({});
return;
}
// We want to use QObject::property(char*) and use dynamic properties on the object rather than
// call QWaylandWindow::property(QString) and send it around.
WaylandXdgForeignExportedV2 *exported = waylandWindow->property(c_kdeXdgForeignExportedProperty).value<WaylandXdgForeignExportedV2 *>();
if (!exported) {
exported = exporter.exportToplevel(surfaceForWindow(window));
exported->setParent(waylandWindow);
waylandWindow->setProperty(c_kdeXdgForeignExportedProperty, QVariant::fromValue(exported));
connect(exported, &QObject::destroyed, waylandWindow, [waylandWindow] {
waylandWindow->setProperty(c_kdeXdgForeignExportedProperty, QVariant());
});
connect(exported, &WaylandXdgForeignExportedV2::handleReceived, window, [window](const QString &handle) {
Q_EMIT KWaylandExtras::self()->windowExported(window, handle);
});
}
if (!exported->handle().isEmpty()) {
emitHandle(exported->handle());
}
}
void WindowSystem::unexportWindow(QWindow *window)
{
auto waylandWindow = window ? window->nativeInterface<QNativeInterface::Private::QWaylandWindow>() : nullptr;
if (!waylandWindow) {
return;
}
WaylandXdgForeignExportedV2 *exported = waylandWindow->property(c_kdeXdgForeignExportedProperty).value<WaylandXdgForeignExportedV2 *>();
delete exported;
Q_ASSERT(!waylandWindow->property(c_kdeXdgForeignExportedProperty).isValid());
}
void WindowSystem::setMainWindow(QWindow *window, const QString &handle)
{
if (!window) {
return;
}
window->create();
auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
if (!waylandWindow) {
return;
}
// We want to use QObject::property(char*) and use dynamic properties on the object rather than
// call QWaylandWindow::property(QString) and send it around.
auto *imported = waylandWindow->property(c_kdeXdgForeignImportedProperty).value<WaylandXdgForeignImportedV2 *>();
// Window already parented with a different handle? Delete imported so we import the new one later.
if (imported && imported->handle() != handle) {
delete imported;
imported = nullptr;
Q_ASSERT(!waylandWindow->property(c_kdeXdgForeignImportedProperty).isValid());
}
// Don't bother.
if (handle.isEmpty()) {
return;
}
if (window->isExposed()) {
doSetMainWindow(window, handle);
} else {
// We can only import an XDG toplevel.
// QWaylandWindow::surfaceRoleCreated is only in Qt 6.8,
// in earlier versions wait for the window be exposed,
// since QWaylandWindow::wlSurfaceCreated is too early.
#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
window->setProperty(c_kdeXdgForeignPendingHandleProperty, handle);
window->installEventFilter(this);
#else
connect(waylandWindow, &QNativeInterface::Private::QWaylandWindow::surfaceRoleCreated, window, [window, handle] {
doSetMainWindow(window, handle);
});
#endif
}
}
bool WindowSystem::eventFilter(QObject *watched, QEvent *event)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
if (event->type() == QEvent::Expose) {
auto *window = static_cast<QWindow *>(watched);
if (window->isExposed()) {
const QString handle = window->property(c_kdeXdgForeignPendingHandleProperty).toString();
if (!handle.isEmpty()) {
doSetMainWindow(window, handle);
window->setProperty(c_kdeXdgForeignPendingHandleProperty, QVariant());
}
window->removeEventFilter(this);
}
}
#endif
return QObject::eventFilter(watched, event);
}
void WindowSystem::doSetMainWindow(QWindow *window, const QString &handle)
{
Q_ASSERT(window);
Q_ASSERT(!handle.isEmpty());
auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
if (!waylandWindow) {
return;
}
auto &importer = WaylandXdgForeignImporterV2::self();
if (!importer.isActive()) {
return;
}
Q_ASSERT(!waylandWindow->property(c_kdeXdgForeignImportedProperty).isValid());
WaylandXdgForeignImportedV2 *imported = importer.importToplevel(handle);
imported->set_parent_of(surfaceForWindow(window)); // foreign parent.
imported->setParent(waylandWindow); // memory owner.
waylandWindow->setProperty(c_kdeXdgForeignImportedProperty, QVariant::fromValue(imported));
connect(imported, &QObject::destroyed, waylandWindow, [waylandWindow] {
waylandWindow->setProperty(c_kdeXdgForeignImportedProperty, QVariant());
});
}
#include "moc_windowsystem.cpp"
@@ -0,0 +1,40 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef WINDOWSYSTEM_H
#define WINDOWSYSTEM_H
#include "kwindowsystem_p.h"
#include <QObject>
class WindowManagement;
class WindowSystem : public QObject, public KWindowSystemPrivateV2
{
Q_OBJECT
public:
WindowSystem();
~WindowSystem() override;
void activateWindow(QWindow *win, long time) override;
void requestToken(QWindow *win, uint32_t serial, const QString &app_id) override;
quint32 lastInputSerial(QWindow *window) override;
void setCurrentToken(const QString &token) override;
bool showingDesktop() override;
void setShowingDesktop(bool showing) override;
void exportWindow(QWindow *window) override;
void unexportWindow(QWindow *window) override;
void setMainWindow(QWindow *window, const QString &handle) override;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
static void doSetMainWindow(QWindow *window, const QString &handle);
QString m_lastToken;
WindowManagement *m_windowManagement;
};
#endif
@@ -0,0 +1,52 @@
add_library(KF6WindowSystemX11Plugin MODULE)
target_sources(KF6WindowSystemX11Plugin PRIVATE
kwindoweffects.cpp
kwindowshadow.cpp
kwindowsystem.cpp
kxutils.cpp
plugin.cpp
)
target_link_libraries(KF6WindowSystemX11Plugin
PRIVATE
KF6WindowSystem
XCB::XCB
XCB::RES
${X11_LIBRARIES}
${X11_Xfixes_LIB}
Qt6::GuiPrivate
)
ecm_generate_headers(KWindowSystemX11_HEADERS
HEADER_NAMES
KSelectionOwner
KSelectionWatcher
KXMessages
NETWM # does not match the classnames in that file...
REQUIRED_HEADERS
KWindowSystemX11_HEADERS
)
install(
FILES
${KWindowSystemX11_HEADERS}
fixx11h.h
DESTINATION
${KDE_INSTALL_INCLUDEDIR_KF}/KWindowSystem
COMPONENT
Devel
)
set_target_properties(
KF6WindowSystemX11Plugin
PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/kf6/kwindowsystem"
)
install(
TARGETS
KF6WindowSystemX11Plugin
DESTINATION
${KDE_INSTALL_PLUGINDIR}/kf6/kwindowsystem/
)
@@ -0,0 +1,177 @@
/*
SPDX-FileCopyrightText: 2015 Thomas Lübking <thomas.luebking@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#if (!defined ATOMS_H) || (defined ENUM_CREATE_CHAR_ARRAY)
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#undef ENUM_COUNT
// the following macros are set in a way so that
// the code below will either construct an enum for "<typ>"
// or a const *char array "<typ>Strings" containing all enum
// symbols as strings, depending on whether ENUM_CREATE_CHAR_ARRAY is
// defined
// The enum gets one extra item "<typ>Count", describing also the
// length of the array
// The header is safe for re-inclusion unless you define ENUM_CREATE_CHAR_ARRAY
// which is therefore undefined after usage
// => You *must* "#define ENUM_CREATE_CHAR_ARRAY 1" *every* time you want to create
// a string array!
// clang-format off
#ifndef ENUM_CREATE_CHAR_ARRAY
#define ATOMS_H
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_COUNT(typ) , typ##Count
#else
#define ENUM_BEGIN(typ) const char * typ##Strings [] = {
#define ENUM(nam) #nam
#define ENUM_COUNT(typ)
#undef ENUM_CREATE_CHAR_ARRAY
#endif
#define ENUM_END(typ) };
ENUM_BEGIN(KwsAtom)
ENUM(UTF8_STRING),
// root window properties
ENUM(_NET_SUPPORTED),
ENUM(_NET_SUPPORTING_WM_CHECK),
ENUM(_NET_CLIENT_LIST),
ENUM(_NET_CLIENT_LIST_STACKING),
ENUM(_NET_NUMBER_OF_DESKTOPS),
ENUM(_NET_DESKTOP_GEOMETRY),
ENUM(_NET_DESKTOP_VIEWPORT),
ENUM(_NET_CURRENT_DESKTOP),
ENUM(_NET_DESKTOP_NAMES),
ENUM(_NET_ACTIVE_WINDOW),
ENUM(_NET_WORKAREA),
ENUM(_NET_VIRTUAL_ROOTS),
ENUM(_NET_DESKTOP_LAYOUT),
ENUM(_NET_SHOWING_DESKTOP),
// root window messages
ENUM(_NET_CLOSE_WINDOW),
ENUM(_NET_RESTACK_WINDOW),
ENUM(_NET_WM_MOVERESIZE),
ENUM(_NET_MOVERESIZE_WINDOW),
// application window properties
ENUM(_NET_WM_NAME),
ENUM(_NET_WM_VISIBLE_NAME),
ENUM(_NET_WM_ICON_NAME),
ENUM(_NET_WM_VISIBLE_ICON_NAME),
ENUM(_NET_WM_DESKTOP),
ENUM(_NET_WM_WINDOW_TYPE),
ENUM(_NET_WM_STATE),
ENUM(_NET_WM_STRUT),
ENUM(_NET_WM_STRUT_PARTIAL),
ENUM(_NET_WM_ICON_GEOMETRY),
ENUM(_NET_WM_ICON),
ENUM(_NET_WM_PID),
ENUM(_NET_WM_USER_TIME),
ENUM(_NET_WM_HANDLED_ICONS),
ENUM(_NET_STARTUP_ID),
ENUM(_NET_WM_ALLOWED_ACTIONS),
ENUM(WM_WINDOW_ROLE),
ENUM(_NET_FRAME_EXTENTS),
ENUM(_NET_WM_WINDOW_OPACITY),
ENUM(_NET_WM_FULLSCREEN_MONITORS),
ENUM(_NET_WM_OPAQUE_REGION),
ENUM(_KDE_NET_WM_DESKTOP_FILE),
// used to determine whether application window is managed or not
ENUM(WM_STATE),
// application window types
ENUM(_NET_WM_WINDOW_TYPE_NORMAL),
ENUM(_NET_WM_WINDOW_TYPE_DESKTOP),
ENUM(_NET_WM_WINDOW_TYPE_DOCK),
ENUM(_NET_WM_WINDOW_TYPE_TOOLBAR),
ENUM(_NET_WM_WINDOW_TYPE_MENU),
ENUM(_NET_WM_WINDOW_TYPE_DIALOG),
ENUM(_NET_WM_WINDOW_TYPE_UTILITY),
ENUM(_NET_WM_WINDOW_TYPE_SPLASH),
ENUM(_NET_WM_WINDOW_TYPE_DROPDOWN_MENU),
ENUM(_NET_WM_WINDOW_TYPE_POPUP_MENU),
ENUM(_NET_WM_WINDOW_TYPE_TOOLTIP),
ENUM(_NET_WM_WINDOW_TYPE_NOTIFICATION),
ENUM(_NET_WM_WINDOW_TYPE_COMBO),
ENUM(_NET_WM_WINDOW_TYPE_DND),
// application window state
ENUM(_NET_WM_STATE_MODAL),
ENUM(_NET_WM_STATE_STICKY),
ENUM(_NET_WM_STATE_MAXIMIZED_VERT),
ENUM(_NET_WM_STATE_MAXIMIZED_HORZ),
ENUM(_NET_WM_STATE_SHADED),
ENUM(_NET_WM_STATE_SKIP_TASKBAR),
ENUM(_NET_WM_STATE_SKIP_PAGER),
ENUM(_NET_WM_STATE_HIDDEN),
ENUM(_NET_WM_STATE_FULLSCREEN),
ENUM(_NET_WM_STATE_ABOVE),
ENUM(_NET_WM_STATE_BELOW),
ENUM(_NET_WM_STATE_DEMANDS_ATTENTION),
ENUM(_NET_WM_STATE_FOCUSED),
// KDE-specific atom
ENUM(_KDE_NET_WM_STATE_SKIP_SWITCHER),
// allowed actions
ENUM(_NET_WM_ACTION_MOVE),
ENUM(_NET_WM_ACTION_RESIZE),
ENUM(_NET_WM_ACTION_MINIMIZE),
ENUM(_NET_WM_ACTION_SHADE),
ENUM(_NET_WM_ACTION_STICK),
ENUM(_NET_WM_ACTION_MAXIMIZE_VERT),
ENUM(_NET_WM_ACTION_MAXIMIZE_HORZ),
ENUM(_NET_WM_ACTION_FULLSCREEN),
ENUM(_NET_WM_ACTION_CHANGE_DESKTOP),
ENUM(_NET_WM_ACTION_CLOSE),
// KDE extensions
ENUM(_KDE_NET_WM_FRAME_STRUT),
ENUM(_KDE_NET_WM_WINDOW_TYPE_OVERRIDE),
ENUM(_KDE_NET_WM_WINDOW_TYPE_TOPMENU),
ENUM(_KDE_NET_WM_WINDOW_TYPE_ON_SCREEN_DISPLAY),
ENUM(_KDE_NET_WM_WINDOW_TYPE_CRITICAL_NOTIFICATION),
ENUM(_KDE_NET_WM_WINDOW_TYPE_APPLET_POPUP),
ENUM(_KDE_NET_WM_TEMPORARY_RULES),
ENUM(_NET_WM_FRAME_OVERLAP),
ENUM(_KDE_NET_WM_APPMENU_SERVICE_NAME),
ENUM(_KDE_NET_WM_APPMENU_OBJECT_PATH),
// deprecated and naming convention violation
ENUM(_NET_WM_STATE_STAYS_ON_TOP),
// GTK extensions
ENUM(_GTK_FRAME_EXTENTS),
ENUM(_GTK_APPLICATION_ID),
ENUM(_GTK_SHOW_WINDOW_MENU),
// application protocols
ENUM(WM_PROTOCOLS),
ENUM(WM_TAKE_FOCUS),
ENUM(WM_DELETE_WINDOW),
ENUM(_NET_WM_PING),
ENUM(_NET_WM_SYNC_REQUEST),
ENUM(_NET_WM_CONTEXT_HELP),
// ability flags
ENUM(_NET_WM_FULL_PLACEMENT),
ENUM(_NET_WM_BYPASS_COMPOSITOR),
ENUM(_KDE_NET_WM_ACTIVITIES),
ENUM(_KDE_NET_WM_BLOCK_COMPOSITING),
ENUM(_KDE_NET_WM_SHADOW)
ENUM_COUNT(KwsAtom)
ENUM_END(KwsAtom)
#endif // ATOMS_H
@@ -0,0 +1,278 @@
/*
SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: MIT
*/
//#ifdef don't do this, this file is supposed to be included
//#define multiple times
#include <QtGlobal>
/* Usage:
If you get compile errors caused by X11 includes (the line
where first error appears contains word like None, Unsorted,
Below, etc.), put #include <fixx11h.h> in the .cpp file
(not .h file!) between the place where X11 headers are
included and the place where the file with compile
error is included (or the place where the compile error
in the .cpp file occurs).
This file remaps X11 #defines to const variables or
inline functions. The side effect may be that these
symbols may now refer to different variables
(e.g. if X11 #defined NoButton, after this file
is included NoButton would no longer be X11's
NoButton, but Qt::NoButton instead). At this time,
there's no conflict known that could cause problems.
The original X11 symbols are still accessible
(e.g. for None) as X::None, XNone, and also still
None, unless name lookup finds different None
first (in the current class, etc.)
Use 'Unsorted', 'Bool' and 'index' as templates.
*/
namespace X
{
// template --->
// Affects: Should be without side effects.
#ifdef Unsorted
#ifndef FIXX11H_Unsorted
#define FIXX11H_Unsorted
const int XUnsorted = Unsorted;
#undef Unsorted
const int Unsorted = XUnsorted;
#endif
#undef Unsorted
#endif
// template <---
// Affects: Should be without side effects.
#ifdef None
#ifndef FIXX11H_None
#define FIXX11H_None
const XID XNone = None;
#undef None
const XID None = XNone;
#endif
#undef None
#endif
// template --->
// Affects: Should be without side effects.
#ifdef Bool
#ifndef FIXX11H_Bool
#define FIXX11H_Bool
#ifdef _XTYPEDEF_BOOL /* Xdefs.h has typedef'ed Bool already */
#undef Bool
#else
typedef Bool XBool;
#undef Bool
typedef XBool Bool;
#endif
#endif
#undef Bool
#define _XTYPEDEF_BOOL
#endif
// template <---
// Affects: Should be without side effects.
#ifdef KeyPress
#ifndef FIXX11H_KeyPress
#define FIXX11H_KeyPress
const int XKeyPress = KeyPress;
#undef KeyPress
const int KeyPress = XKeyPress;
#endif
#undef KeyPress
#endif
// Affects: Should be without side effects.
#ifdef KeyRelease
#ifndef FIXX11H_KeyRelease
#define FIXX11H_KeyRelease
const int XKeyRelease = KeyRelease;
#undef KeyRelease
const int KeyRelease = XKeyRelease;
#endif
#undef KeyRelease
#endif
// Affects: Should be without side effects.
#ifdef Above
#ifndef FIXX11H_Above
#define FIXX11H_Above
const int XAbove = Above;
#undef Above
const int Above = XAbove;
#endif
#undef Above
#endif
// Affects: Should be without side effects.
#ifdef Below
#ifndef FIXX11H_Below
#define FIXX11H_Below
const int XBelow = Below;
#undef Below
const int Below = XBelow;
#endif
#undef Below
#endif
// Affects: Should be without side effects.
#ifdef FocusIn
#ifndef FIXX11H_FocusIn
#define FIXX11H_FocusIn
const int XFocusIn = FocusIn;
#undef FocusIn
const int FocusIn = XFocusIn;
#endif
#undef FocusIn
#endif
// Affects: Should be without side effects.
#ifdef FocusOut
#ifndef FIXX11H_FocusOut
#define FIXX11H_FocusOut
const int XFocusOut = FocusOut;
#undef FocusOut
const int FocusOut = XFocusOut;
#endif
#undef FocusOut
#endif
// Affects: Should be without side effects.
#ifdef Always
#ifndef FIXX11H_Always
#define FIXX11H_Always
const int XAlways = Always;
#undef Always
const int Always = XAlways;
#endif
#undef Always
#endif
// Affects: Should be without side effects.
#ifdef Expose
#ifndef FIXX11H_Expose
#define FIXX11H_Expose
const int XExpose = Expose;
#undef Expose
const int Expose = XExpose;
#endif
#undef Expose
#endif
// Affects: Should be without side effects.
#ifdef Success
#ifndef FIXX11H_Success
#define FIXX11H_Success
const int XSuccess = Success;
#undef Success
const int Success = XSuccess;
#endif
#undef Success
#endif
// Affects: Should be without side effects.
#ifdef GrayScale
#ifndef FIXX11H_GrayScale
#define FIXX11H_GrayScale
const int XGrayScale = GrayScale;
#undef GrayScale
const int GrayScale = XGrayScale;
#endif
#undef GrayScale
#endif
// Affects: Should be without side effects.
#ifdef Status
#ifndef FIXX11H_Status
#define FIXX11H_Status
typedef Status XStatus;
#undef Status
typedef XStatus Status;
#endif
#undef Status
#endif
// template --->
// Affects: Should be without side effects.
#ifdef CursorShape
#ifndef FIXX11H_CursorShape
#define FIXX11H_CursorShape
const int XCursorShape = CursorShape;
#undef CursorShape
const int CursorShape = XCursorShape;
#endif
#undef CursorShape
#endif
// template <---
// template --->
// Affects: Should be without side effects.
#ifdef FontChange
#ifndef FIXX11H_FontChange
#define FIXX11H_FontChange
const int XFontChange = FontChange;
#undef FontChange
const int FontChange = XFontChange;
#endif
#undef FontChange
#endif
// template <---
// Affects: Should be without side effects.
#ifdef NormalState
#ifndef FIXX11H_NormalState
#define FIXX11H_NormalState
const int XNormalState = NormalState;
#undef NormalState
const int NormalState = XNormalState;
#endif
#undef NormalState
#endif
// template --->
// Affects: Should be without side effects.
#ifdef index
#ifndef FIXX11H_index
#define FIXX11H_index
inline const char *Xindex(const char *s, int c)
{
return index(s, c);
}
#undef index
inline const char *index(const char *s, int c)
{
return Xindex(s, c);
}
#endif
#undef index
#endif
// template <---
#ifdef rindex
// Affects: Should be without side effects.
#ifndef FIXX11H_rindex
#define FIXX11H_rindex
inline const char *Xrindex(const char *s, int c)
{
return rindex(s, c);
}
#undef rindex
inline const char *rindex(const char *s, int c)
{
return Xrindex(s, c);
}
#endif
#undef rindex
#endif
}
using namespace X;
@@ -0,0 +1,615 @@
/*
SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: MIT
*/
#include "kselectionowner.h"
#include "kwindowsystem.h"
#include "kxcbevent_p.h"
#include <config-kwindowsystem.h>
#include <QAbstractNativeEventFilter>
#include <QBasicTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QTimerEvent>
#include <private/qtx11extras_p.h>
static xcb_window_t get_selection_owner(xcb_connection_t *c, xcb_atom_t selection)
{
xcb_window_t owner = XCB_NONE;
xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(c, xcb_get_selection_owner(c, selection), nullptr);
if (reply) {
owner = reply->owner;
free(reply);
}
return owner;
}
static xcb_atom_t intern_atom(xcb_connection_t *c, const char *name)
{
xcb_atom_t atom = XCB_NONE;
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, false, strlen(name), name), nullptr);
if (reply) {
atom = reply->atom;
free(reply);
}
return atom;
}
class Q_DECL_HIDDEN KSelectionOwner::Private : public QAbstractNativeEventFilter
{
public:
enum State { Idle, WaitingForTimestamp, WaitingForPreviousOwner };
Private(KSelectionOwner *owner_P, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
: state(Idle)
, selection(selection_P)
, connection(c)
, root(root)
, window(XCB_NONE)
, prev_owner(XCB_NONE)
, timestamp(XCB_CURRENT_TIME)
, extra1(0)
, extra2(0)
, force_kill(false)
, owner(owner_P)
{
QCoreApplication::instance()->installNativeEventFilter(this);
}
void claimSucceeded();
void gotTimestamp();
void timeout();
State state;
const xcb_atom_t selection;
xcb_connection_t *connection;
xcb_window_t root;
xcb_window_t window;
xcb_window_t prev_owner;
xcb_timestamp_t timestamp;
uint32_t extra1, extra2;
QBasicTimer timer;
bool force_kill;
static xcb_atom_t manager_atom;
static xcb_atom_t xa_multiple;
static xcb_atom_t xa_targets;
static xcb_atom_t xa_timestamp;
static Private *create(KSelectionOwner *owner, xcb_atom_t selection_P, int screen_P);
static Private *create(KSelectionOwner *owner, const char *selection_P, int screen_P);
static Private *create(KSelectionOwner *owner, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root);
static Private *create(KSelectionOwner *owner, const char *selection_P, xcb_connection_t *c, xcb_window_t root);
protected:
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override
{
if (eventType != "xcb_generic_event_t") {
return false;
}
return owner->filterEvent(message);
}
private:
KSelectionOwner *owner;
};
KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, xcb_atom_t selection_P, int screen_P)
{
if (KWindowSystem::isPlatformX11()) {
return create(owner, selection_P, QX11Info::connection(), QX11Info::appRootWindow(screen_P));
}
qWarning() << "Trying to use KSelectionOwner on a non-X11 platform! This is an application bug.";
return nullptr;
}
KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
{
return new Private(owner, selection_P, c, root);
}
KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, const char *selection_P, int screen_P)
{
if (KWindowSystem::isPlatformX11()) {
return create(owner, selection_P, QX11Info::connection(), QX11Info::appRootWindow(screen_P));
}
qWarning() << "Trying to use KSelectionOwner on a non-X11 platform! This is an application bug.";
return nullptr;
}
KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, const char *selection_P, xcb_connection_t *c, xcb_window_t root)
{
return new Private(owner, intern_atom(c, selection_P), c, root);
}
KSelectionOwner::KSelectionOwner(xcb_atom_t selection_P, int screen_P, QObject *parent_P)
: QObject(parent_P)
, d(Private::create(this, selection_P, screen_P))
{
}
KSelectionOwner::KSelectionOwner(const char *selection_P, int screen_P, QObject *parent_P)
: QObject(parent_P)
, d(Private::create(this, selection_P, screen_P))
{
}
KSelectionOwner::KSelectionOwner(xcb_atom_t selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
: QObject(parent)
, d(Private::create(this, selection, c, root))
{
}
KSelectionOwner::KSelectionOwner(const char *selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
: QObject(parent)
, d(Private::create(this, selection, c, root))
{
}
KSelectionOwner::~KSelectionOwner()
{
if (d) {
release();
if (d->window != XCB_WINDOW_NONE) {
xcb_destroy_window(d->connection, d->window); // also makes the selection not owned
}
delete d;
}
}
void KSelectionOwner::Private::claimSucceeded()
{
state = Idle;
KXcbEvent<xcb_client_message_event_t> ev;
ev.response_type = XCB_CLIENT_MESSAGE;
ev.format = 32;
ev.window = root;
ev.type = Private::manager_atom;
ev.data.data32[0] = timestamp;
ev.data.data32[1] = selection;
ev.data.data32[2] = window;
ev.data.data32[3] = extra1;
ev.data.data32[4] = extra2;
xcb_send_event(connection, false, root, XCB_EVENT_MASK_STRUCTURE_NOTIFY, ev.buffer());
// qDebug() << "Claimed selection";
Q_EMIT owner->claimedOwnership();
}
void KSelectionOwner::Private::gotTimestamp()
{
Q_ASSERT(state == WaitingForTimestamp);
state = Idle;
xcb_connection_t *c = connection;
// Set the selection owner and immediately verify that the claim was successful
xcb_set_selection_owner(c, window, selection, timestamp);
xcb_window_t new_owner = get_selection_owner(c, selection);
if (new_owner != window) {
// qDebug() << "Failed to claim selection : " << new_owner;
xcb_destroy_window(c, window);
timestamp = XCB_CURRENT_TIME;
window = XCB_NONE;
Q_EMIT owner->failedToClaimOwnership();
return;
}
if (prev_owner != XCB_NONE && force_kill) {
// qDebug() << "Waiting for previous owner to disown";
timer.start(1000, owner);
state = WaitingForPreviousOwner;
// Note: We've already selected for structure notify events
// on the previous owner window
} else {
// If there was no previous owner, we're done
claimSucceeded();
}
}
void KSelectionOwner::Private::timeout()
{
Q_ASSERT(state == WaitingForPreviousOwner);
state = Idle;
if (force_kill) {
// qDebug() << "Killing previous owner";
xcb_connection_t *c = connection;
// Ignore any errors from the kill request
xcb_generic_error_t *err = xcb_request_check(c, xcb_kill_client_checked(c, prev_owner));
free(err);
claimSucceeded();
} else {
Q_EMIT owner->failedToClaimOwnership();
}
}
void KSelectionOwner::claim(bool force_P, bool force_kill_P)
{
if (!d) {
return;
}
Q_ASSERT(d->state == Private::Idle);
if (Private::manager_atom == XCB_NONE) {
getAtoms();
}
if (d->timestamp != XCB_CURRENT_TIME) {
release();
}
xcb_connection_t *c = d->connection;
d->prev_owner = get_selection_owner(c, d->selection);
if (d->prev_owner != XCB_NONE) {
if (!force_P) {
// qDebug() << "Selection already owned, failing";
Q_EMIT failedToClaimOwnership();
return;
}
// Select structure notify events so get an event when the previous owner
// destroys the window
uint32_t mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
xcb_change_window_attributes(c, d->prev_owner, XCB_CW_EVENT_MASK, &mask);
}
uint32_t values[] = {true, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
d->window = xcb_generate_id(c);
xcb_create_window(c,
XCB_COPY_FROM_PARENT,
d->window,
d->root,
0,
0,
1,
1,
0,
XCB_WINDOW_CLASS_INPUT_ONLY,
XCB_COPY_FROM_PARENT,
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
values);
// Trigger a property change event so we get a timestamp
xcb_atom_t tmp = XCB_ATOM_ATOM;
xcb_change_property(c, XCB_PROP_MODE_REPLACE, d->window, XCB_ATOM_ATOM, XCB_ATOM_ATOM, 32, 1, (const void *)&tmp);
// Now we have to return to the event loop and wait for the property change event
d->force_kill = force_kill_P;
d->state = Private::WaitingForTimestamp;
}
// destroy resource first
void KSelectionOwner::release()
{
if (!d) {
return;
}
if (d->timestamp == XCB_CURRENT_TIME) {
return;
}
xcb_destroy_window(d->connection, d->window); // also makes the selection not owned
d->window = XCB_NONE;
// qDebug() << "Releasing selection";
d->timestamp = XCB_CURRENT_TIME;
}
xcb_window_t KSelectionOwner::ownerWindow() const
{
if (!d) {
return XCB_WINDOW_NONE;
}
if (d->timestamp == XCB_CURRENT_TIME) {
return XCB_NONE;
}
return d->window;
}
void KSelectionOwner::setData(uint32_t extra1_P, uint32_t extra2_P)
{
if (!d) {
return;
}
d->extra1 = extra1_P;
d->extra2 = extra2_P;
}
bool KSelectionOwner::filterEvent(void *ev_P)
{
if (!d) {
return false;
}
xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(ev_P);
const uint response_type = event->response_type & ~0x80;
#if 0
// There's no generic way to get the window for an event in xcb, it depends on the type of event
// This handleMessage virtual doesn't seem used anyway.
if (d->timestamp != CurrentTime && ev_P->xany.window == d->window) {
if (handleMessage(ev_P)) {
return true;
}
}
#endif
switch (response_type) {
case XCB_SELECTION_CLEAR: {
xcb_selection_clear_event_t *ev = reinterpret_cast<xcb_selection_clear_event_t *>(event);
if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) {
return false;
}
d->timestamp = XCB_CURRENT_TIME;
// qDebug() << "Lost selection";
xcb_window_t window = d->window;
Q_EMIT lostOwnership();
// Unset the event mask before we destroy the window so we don't get a destroy event
uint32_t event_mask = XCB_NONE;
xcb_change_window_attributes(d->connection, window, XCB_CW_EVENT_MASK, &event_mask);
xcb_destroy_window(d->connection, window);
return true;
}
case XCB_DESTROY_NOTIFY: {
xcb_destroy_notify_event_t *ev = reinterpret_cast<xcb_destroy_notify_event_t *>(event);
if (ev->window == d->prev_owner) {
if (d->state == Private::WaitingForPreviousOwner) {
d->timer.stop();
d->claimSucceeded();
return true;
}
// It is possible for the previous owner to be destroyed
// while we're waiting for the timestamp
d->prev_owner = XCB_NONE;
}
if (d->timestamp == XCB_CURRENT_TIME || ev->window != d->window) {
return false;
}
d->timestamp = XCB_CURRENT_TIME;
// qDebug() << "Lost selection (destroyed)";
Q_EMIT lostOwnership();
return true;
}
case XCB_SELECTION_NOTIFY: {
xcb_selection_notify_event_t *ev = reinterpret_cast<xcb_selection_notify_event_t *>(event);
if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) {
return false;
}
// ignore?
return false;
}
case XCB_SELECTION_REQUEST:
filter_selection_request(event);
return false;
case XCB_PROPERTY_NOTIFY: {
xcb_property_notify_event_t *ev = reinterpret_cast<xcb_property_notify_event_t *>(event);
if (ev->window == d->window && d->state == Private::WaitingForTimestamp) {
d->timestamp = ev->time;
d->gotTimestamp();
return true;
}
return false;
}
default:
return false;
}
}
void KSelectionOwner::timerEvent(QTimerEvent *event)
{
if (!d) {
QObject::timerEvent(event);
return;
}
if (event->timerId() == d->timer.timerId()) {
d->timer.stop();
d->timeout();
return;
}
QObject::timerEvent(event);
}
#if 0
bool KSelectionOwner::handleMessage(XEvent *)
{
return false;
}
#endif
void KSelectionOwner::filter_selection_request(void *event)
{
if (!d) {
return;
}
xcb_selection_request_event_t *ev = reinterpret_cast<xcb_selection_request_event_t *>(event);
if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) {
return;
}
if (ev->time != XCB_CURRENT_TIME && ev->time - d->timestamp > 1U << 31) {
return; // too old or too new request
}
// qDebug() << "Got selection request";
xcb_connection_t *c = d->connection;
bool handled = false;
if (ev->target == Private::xa_multiple) {
if (ev->property != XCB_NONE) {
const int MAX_ATOMS = 100;
xcb_get_property_cookie_t cookie = xcb_get_property(c, false, ev->requestor, ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, MAX_ATOMS);
xcb_get_property_reply_t *reply = xcb_get_property_reply(c, cookie, nullptr);
if (reply && reply->format == 32 && reply->value_len % 2 == 0) {
xcb_atom_t *atoms = reinterpret_cast<xcb_atom_t *>(xcb_get_property_value(reply));
bool handled_array[MAX_ATOMS];
for (uint i = 0; i < reply->value_len / 2; i++) {
handled_array[i] = handle_selection(atoms[i * 2], atoms[i * 2 + 1], ev->requestor);
}
bool all_handled = true;
for (uint i = 0; i < reply->value_len / 2; i++) {
if (!handled_array[i]) {
all_handled = false;
atoms[i * 2 + 1] = XCB_NONE;
}
}
if (!all_handled) {
xcb_change_property(c,
ev->requestor,
ev->property,
XCB_ATOM_ATOM,
32,
XCB_PROP_MODE_REPLACE,
reply->value_len,
reinterpret_cast<const void *>(atoms));
}
handled = true;
}
if (reply) {
free(reply);
}
}
} else {
if (ev->property == XCB_NONE) { // obsolete client
ev->property = ev->target;
}
handled = handle_selection(ev->target, ev->property, ev->requestor);
}
KXcbEvent<xcb_selection_notify_event_t> xev;
xev.response_type = XCB_SELECTION_NOTIFY;
xev.selection = ev->selection;
xev.requestor = ev->requestor;
xev.target = ev->target;
xev.property = handled ? ev->property : XCB_NONE;
xcb_send_event(c, false, ev->requestor, 0, xev.buffer());
}
bool KSelectionOwner::handle_selection(xcb_atom_t target_P, xcb_atom_t property_P, xcb_window_t requestor_P)
{
if (!d) {
return false;
}
if (target_P == Private::xa_timestamp) {
// qDebug() << "Handling timestamp request";
xcb_change_property(d->connection,
requestor_P,
property_P,
XCB_ATOM_INTEGER,
32,
XCB_PROP_MODE_REPLACE,
1,
reinterpret_cast<const void *>(&d->timestamp));
} else if (target_P == Private::xa_targets) {
replyTargets(property_P, requestor_P);
} else if (genericReply(target_P, property_P, requestor_P)) {
// handled
} else {
return false; // unknown
}
return true;
}
void KSelectionOwner::replyTargets(xcb_atom_t property_P, xcb_window_t requestor_P)
{
if (!d) {
return;
}
xcb_atom_t atoms[3] = {Private::xa_multiple, Private::xa_timestamp, Private::xa_targets};
xcb_change_property(d->connection,
requestor_P,
property_P,
XCB_ATOM_ATOM,
32,
XCB_PROP_MODE_REPLACE,
sizeof(atoms) / sizeof(atoms[0]),
reinterpret_cast<const void *>(atoms));
// qDebug() << "Handling targets request";
}
bool KSelectionOwner::genericReply(xcb_atom_t, xcb_atom_t, xcb_window_t)
{
return false;
}
void KSelectionOwner::getAtoms()
{
if (!d) {
return;
}
if (Private::manager_atom != XCB_NONE) {
return;
}
xcb_connection_t *c = d->connection;
struct {
const char *name;
xcb_atom_t *atom;
} atoms[] = {{"MANAGER", &Private::manager_atom},
{"MULTIPLE", &Private::xa_multiple},
{"TARGETS", &Private::xa_targets},
{"TIMESTAMP", &Private::xa_timestamp}};
const int count = sizeof(atoms) / sizeof(atoms[0]);
xcb_intern_atom_cookie_t cookies[count];
for (int i = 0; i < count; i++) {
cookies[i] = xcb_intern_atom(c, false, strlen(atoms[i].name), atoms[i].name);
}
for (int i = 0; i < count; i++) {
if (xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, cookies[i], nullptr)) {
*atoms[i].atom = reply->atom;
free(reply);
}
}
}
xcb_atom_t KSelectionOwner::Private::manager_atom = XCB_NONE;
xcb_atom_t KSelectionOwner::Private::xa_multiple = XCB_NONE;
xcb_atom_t KSelectionOwner::Private::xa_targets = XCB_NONE;
xcb_atom_t KSelectionOwner::Private::xa_timestamp = XCB_NONE;
#include "moc_kselectionowner.cpp"
@@ -0,0 +1,188 @@
/*
SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef KSELECTIONOWNER_H
#define KSELECTIONOWNER_H
#include <QObject>
#include <kwindowsystem_export.h>
#include <xcb/xcb.h>
#include <xcb/xproto.h>
/**
This class implements claiming and owning manager selections, as described
in the ICCCM, section 2.8. The selection atom is passed to the constructor,
claim() attempts to claim ownership of the selection, release() gives up
the selection ownership. Signal lostOwnership() is emitted when the selection
is claimed by another owner.
@short ICCCM manager selection owner
This class is only useful on the xcb platform. On other platforms the code is only
functional if the constructor overloads taking an xcb_connection_t are used. In case
you inherit from this class ensure that you don't use xcb and/or XLib without verifying
the platform.
*/
class KWINDOWSYSTEM_EXPORT KSelectionOwner : public QObject
{
Q_OBJECT
public:
/**
* This constructor initializes the object, but doesn't perform any
* operation on the selection.
*
* @param selection atom representing the manager selection
* @param screen X screen, or -1 for default
* @param parent parent object, or nullptr if there is none
*/
explicit KSelectionOwner(xcb_atom_t selection, int screen = -1, QObject *parent = nullptr);
/**
* @overload
* This constructor accepts the selection name and creates the appropriate atom
* for it automatically.
*
* @param selection name of the manager selection
* @param screen X screen, or -1 for default
* @param parent parent object, or nullptr if there is none
*/
explicit KSelectionOwner(const char *selection, int screen = -1, QObject *parent = nullptr);
/**
* @overload
* This constructor accepts the xcb_connection_t and root window and doesn't depend on
* running on the xcb platform. Otherwise this constructor behaves like the similar one
* without the xcb_connection_t.
*
* @param selection atom representing the manager selection
* @param c the xcb connection this KSelectionWatcher should use
* @param root the root window this KSelectionWatcher should use
* @param parent parent object, or nullptr if there is none
* @since 5.8
**/
explicit KSelectionOwner(xcb_atom_t selection, xcb_connection_t *c, xcb_window_t root, QObject *parent = nullptr);
/**
* @overload
* This constructor accepts the xcb_connection_t and root window and doesn't depend on
* running on the xcb platform. Otherwise this constructor behaves like the similar one
* without the xcb_connection_t.
*
* @param selection name of the manager selection
* @param c the xcb connection this KSelectionWatcher should use
* @param root the root window this KSelectionWatcher should use
* @param parent parent object, or nullptr if there is none
* @since 5.8
**/
explicit KSelectionOwner(const char *selection, xcb_connection_t *c, xcb_window_t root, QObject *parent = nullptr);
/**
* Destructor. Calls release().
*/
~KSelectionOwner() override;
/**
* Try to claim ownership of the manager selection using the current X timestamp.
*
* This function returns immediately, but it may take up to one second for the claim
* to succeed or fail, at which point either the claimedOwnership() or
* failedToClaimOwnership() signal is emitted. The claim will not be completed until
* the caller has returned to the event loop.
*
* If @p force is false, and the selection is already owned, the selection is not claimed,
* and failedToClaimOwnership() is emitted. If @p force is true and the selection is
* owned by another client, the client will be given one second to relinquish ownership
* of the selection. If @p force_kill is true, and the previous owner fails to disown
* the selection in time, it will be forcibly killed.
*/
void claim(bool force, bool force_kill = true);
/**
* If the selection is owned, the ownership is given up.
*/
void release();
/**
* If the selection is owned, returns the window used internally
* for owning the selection.
*/
xcb_window_t ownerWindow() const; // None if not owning the selection
/**
* @internal
*/
bool filterEvent(void *ev_P); // internal
/**
* @internal
*/
void timerEvent(QTimerEvent *event) override;
Q_SIGNALS:
/**
* This signal is emitted if the selection was owned and the ownership
* has been lost due to another client claiming it, this signal is emitted.
* IMPORTANT: It's not safe to delete the instance in a slot connected
* to this signal.
*/
void lostOwnership();
/**
* This signal is emitted when claim() was successful in claiming
* ownership of the selection.
*/
void claimedOwnership();
/**
* This signal is emitted when claim() failed to claim ownership
* of the selection.
*/
void failedToClaimOwnership();
protected:
/**
* Called for every X event received on the window used for owning
* the selection. If true is returned, the event is filtered out.
*/
// virtual bool handleMessage( XEvent* ev ); // removed for KF5, please shout if you need this
/**
* Called when a SelectionRequest event is received. A reply should
* be sent using the selection handling mechanism described in the ICCCM
* section 2.
*
* @param target requested target type
* @param property property to use for the reply data
* @param requestor requestor window
*/
virtual bool genericReply(xcb_atom_t target, xcb_atom_t property, xcb_window_t requestor);
/**
* Called to announce the supported targets, as described in the ICCCM
* section 2.6. The default implementation announces the required targets
* MULTIPLE, TIMESTAMP and TARGETS.
*/
virtual void replyTargets(xcb_atom_t property, xcb_window_t requestor);
/**
* Called to create atoms needed for claiming the selection and
* communication using the selection handling mechanism. The default
* implementation must be called if reimplemented. This method
* may be called repeatedly.
*/
virtual void getAtoms();
/**
* Sets extra data to be sent in the message sent to root window
* after successfully claiming a selection. These extra data
* are in data.l[3] and data.l[4] fields of the XClientMessage.
*/
void setData(uint32_t extra1, uint32_t extra2);
private:
void filter_selection_request(void *ev_P);
bool handle_selection(xcb_atom_t target_P, xcb_atom_t property_P, xcb_window_t requestor_P);
class Private;
Private *const d;
};
#endif
@@ -0,0 +1,246 @@
/*
SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: MIT
*/
#include "kselectionwatcher.h"
#include "kwindowsystem.h"
#include <config-kwindowsystem.h>
#include <QAbstractNativeEventFilter>
#include <QCoreApplication>
#include <private/qtx11extras_p.h>
static xcb_window_t get_selection_owner(xcb_connection_t *c, xcb_atom_t selection)
{
xcb_window_t owner = XCB_NONE;
xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(c, xcb_get_selection_owner(c, selection), nullptr);
if (reply) {
owner = reply->owner;
free(reply);
}
return owner;
}
static xcb_atom_t intern_atom(xcb_connection_t *c, const char *name)
{
xcb_atom_t atom = XCB_NONE;
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, false, strlen(name), name), nullptr);
if (reply) {
atom = reply->atom;
free(reply);
}
return atom;
}
//*******************************************
// KSelectionWatcher
//*******************************************
class Q_DECL_HIDDEN KSelectionWatcher::Private : public QAbstractNativeEventFilter
{
public:
Private(KSelectionWatcher *watcher_P, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
: connection(c)
, root(root)
, selection(selection_P)
, selection_owner(XCB_NONE)
, watcher(watcher_P)
{
QCoreApplication::instance()->installNativeEventFilter(this);
}
xcb_connection_t *connection;
xcb_window_t root;
const xcb_atom_t selection;
xcb_window_t selection_owner;
static xcb_atom_t manager_atom;
static Private *create(KSelectionWatcher *watcher, xcb_atom_t selection_P, int screen_P);
static Private *create(KSelectionWatcher *watcher, const char *selection_P, int screen_P);
static Private *create(KSelectionWatcher *watcher, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root);
static Private *create(KSelectionWatcher *watcher, const char *selection_P, xcb_connection_t *c, xcb_window_t root);
protected:
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override
{
if (eventType != "xcb_generic_event_t") {
return false;
}
watcher->filterEvent(message);
return false;
}
private:
KSelectionWatcher *watcher;
};
KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, xcb_atom_t selection_P, int screen_P)
{
if (KWindowSystem::isPlatformX11()) {
return create(watcher, selection_P, QX11Info::connection(), QX11Info::appRootWindow(screen_P));
}
return nullptr;
}
KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
{
return new Private(watcher, selection_P, c, root);
}
KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, const char *selection_P, int screen_P)
{
if (KWindowSystem::isPlatformX11()) {
return create(watcher, selection_P, QX11Info::connection(), QX11Info::appRootWindow(screen_P));
}
return nullptr;
}
KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, const char *selection_P, xcb_connection_t *c, xcb_window_t root)
{
return new Private(watcher, intern_atom(c, selection_P), c, root);
}
KSelectionWatcher::KSelectionWatcher(xcb_atom_t selection_P, int screen_P, QObject *parent_P)
: QObject(parent_P)
, d(Private::create(this, selection_P, screen_P))
{
init();
}
KSelectionWatcher::KSelectionWatcher(const char *selection_P, int screen_P, QObject *parent_P)
: QObject(parent_P)
, d(Private::create(this, selection_P, screen_P))
{
init();
}
KSelectionWatcher::KSelectionWatcher(xcb_atom_t selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
: QObject(parent)
, d(Private::create(this, selection, c, root))
{
init();
}
KSelectionWatcher::KSelectionWatcher(const char *selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
: QObject(parent)
, d(Private::create(this, selection, c, root))
{
init();
}
KSelectionWatcher::~KSelectionWatcher()
{
delete d;
}
void KSelectionWatcher::init()
{
if (!d) {
return;
}
if (Private::manager_atom == XCB_NONE) {
xcb_connection_t *c = d->connection;
xcb_intern_atom_cookie_t atom_cookie = xcb_intern_atom(c, false, strlen("MANAGER"), "MANAGER");
xcb_get_window_attributes_cookie_t attr_cookie = xcb_get_window_attributes(c, d->root);
xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(c, atom_cookie, nullptr);
Private::manager_atom = atom_reply->atom;
free(atom_reply);
xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(c, attr_cookie, nullptr);
uint32_t event_mask = attr->your_event_mask;
free(attr);
if (!(event_mask & XCB_EVENT_MASK_STRUCTURE_NOTIFY)) {
// We need XCB_EVENT_MASK_STRUCTURE_NORITY on the root window
event_mask |= XCB_EVENT_MASK_STRUCTURE_NOTIFY;
xcb_change_window_attributes(c, d->root, XCB_CW_EVENT_MASK, &event_mask);
}
}
owner(); // trigger reading of current selection status
}
xcb_window_t KSelectionWatcher::owner()
{
if (!d) {
return XCB_WINDOW_NONE;
}
xcb_connection_t *c = d->connection;
xcb_window_t current_owner = get_selection_owner(c, d->selection);
if (current_owner == XCB_NONE) {
return XCB_NONE;
}
if (current_owner == d->selection_owner) {
return d->selection_owner;
}
// We have a new selection owner - select for structure notify events
uint32_t mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
xcb_void_cookie_t cookie = xcb_change_window_attributes_checked(c, current_owner, XCB_CW_EVENT_MASK, &mask);
// Verify that the owner didn't change again while selecting for events
xcb_window_t new_owner = get_selection_owner(c, d->selection);
xcb_generic_error_t *err = xcb_request_check(c, cookie);
if (!err && current_owner == new_owner) {
d->selection_owner = current_owner;
Q_EMIT newOwner(d->selection_owner);
} else {
// ### This doesn't look right - the selection could have an owner
d->selection_owner = XCB_NONE;
}
if (err) {
free(err);
}
return d->selection_owner;
}
void KSelectionWatcher::filterEvent(void *ev_P)
{
if (!d) {
return;
}
xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(ev_P);
const uint response_type = event->response_type & ~0x80;
if (response_type == XCB_CLIENT_MESSAGE) {
xcb_client_message_event_t *cm_event = reinterpret_cast<xcb_client_message_event_t *>(event);
if (cm_event->type != Private::manager_atom || cm_event->data.data32[1] != d->selection) {
return;
}
// owner() checks whether the owner changed and emits newOwner()
owner();
return;
}
if (response_type == XCB_DESTROY_NOTIFY) {
xcb_destroy_notify_event_t *ev = reinterpret_cast<xcb_destroy_notify_event_t *>(event);
if (d->selection_owner == XCB_NONE || ev->window != d->selection_owner) {
return;
}
d->selection_owner = XCB_NONE; // in case the exactly same ID gets reused as the owner
if (owner() == XCB_NONE) {
Q_EMIT lostOwner(); // it must be safe to delete 'this' in a slot
}
return;
}
}
xcb_atom_t KSelectionWatcher::Private::manager_atom = XCB_NONE;
#include "moc_kselectionwatcher.cpp"
@@ -0,0 +1,110 @@
/*
SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef KSELECTIONWATCHER_H
#define KSELECTIONWATCHER_H
#include <QObject>
#include <kwindowsystem_export.h>
#include <xcb/xcb.h>
#include <xcb/xproto.h>
/**
This class implements watching manager selections, as described in the ICCCM
section 2.8. It emits signal newOwner() when a new owner claim the selection,
and emits lostOwner() when the selection ownership is given up. To find
out current owner of the selection, owner() can be used.
@short ICCCM manager selection watching
This class is only useful on the xcb platform. On other platforms the code is only
functional if the constructor overloads taking an xcb_connection_t are used. In case
you inherit from this class ensure that you don't use xcb and/or XLib without verifying
the platform.
*/
class KWINDOWSYSTEM_EXPORT KSelectionWatcher : public QObject
{
Q_OBJECT
public:
/**
* This constructor initializes the object, but doesn't perform any
* operation on the selection.
*
* @param selection atom representing the manager selection
* @param screen X screen, or -1 for default
* @param parent parent object, or nullptr if there is none
*/
explicit KSelectionWatcher(xcb_atom_t selection, int screen = -1, QObject *parent = nullptr);
/**
* @overload
* This constructor accepts the selection name and creates the appropriate atom
* for it automatically.
*
* @param selection name of the manager selection
* @param screen X screen, or -1 for default
* @param parent parent object, or nullptr if there is none
*/
explicit KSelectionWatcher(const char *selection, int screen = -1, QObject *parent = nullptr);
/**
* @overload
* This constructor accepts the xcb_connection_t and root window and doesn't depend on
* running on the xcb platform. Otherwise this constructor behaves like the similar one
* without the xcb_connection_t.
*
* @param selection atom representing the manager selection
* @param c the xcb connection this KSelectionWatcher should use
* @param root the root window this KSelectionWatcher should use
* @since 5.8
**/
explicit KSelectionWatcher(xcb_atom_t selection, xcb_connection_t *c, xcb_window_t root, QObject *parent = nullptr);
/**
* @overload
* This constructor accepts the xcb_connection_t and root window and doesn't depend on
* running on the xcb platform. Otherwise this constructor behaves like the similar one
* without the xcb_connection_t.
*
* @param selection name of the manager selection
* @param c the xcb connection this KSelectionWatcher should use
* @param root the root window this KSelectionWatcher should use
* @since 5.8
**/
explicit KSelectionWatcher(const char *selection, xcb_connection_t *c, xcb_window_t root, QObject *parent = nullptr);
~KSelectionWatcher() override;
/**
* Return the current owner of the manager selection, if any. Note that if the event
* informing about the owner change is still in the input queue, newOwner() might
* have been emitted yet.
*/
xcb_window_t owner();
/**
* @internal
*/
void filterEvent(void *ev_P); // internal
Q_SIGNALS:
/**
* This signal is emitted when the selection is successfully claimed by a new
* owner.
* @param owner the new owner of the selection
*/
void newOwner(xcb_window_t owner);
/**
* This signal is emitted when the selection is given up, i.e. there's no
* owner. Note that the selection may be immediately claimed again,
* so the newOwner() signal may be emitted right after this one.
* It's safe to delete the instance in a slot connected to this signal.
*/
void lostOwner();
private:
void init();
class Private;
Private *const d;
};
Q_DECLARE_METATYPE(xcb_window_t)
#endif
@@ -0,0 +1,211 @@
/*
SPDX-FileCopyrightText: 2009 Marco Martin <notmart@gmail.com>
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "kwindoweffects_x11.h"
#include <QGuiApplication>
#include <QVarLengthArray>
#include "kx11extras.h"
#include <config-kwindowsystem.h>
#include <QMatrix4x4>
#include <QWindow>
#include <private/qtx11extras_p.h>
#include <xcb/xcb.h>
#include "cptr_p.h"
#include <cmath>
using namespace KWindowEffects;
KWindowEffectsPrivateX11::KWindowEffectsPrivateX11()
{
}
KWindowEffectsPrivateX11::~KWindowEffectsPrivateX11()
{
}
bool KWindowEffectsPrivateX11::isEffectAvailable(Effect effect)
{
if (!KX11Extras::self()->compositingActive()) {
return false;
}
QByteArray effectName;
switch (effect) {
case Slide:
effectName = QByteArrayLiteral("_KDE_SLIDE");
break;
case BlurBehind:
effectName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
break;
case BackgroundContrast:
effectName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION");
break;
default:
return false;
}
// hackish way to find out if KWin has the effect enabled,
// TODO provide proper support
xcb_connection_t *c = QX11Info::connection();
xcb_list_properties_cookie_t propsCookie = xcb_list_properties_unchecked(c, QX11Info::appRootWindow());
xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
UniqueCPointer<xcb_list_properties_reply_t> props(xcb_list_properties_reply(c, propsCookie, nullptr));
UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
if (!atom || !props) {
return false;
}
xcb_atom_t *atoms = xcb_list_properties_atoms(props.get());
for (int i = 0; i < props->atoms_len; ++i) {
if (atoms[i] == atom->atom) {
return true;
}
}
return false;
}
void KWindowEffectsPrivateX11::slideWindow(QWindow *window, SlideFromLocation location, int offset)
{
xcb_connection_t *c = QX11Info::connection();
if (!c) {
return;
}
const QByteArray effectName = QByteArrayLiteral("_KDE_SLIDE");
xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
const int size = 2;
int32_t data[size];
data[0] = offset;
switch (location) {
case LeftEdge:
data[1] = 0;
break;
case TopEdge:
data[1] = 1;
break;
case RightEdge:
data[1] = 2;
break;
case BottomEdge:
data[1] = 3;
default:
break;
}
UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
if (!atom) {
return;
}
if (location == NoEdge) {
xcb_delete_property(c, window->winId(), atom->atom);
} else {
xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom->atom, atom->atom, 32, size, data);
}
}
void KWindowEffectsPrivateX11::enableBlurBehind(QWindow *window, bool enable, const QRegion &region)
{
xcb_connection_t *c = QX11Info::connection();
if (!c) {
return;
}
const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
if (!atom) {
return;
}
if (enable) {
QList<uint32_t> data;
data.reserve(region.rectCount() * 4);
for (const QRect &r : region) {
// kwin on X uses device pixels, convert from logical
auto dpr = qApp->devicePixelRatio();
data << std::floor(r.x() * dpr) << std::floor(r.y() * dpr) << std::ceil(r.width() * dpr) << std::ceil(r.height() * dpr);
}
xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom->atom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData());
} else {
xcb_delete_property(c, window->winId(), atom->atom);
}
}
void KWindowEffectsPrivateX11::enableBackgroundContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
{
xcb_connection_t *c = QX11Info::connection();
const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION");
xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
if (!atom) {
return;
}
if (enable) {
QList<uint32_t> data;
data.reserve(region.rectCount() * 4 + 16);
for (const QRect &r : region) {
auto dpr = qApp->devicePixelRatio();
data << std::floor(r.x() * dpr) << std::floor(r.y() * dpr) << std::ceil(r.width() * dpr) << std::ceil(r.height() * dpr);
}
QMatrix4x4 satMatrix; // saturation
QMatrix4x4 intMatrix; // intensity
QMatrix4x4 contMatrix; // contrast
// clang-format off
//Saturation matrix
if (!qFuzzyCompare(saturation, 1.0)) {
const qreal rval = (1.0 - saturation) * .2126;
const qreal gval = (1.0 - saturation) * .7152;
const qreal bval = (1.0 - saturation) * .0722;
satMatrix = QMatrix4x4(rval + saturation, rval, rval, 0.0,
gval, gval + saturation, gval, 0.0,
bval, bval, bval + saturation, 0.0,
0, 0, 0, 1.0);
}
//IntensityMatrix
if (!qFuzzyCompare(intensity, 1.0)) {
intMatrix.scale(intensity, intensity, intensity);
}
//Contrast Matrix
if (!qFuzzyCompare(contrast, 1.0)) {
const float transl = (1.0 - contrast) / 2.0;
contMatrix = QMatrix4x4(contrast, 0, 0, 0.0,
0, contrast, 0, 0.0,
0, 0, contrast, 0.0,
transl, transl, transl, 1.0);
}
// clang-format on
QMatrix4x4 colorMatrix = contMatrix * satMatrix * intMatrix;
colorMatrix = colorMatrix.transposed();
uint32_t *rawData = reinterpret_cast<uint32_t *>(colorMatrix.data());
for (int i = 0; i < 16; ++i) {
data << rawData[i];
}
xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom->atom, atom->atom, 32, data.size(), data.constData());
} else {
xcb_delete_property(c, window->winId(), atom->atom);
}
}
@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KWINDOWEFFECTS_X11_H
#define KWINDOWEFFECTS_X11_H
#include "kwindoweffects_p.h"
class KWindowEffectsPrivateX11 : public KWindowEffectsPrivate
{
public:
KWindowEffectsPrivateX11();
~KWindowEffectsPrivateX11() override;
bool isEffectAvailable(KWindowEffects::Effect effect) override;
void slideWindow(QWindow *window, KWindowEffects::SlideFromLocation location, int offset) override;
void enableBlurBehind(QWindow *window, bool enable = true, const QRegion &region = QRegion()) override;
void enableBackgroundContrast(QWindow *window,
bool enable = true,
qreal contrast = 1,
qreal intensity = 1,
qreal saturation = 1,
const QRegion &region = QRegion()) override;
};
#endif
@@ -0,0 +1,218 @@
/*
SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "kwindowshadow_p_x11.h"
#include <private/qtx11extras_p.h>
static const QByteArray s_atomName = QByteArrayLiteral("_KDE_NET_WM_SHADOW");
bool KWindowShadowTilePrivateX11::create()
{
xcb_connection_t *connection = QX11Info::connection();
xcb_window_t rootWindow = QX11Info::appRootWindow();
const uint16_t width = uint16_t(image.width());
const uint16_t height = uint16_t(image.height());
const uint8_t depth = uint8_t(image.depth());
pixmap = xcb_generate_id(connection);
gc = xcb_generate_id(connection);
xcb_create_pixmap(connection, depth, pixmap, rootWindow, width, height);
xcb_create_gc(connection, gc, pixmap, 0, nullptr);
xcb_put_image(connection, //
XCB_IMAGE_FORMAT_Z_PIXMAP,
pixmap,
gc,
width,
height,
0,
0,
0,
depth,
image.sizeInBytes(),
image.constBits());
return true;
}
void KWindowShadowTilePrivateX11::destroy()
{
xcb_connection_t *connection = QX11Info::connection();
if (connection) {
xcb_free_pixmap(connection, pixmap);
xcb_free_gc(connection, gc);
}
pixmap = XCB_PIXMAP_NONE;
gc = XCB_NONE;
}
KWindowShadowTilePrivateX11 *KWindowShadowTilePrivateX11::get(const KWindowShadowTile *tile)
{
KWindowShadowTilePrivate *d = KWindowShadowTilePrivate::get(tile);
return static_cast<KWindowShadowTilePrivateX11 *>(d);
}
static xcb_atom_t lookupAtom(const QByteArray &atomName)
{
xcb_connection_t *connection = QX11Info::connection();
if (!connection) {
return XCB_ATOM_NONE;
}
xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(connection, //
false,
atomName.size(),
atomName.constData());
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, atomCookie, nullptr);
if (!reply) {
return XCB_ATOM_NONE;
}
xcb_atom_t atom = reply->atom;
free(reply);
return atom;
}
static xcb_pixmap_t nativeHandleForTile(const KWindowShadowTile::Ptr &tile)
{
const auto d = KWindowShadowTilePrivateX11::get(tile.data());
return d->pixmap;
}
bool KWindowShadowPrivateX11::create()
{
xcb_connection_t *connection = QX11Info::connection();
const xcb_atom_t atom = lookupAtom(s_atomName);
if (atom == XCB_ATOM_NONE) {
return false;
}
QList<quint32> data(12);
int i = 0;
// Unfortunately we cannot use handle of XCB_PIXMAP_NONE for missing shadow tiles because
// KWin expects **all** shadow tile handles to be valid. Maybe we could address this small
// inconvenience and then remove the empty tile stuff.
if (topTile) {
data[i++] = nativeHandleForTile(topTile);
} else {
data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
}
if (topRightTile) {
data[i++] = nativeHandleForTile(topRightTile);
} else {
data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
}
if (rightTile) {
data[i++] = nativeHandleForTile(rightTile);
} else {
data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
}
if (bottomRightTile) {
data[i++] = nativeHandleForTile(bottomRightTile);
} else {
data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
}
if (bottomTile) {
data[i++] = nativeHandleForTile(bottomTile);
} else {
data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
}
if (bottomLeftTile) {
data[i++] = nativeHandleForTile(bottomLeftTile);
} else {
data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
}
if (leftTile) {
data[i++] = nativeHandleForTile(leftTile);
} else {
data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
}
if (topLeftTile) {
data[i++] = nativeHandleForTile(topLeftTile);
} else {
data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
}
if (topLeftTile || topTile || topRightTile) {
data[i++] = uint32_t(padding.top());
} else {
data[i++] = 1;
}
if (topRightTile || rightTile || bottomRightTile) {
data[i++] = uint32_t(padding.right());
} else {
data[i++] = 1;
}
if (bottomRightTile || bottomTile || bottomLeftTile) {
data[i++] = uint32_t(padding.bottom());
} else {
data[i++] = 1;
}
if (bottomLeftTile || leftTile || topLeftTile) {
data[i++] = uint32_t(padding.left());
} else {
data[i++] = 1;
}
xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window->winId(), atom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData());
xcb_flush(connection);
return true;
}
void KWindowShadowPrivateX11::destroy()
{
emptyTile = nullptr;
// For some reason, QWindow changes visibility of QSurface::surfaceHandle().
const QSurface *surface = window;
// Attempting to uninstall the shadow after the platform window had been destroyed.
if (!(surface && surface->surfaceHandle())) {
return;
}
xcb_connection_t *connection = QX11Info::connection();
const xcb_atom_t atom = lookupAtom(s_atomName);
if (atom == XCB_ATOM_NONE) {
return;
}
xcb_delete_property(connection, window->winId(), atom);
}
KWindowShadowTile::Ptr KWindowShadowPrivateX11::getOrCreateEmptyTile()
{
if (!emptyTile) {
QImage image(QSize(1, 1), QImage::Format_ARGB32);
image.fill(Qt::transparent);
emptyTile = KWindowShadowTile::Ptr::create();
emptyTile->setImage(image);
emptyTile->create();
}
return emptyTile;
}
@@ -0,0 +1,37 @@
/*
SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KWINDOWSHADOW_P_X11_H
#define KWINDOWSHADOW_P_X11_H
#include "kwindowshadow_p.h"
#include <xcb/xcb.h>
class KWindowShadowTilePrivateX11 final : public KWindowShadowTilePrivate
{
public:
bool create() override;
void destroy() override;
static KWindowShadowTilePrivateX11 *get(const KWindowShadowTile *tile);
xcb_pixmap_t pixmap = XCB_PIXMAP_NONE;
xcb_gcontext_t gc = XCB_NONE;
};
class KWindowShadowPrivateX11 final : public KWindowShadowPrivate
{
public:
bool create() override;
void destroy() override;
KWindowShadowTile::Ptr getOrCreateEmptyTile();
KWindowShadowTile::Ptr emptyTile;
};
#endif // KWINDOWSHADOW_P_X11_H
@@ -0,0 +1,27 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Matthias Ettrich <ettrich@kde.org>
SPDX-FileCopyrightText: 2007 Lubos Lunak <l.lunak@kde.org>
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "kwindowsystem_p_x11.h"
#include "kx11extras.h"
void KWindowSystemPrivateX11::activateWindow(QWindow *win, long time)
{
KX11Extras::activateWindow(win->winId(), time);
}
bool KWindowSystemPrivateX11::showingDesktop()
{
return KX11Extras::showingDesktop();
}
void KWindowSystemPrivateX11::setShowingDesktop(bool showing)
{
KX11Extras::setShowingDesktop(showing);
}
@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KWINDOWSYSTEM_P_X11_H
#define KWINDOWSYSTEM_P_X11_H
#include "kwindowsystem_p.h"
class KWindowSystemPrivateX11 : public KWindowSystemPrivate
{
public:
void activateWindow(QWindow *win, long time) override;
bool showingDesktop() override;
void setShowingDesktop(bool showing) override;
};
#endif
@@ -0,0 +1,94 @@
/*
SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef KXERRORHANDLER_H
#define KXERRORHANDLER_H
#include <config-kwindowsystem.h>
#include <QtGlobal>
#include <private/qtx11extras_p.h>
#include <X11/Xlib.h>
class KXErrorHandlerPrivate;
/**
* This class simplifies handling of X errors. It shouldn't be necessary to use
* with Qt classes, as the toolkit should handle X errors itself, so this
* class will be mainly used with direct Xlib usage, and some lowlevel classes
* like NETWinInfo.
*
* The usual usage is to create a KXErrorHandler instance right before starting
* operations that might cause X errors, and checking if there was an error
* by calling error() after the operations are finished. The handlers
* may be nested, but must be destroyed in reverse order they were created.
*
* There's no need to do X sync before creating an instance, every instance
* will handle only errors for request issued after the instance was created.
* Errors for older requests will be passed to previous error handler.
* When checking for error by calling error() at the end, it is necessary
* to sync with X, to catch all errors that were caused by requests issued
* before the call to error(). This can be done by passing true to error()
* to cause explicit XSync(), however, if the last X request needed a roundtrip
* (e.g. XGetWindowAttributes(), XGetGeometry(), etc.), it is not required
* to do an explicit sync.
*
* @author Lubos Lunak <l.lunak@kde.org>
* @short Handler for X errors
*/
class KXErrorHandler
{
public:
/**
* Creates error handler that will set error flag after encountering
* any X error.
*/
explicit KXErrorHandler(Display *dpy = QX11Info::display());
/**
* This constructor takes pointer to a function whose prototype matches
* the one that's used with the XSetErrorHandler() Xlib function.
* NOTE: For the error flag to be set, the function must return a non-zero
* value.
*/
explicit KXErrorHandler(int (*handler)(Display *, XErrorEvent *), Display *dpy = QX11Info::display());
/**
* This function returns true if the error flag is set (i.e. no custom handler
* function was used and there was any error, or the custom handler indicated
* an error by its return value).
*
* @param sync if true, an explicit XSync() will be done. Not necessary
* when the last X request required a roundtrip.
*/
bool error(bool sync) const;
/**
* This function returns the error event for the first X error that occurred.
* The return value is useful only if error() returned true.
* @since 4.0.1
*/
XErrorEvent errorEvent() const;
/**
* Returns error message for the given error. The error message is not translated,
* as it is meant for debugging.
* @since 4.0.1
*/
static QByteArray errorMessage(const XErrorEvent &e, Display *dpy = QX11Info::display());
~KXErrorHandler();
private:
void addHandler();
int handle(Display *dpy, XErrorEvent *e);
bool (*user_handler1)(int request, int error_code, unsigned long resource_id);
int (*user_handler2)(Display *, XErrorEvent *);
int (*old_handler)(Display *, XErrorEvent *);
static int handler_wrapper(Display *, XErrorEvent *);
static KXErrorHandler **handlers;
static int pos;
static int size;
Q_DISABLE_COPY(KXErrorHandler)
KXErrorHandlerPrivate *const d;
};
#endif
@@ -0,0 +1,267 @@
/*
SPDX-FileCopyrightText: 2001-2003 Lubos Lunak <l.lunak@kde.org>
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
SPDX-License-Identifier: MIT
*/
#include "kxmessages.h"
#include "cptr_p.h"
#include "kxutils_p.h"
#include "kxcbevent_p.h"
#if KWINDOWSYSTEM_HAVE_X11
#include <QAbstractNativeEventFilter>
#include <QCoreApplication>
#include <QDebug>
#include <QWindow> // WId
#include <X11/Xlib.h>
#include <private/qtx11extras_p.h>
class XcbAtom
{
public:
explicit XcbAtom(const QByteArray &name, bool onlyIfExists = false)
: m_name(name)
, m_atom(XCB_ATOM_NONE)
, m_connection(nullptr)
, m_retrieved(false)
, m_onlyIfExists(onlyIfExists)
{
m_cookie.sequence = 0;
}
explicit XcbAtom(xcb_connection_t *c, const QByteArray &name, bool onlyIfExists = false)
: m_name(name)
, m_atom(XCB_ATOM_NONE)
, m_cookie(xcb_intern_atom_unchecked(c, onlyIfExists, name.length(), name.constData()))
, m_connection(c)
, m_retrieved(false)
, m_onlyIfExists(onlyIfExists)
{
}
~XcbAtom()
{
if (!m_retrieved && m_cookie.sequence && m_connection) {
xcb_discard_reply(m_connection, m_cookie.sequence);
}
}
operator xcb_atom_t()
{
getReply();
return m_atom;
}
inline const QByteArray &name() const
{
return m_name;
}
inline void setConnection(xcb_connection_t *c)
{
m_connection = c;
}
inline void fetch()
{
if (!m_connection || m_name.isEmpty()) {
return;
}
m_cookie = xcb_intern_atom_unchecked(m_connection, m_onlyIfExists, m_name.length(), m_name.constData());
}
private:
void getReply()
{
if (m_retrieved || !m_cookie.sequence || !m_connection) {
return;
}
UniqueCPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(m_connection, m_cookie, nullptr));
if (reply) {
m_atom = reply->atom;
}
m_retrieved = true;
}
QByteArray m_name;
xcb_atom_t m_atom;
xcb_intern_atom_cookie_t m_cookie;
xcb_connection_t *m_connection;
bool m_retrieved;
bool m_onlyIfExists;
};
class KXMessagesPrivate : public QAbstractNativeEventFilter
{
public:
KXMessagesPrivate(KXMessages *parent, const char *acceptBroadcast, xcb_connection_t *c, xcb_window_t root)
: accept_atom1(acceptBroadcast ? QByteArray(acceptBroadcast) + QByteArrayLiteral("_BEGIN") : QByteArray())
, accept_atom2(acceptBroadcast ? QByteArray(acceptBroadcast) : QByteArray())
, handle(new QWindow)
, q(parent)
, valid(c)
, connection(c)
, rootWindow(root)
{
if (acceptBroadcast) {
accept_atom1.setConnection(c);
accept_atom1.fetch();
accept_atom2.setConnection(c);
accept_atom2.fetch();
QCoreApplication::instance()->installNativeEventFilter(this);
}
}
XcbAtom accept_atom1;
XcbAtom accept_atom2;
QMap<WId, QByteArray> incoming_messages;
std::unique_ptr<QWindow> handle;
KXMessages *q;
bool valid;
xcb_connection_t *connection;
xcb_window_t rootWindow;
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override
{
// A faster comparison than eventType != "xcb_generic_event_t"
if (eventType[0] != 'x') {
return false;
}
xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(message);
uint response_type = event->response_type & ~0x80;
if (response_type != XCB_CLIENT_MESSAGE) {
return false;
}
xcb_client_message_event_t *cm_event = reinterpret_cast<xcb_client_message_event_t *>(event);
if (cm_event->format != 8) {
return false;
}
if (cm_event->type != accept_atom1 && cm_event->type != accept_atom2) {
return false;
}
char buf[21]; // can't be longer
// Copy the data in order to null-terminate it
qstrncpy(buf, reinterpret_cast<char *>(cm_event->data.data8), 21);
// qDebug() << cm_event->window << "buf=\"" << buf << "\" atom=" << (cm_event->type == accept_atom1 ? "atom1" : "atom2");
if (incoming_messages.contains(cm_event->window)) {
if (cm_event->type == accept_atom1)
// two different messages on the same window at the same time shouldn't happen anyway
{
incoming_messages[cm_event->window] = QByteArray();
}
incoming_messages[cm_event->window] += buf;
} else {
if (cm_event->type == accept_atom2) {
return false; // middle of message, but we don't have the beginning
}
incoming_messages[cm_event->window] = buf;
}
if (strlen(buf) < 20) { // last message fragment
Q_EMIT q->gotMessage(QString::fromUtf8(incoming_messages[cm_event->window].constData()));
incoming_messages.remove(cm_event->window);
}
return false; // lets other KXMessages instances get the event too
}
};
static void
send_message_internal(xcb_window_t w, const QString &msg, xcb_connection_t *c, xcb_atom_t leadingMessage, xcb_atom_t followingMessage, xcb_window_t handle);
KXMessages::KXMessages(const char *accept_broadcast_P, QObject *parent_P)
: QObject(parent_P)
, d(new KXMessagesPrivate(this,
accept_broadcast_P,
QX11Info::isPlatformX11() ? QX11Info::connection() : nullptr,
QX11Info::isPlatformX11() ? QX11Info::appRootWindow() : 0))
{
}
KXMessages::KXMessages(xcb_connection_t *connection, xcb_window_t rootWindow, const char *accept_broadcast, QObject *parent)
: QObject(parent)
, d(new KXMessagesPrivate(this, accept_broadcast, connection, rootWindow))
{
}
KXMessages::~KXMessages()
{
delete d;
}
static xcb_screen_t *defaultScreen(xcb_connection_t *c, int screen)
{
for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(c)); it.rem; --screen, xcb_screen_next(&it)) {
if (screen == 0) {
return it.data;
}
}
return nullptr;
}
void KXMessages::broadcastMessage(const char *msg_type_P, const QString &message_P, int screen_P)
{
if (!d->valid) {
qWarning() << "KXMessages used on non-X11 platform! This is an application bug.";
return;
}
const QByteArray msg(msg_type_P);
XcbAtom a2(d->connection, msg);
XcbAtom a1(d->connection, msg + QByteArrayLiteral("_BEGIN"));
xcb_window_t root = screen_P == -1 ? d->rootWindow : defaultScreen(d->connection, screen_P)->root;
send_message_internal(root, message_P, d->connection, a1, a2, d->handle->winId());
}
bool KXMessages::broadcastMessageX(xcb_connection_t *c, const char *msg_type_P, const QString &message, int screenNumber)
{
if (!c) {
return false;
}
const QByteArray msg(msg_type_P);
XcbAtom a2(c, msg);
XcbAtom a1(c, msg + QByteArrayLiteral("_BEGIN"));
const xcb_screen_t *screen = defaultScreen(c, screenNumber);
if (!screen) {
return false;
}
const xcb_window_t root = screen->root;
const xcb_window_t win = xcb_generate_id(c);
xcb_create_window(c, XCB_COPY_FROM_PARENT, win, root, 0, 0, 1, 1, 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, 0, nullptr);
send_message_internal(root, message, c, a1, a2, win);
xcb_destroy_window(c, win);
return true;
}
static void
send_message_internal(xcb_window_t w, const QString &msg_P, xcb_connection_t *c, xcb_atom_t leadingMessage, xcb_atom_t followingMessage, xcb_window_t handle)
{
unsigned int pos = 0;
QByteArray msg = msg_P.toUtf8();
const size_t len = msg.size();
KXcbEvent<xcb_client_message_event_t> event;
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 8;
event.sequence = 0;
event.window = handle;
event.type = leadingMessage;
do {
unsigned int i;
for (i = 0; i < 20 && i + pos < len; ++i) {
event.data.data8[i] = msg[i + pos];
}
for (; i < 20; ++i) {
event.data.data8[i] = 0;
}
xcb_send_event(c, false, w, XCB_EVENT_MASK_PROPERTY_CHANGE, event.buffer());
event.type = followingMessage;
pos += i;
} while (pos <= len);
xcb_flush(c);
}
#endif
#include "moc_kxmessages.cpp"
@@ -0,0 +1,92 @@
/*
SPDX-FileCopyrightText: 2001-2003 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef KXMESSAGES_H
#define KXMESSAGES_H
#include <QObject>
#include <kwindowsystem_export.h>
#include <config-kwindowsystem.h> // KWINDOWSYSTEM_HAVE_X11
#if KWINDOWSYSTEM_HAVE_X11
#include <xcb/xcb.h>
typedef struct _XDisplay Display;
class QString;
class KXMessagesPrivate;
/**
* Sending string messages to other applications using the X Client Messages.
*
* Used internally by KStartupInfo and kstart.
* You usually don't want to use this, use D-Bus instead.
*
* @internal
*
* @author Lubos Lunak <l.lunak@kde.org>
*/
class KWINDOWSYSTEM_EXPORT KXMessages : public QObject
{
Q_OBJECT
public:
/**
* Creates an instance which will receive X messages.
*
* @param accept_broadcast if non-nullptr, all broadcast messages with
* this message type will be received.
* @param parent the parent of this widget
*/
explicit KXMessages(const char *accept_broadcast = nullptr, QObject *parent = nullptr);
/**
* @overload
* Overload passing in the xcb_connection_t to use instead relying on platform xcb.
*
* @param connection The xcb connection
* @param rootWindow The rootWindow to use
* @param accept_broadcast if non-nullptr, all broadcast messages with
* this message type will be received.
* @param parent the parent of this object
* @since 5.8
**/
explicit KXMessages(xcb_connection_t *connection, xcb_window_t rootWindow, const char *accept_broadcast = nullptr, QObject *parent = nullptr);
~KXMessages() override;
/**
* Broadcasts the given message with the given message type.
* @param msg_type the type of the message
* @param message the message itself
* @param screen X11 screen to use, -1 for the default
*/
void broadcastMessage(const char *msg_type, const QString &message, int screen = -1);
/**
* Broadcasts the given message with the given message type.
*
* @param c X11 connection which will be used instead of
* QX11Info::connection()
* @param msg_type the type of the message
* @param message the message itself
* @param screenNumber X11 screen to use
* @return false when an error occurred, true otherwise
*/
static bool broadcastMessageX(xcb_connection_t *c, const char *msg_type, const QString &message, int screenNumber);
Q_SIGNALS:
/**
* Emitted when a message was received.
* @param message the message that has been received
*/
void gotMessage(const QString &message);
private:
friend class KXMessagesPrivate;
KXMessagesPrivate *const d;
};
#endif
#endif
@@ -0,0 +1,146 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2008 Lubos Lunak <l.lunak@kde.org>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "cptr_p.h"
#include "kxutils_p.h"
#include <QBitmap>
#include <QDebug>
#include <private/qtx11extras_p.h>
#include <xcb/xcb.h>
namespace KXUtils
{
template<typename T>
T fromNative(xcb_pixmap_t pixmap, xcb_connection_t *c)
{
const xcb_get_geometry_cookie_t geoCookie = xcb_get_geometry_unchecked(c, pixmap);
UniqueCPointer<xcb_get_geometry_reply_t> geo(xcb_get_geometry_reply(c, geoCookie, nullptr));
if (!geo) {
// getting geometry for the pixmap failed
return T();
}
const xcb_get_image_cookie_t imageCookie = xcb_get_image_unchecked(c, XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, 0, 0, geo->width, geo->height, ~0);
UniqueCPointer<xcb_get_image_reply_t> xImage(xcb_get_image_reply(c, imageCookie, nullptr));
if (!xImage) {
// request for image data failed
return T();
}
QImage::Format format = QImage::Format_Invalid;
switch (xImage->depth) {
case 1:
format = QImage::Format_MonoLSB;
break;
case 16:
format = QImage::Format_RGB16;
break;
case 24:
format = QImage::Format_RGB32;
break;
case 30: {
// Qt doesn't have a matching image format. We need to convert manually
uint32_t *pixels = reinterpret_cast<uint32_t *>(xcb_get_image_data(xImage.get()));
for (uint i = 0; i < xImage.get()->length; ++i) {
int r = (pixels[i] >> 22) & 0xff;
int g = (pixels[i] >> 12) & 0xff;
int b = (pixels[i] >> 2) & 0xff;
pixels[i] = qRgba(r, g, b, 0xff);
}
// fall through, Qt format is still Format_ARGB32_Premultiplied
Q_FALLTHROUGH();
}
case 32:
format = QImage::Format_ARGB32_Premultiplied;
break;
default:
return T(); // we don't know
}
QImage image(xcb_get_image_data(xImage.get()), geo->width, geo->height, xcb_get_image_data_length(xImage.get()) / geo->height, format, free, xImage.get());
xImage.release();
if (image.isNull()) {
return T();
}
if (image.format() == QImage::Format_MonoLSB) {
// work around an abort in QImage::color
image.setColorCount(2);
image.setColor(0, QColor(Qt::white).rgb());
image.setColor(1, QColor(Qt::black).rgb());
}
return T::fromImage(image);
}
// Create QPixmap from X pixmap. Take care of different depths if needed.
QPixmap createPixmapFromHandle(WId pixmap, WId pixmap_mask)
{
return createPixmapFromHandle(QX11Info::connection(), pixmap, pixmap_mask);
}
QPixmap createPixmapFromHandle(xcb_connection_t *c, WId pixmap, WId pixmap_mask)
{
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
qDebug() << "Byte order not supported";
return QPixmap();
#endif
const xcb_setup_t *setup = xcb_get_setup(c);
if (setup->image_byte_order != XCB_IMAGE_ORDER_LSB_FIRST) {
qDebug() << "Byte order not supported";
return QPixmap();
}
QPixmap pix = fromNative<QPixmap>(pixmap, c);
if (pixmap_mask != XCB_PIXMAP_NONE) {
QBitmap mask = fromNative<QBitmap>(pixmap_mask, c);
if (mask.size() != pix.size()) {
return QPixmap();
}
pix.setMask(mask);
}
return pix;
}
// Functions for X timestamp comparing. For Time being 32bit they're fairly simple
// (the #if 0 part), but on 64bit architectures Time is 64bit unsigned long,
// so there special care needs to be taken to always use only the lower 32bits.
#if 0
int timestampCompare(Time time1, Time time2) // like strcmp()
{
if (time1 == time2) {
return 0;
}
return (time1 - time2) < 0x7fffffffU ? 1 : -1; // time1 > time2 -> 1, handle wrapping
}
Time timestampDiff(Time time1, Time time2) // returns time2 - time1
{
// no need to handle wrapping?
return time2 - time1;
}
#else
int timestampCompare(unsigned long time1_, unsigned long time2_) // like strcmp()
{
quint32 time1 = time1_;
quint32 time2 = time2_;
if (time1 == time2) {
return 0;
}
return quint32(time1 - time2) < 0x7fffffffU ? 1 : -1; // time1 > time2 -> 1, handle wrapping
}
int timestampDiff(unsigned long time1_, unsigned long time2_) // returns time2 - time1
{
// no need to handle wrapping?
quint32 time1 = time1_;
quint32 time2 = time2_;
return quint32(time2 - time1);
}
#endif
} // namespace
@@ -0,0 +1,52 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2008 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KXUTILS_H
#define KXUTILS_H
#include <QPixmap>
#include <config-kwindowsystem.h>
#if KWINDOWSYSTEM_HAVE_X11
#include <kwindowsystem_export.h>
struct xcb_connection_t;
/**
* Namespace with various generic X11-related functionality.
*/
namespace KXUtils
{
/**
* Creates a QPixmap that contains a copy of the pixmap given by the X handle @p pixmap
* and optionally also mask given as another X handle @mask. This function tries to
* also handle the case when the depth of the pixmap differs from the native QPixmap depth.
* @since 4.0.2
*/
QPixmap createPixmapFromHandle(WId pixmap, WId mask = 0);
QPixmap createPixmapFromHandle(xcb_connection_t *c, WId pixmap, WId mask = 0);
/**
* Compares two X timestamps, taking into account wrapping and 64bit architectures.
* Return value is like with strcmp(), 0 for equal, -1 for time1 < time2, 1 for time1 > time2.
* @since 4.1.0
*/
int timestampCompare(unsigned long time1, unsigned long time2);
/**
* Returns a difference of two X timestamps, time2 - time1, where time2 must be later than time1,
* as returned by timestampCompare().
* @since 4.1.0
*/
int timestampDiff(unsigned long time1, unsigned long time2);
} // namespace
#endif // KWINDOWSYSTEM_HAVE_X11
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,195 @@
/*
SPDX-FileCopyrightText: 2000 Troll Tech AS
SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef netwm_p_h
#define netwm_p_h
#include <QSharedData>
#include <QSharedDataPointer>
#include "atoms_p.h"
class Atoms : public QSharedData
{
public:
explicit Atoms(xcb_connection_t *c);
xcb_atom_t atom(KwsAtom atom) const
{
return m_atoms[atom];
}
private:
void init();
xcb_atom_t m_atoms[KwsAtomCount];
xcb_connection_t *m_connection;
};
/**
Resizable array class.
This resizable array is used to simplify the implementation. The existence of
this class is to keep the implementation from depending on a separate
framework/library.
@internal
**/
template<class Z>
class NETRArray
{
public:
/**
Constructs an empty (size == 0) array.
**/
NETRArray();
/**
Resizable array destructor.
**/
~NETRArray();
/**
The [] operator does the work. If the index is larger than the current
size of the array, it is resized.
**/
Z &operator[](int);
/**
Returns the size of the array.
**/
int size() const
{
return sz;
}
/**
Resets the array (size == 0).
**/
void reset();
private:
int sz;
int capacity;
Z *d;
};
/**
Private data for the NETRootInfo class.
@internal
**/
struct NETRootInfoPrivate {
NET::Role role;
// information about the X server
xcb_connection_t *conn;
NETSize rootSize;
xcb_window_t root;
xcb_window_t supportwindow;
const char *name;
uint32_t *temp_buf;
size_t temp_buf_size;
// data that changes (either by the window manager or by a client)
// and requires updates
NETRArray<NETPoint> viewport;
NETRArray<NETRect> workarea;
NETSize geometry;
xcb_window_t active;
xcb_window_t *clients, *stacking, *virtual_roots;
NETRArray<const char *> desktop_names;
int number_of_desktops;
int current_desktop;
unsigned long clients_count, stacking_count, virtual_roots_count;
bool showing_desktop;
NET::Orientation desktop_layout_orientation;
NET::DesktopLayoutCorner desktop_layout_corner;
int desktop_layout_columns, desktop_layout_rows;
NET::Properties properties;
NET::Properties2 properties2;
NET::WindowTypes windowTypes;
NET::States states;
NET::Actions actions;
NET::Properties clientProperties;
NET::Properties2 clientProperties2;
int ref;
QSharedDataPointer<Atoms> atoms;
xcb_atom_t atom(KwsAtom atom) const
{
return atoms->atom(atom);
}
};
/**
Private data for the NETWinInfo class.
@internal
**/
struct NETWinInfoPrivate {
NET::Role role;
xcb_connection_t *conn;
xcb_window_t window, root;
NET::MappingState mapping_state;
bool mapping_state_dirty;
NETRArray<NETIcon> icons;
int icon_count;
int *icon_sizes; // for iconSizes() only
NETRect icon_geom, win_geom;
NET::States state;
NETExtendedStrut extended_strut;
NETStrut strut;
NETStrut frame_strut; // strut?
NETStrut frame_overlap;
NETStrut gtk_frame_extents;
NETRArray<NET::WindowType> types;
char *name, *visible_name, *icon_name, *visible_icon_name;
int desktop;
int pid;
bool handled_icons;
xcb_timestamp_t user_time;
char *startup_id;
unsigned long opacity;
xcb_window_t transient_for, window_group;
xcb_pixmap_t icon_pixmap, icon_mask;
NET::Actions allowed_actions;
char *class_class, *class_name, *window_role, *client_machine, *desktop_file, *appmenu_object_path, *appmenu_service_name, *gtk_application_id;
NET::Properties properties;
NET::Properties2 properties2;
NETFullscreenMonitors fullscreen_monitors;
bool has_net_support;
const char *activities;
bool blockCompositing;
bool urgency;
bool input;
NET::MappingState initialMappingState;
NET::Protocols protocols;
std::vector<NETRect> opaqueRegion;
int ref;
QSharedDataPointer<Atoms> atoms;
xcb_atom_t atom(KwsAtom atom) const
{
return atoms->atom(atom);
}
};
#endif // netwm_p_h
@@ -0,0 +1,41 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "plugin.h"
#include "kwindoweffects_x11.h"
#include "kwindowshadow_p_x11.h"
#include "kwindowsystem_p_x11.h"
X11Plugin::X11Plugin(QObject *parent)
: KWindowSystemPluginInterface(parent)
{
}
X11Plugin::~X11Plugin()
{
}
KWindowEffectsPrivate *X11Plugin::createEffects()
{
return new KWindowEffectsPrivateX11();
}
KWindowSystemPrivate *X11Plugin::createWindowSystem()
{
return new KWindowSystemPrivateX11();
}
KWindowShadowPrivate *X11Plugin::createWindowShadow()
{
return new KWindowShadowPrivateX11();
}
KWindowShadowTilePrivate *X11Plugin::createWindowShadowTile()
{
return new KWindowShadowTilePrivateX11();
}
#include "moc_plugin.cpp"
@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KWINDOWSYSTEM_X11_PLUGIN_H
#define KWINDOWSYSTEM_X11_PLUGIN_H
#include "kwindowsystemplugininterface_p.h"
class X11Plugin : public KWindowSystemPluginInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kde.kwindowsystem.KWindowSystemPluginInterface" FILE "xcb.json")
Q_INTERFACES(KWindowSystemPluginInterface)
public:
explicit X11Plugin(QObject *parent = nullptr);
~X11Plugin() override;
KWindowEffectsPrivate *createEffects() override;
KWindowSystemPrivate *createWindowSystem() override;
KWindowShadowPrivate *createWindowShadow() override final;
KWindowShadowTilePrivate *createWindowShadowTile() override final;
};
#endif
@@ -0,0 +1,3 @@
{
"platforms": ["xcb"]
}