Files
RedBear-OS/local/recipes/kde/kf6-kwindowsystem/source/src/kx11extras.cpp
T
2026-04-14 10:51:06 +01:00

1266 lines
44 KiB
C++

/*
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 "kx11extras.h"
// clang-format off
#include <kxerrorhandler_p.h>
#include <fixx11h.h>
#include <kxutils_p.h>
// clang-format on
#include "cptr_p.h"
#include "kwindowsystem.h"
#include "kwindowsystem_debug.h"
#include "netwm.h"
#include "kxcbevent_p.h"
#include <QAbstractNativeEventFilter>
#include <QGuiApplication>
#include <QMetaMethod>
#include <QRect>
#include <QScreen>
#include <private/qtx11extras_p.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h>
#include <xcb/xcb.h>
#include <xcb/xfixes.h>
// QPoint and QSize all have handy / operators which are useful for scaling, positions and sizes for high DPI support
// QRect does not, so we create one for internal purposes within this class
inline QRect operator/(const QRect &rectangle, qreal factor)
{
return QRect(rectangle.topLeft() / factor, rectangle.size() / factor);
}
class MainThreadInstantiator : public QObject
{
Q_OBJECT
public:
MainThreadInstantiator(KX11Extras::FilterInfo _what);
Q_INVOKABLE NETEventFilter *createNETEventFilter();
private:
KX11Extras::FilterInfo m_what;
};
class NETEventFilter : public NETRootInfo, public QAbstractNativeEventFilter
{
public:
NETEventFilter(KX11Extras::FilterInfo _what);
~NETEventFilter() override;
void activate();
QList<WId> windows;
QList<WId> stackingOrder;
struct StrutData {
StrutData(WId window_, const NETStrut &strut_, int desktop_)
: window(window_)
, strut(strut_)
, desktop(desktop_)
{
}
WId window;
NETStrut strut;
int desktop;
};
QList<StrutData> strutWindows;
QList<WId> possibleStrutWindows;
bool strutSignalConnected;
bool compositingEnabled;
bool haveXfixes;
KX11Extras::FilterInfo what;
int xfixesEventBase;
bool mapViewport();
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override;
void updateStackingOrder();
bool removeStrutWindow(WId);
protected:
void addClient(xcb_window_t) override;
void removeClient(xcb_window_t) override;
private:
bool nativeEventFilter(xcb_generic_event_t *event);
xcb_window_t winId;
xcb_window_t m_appRootWindow;
};
static Atom net_wm_cm;
static void create_atoms();
static inline const QRect &displayGeometry()
{
static QRect displayGeometry;
static bool isDirty = true;
if (isDirty) {
static QList<QMetaObject::Connection> connections;
auto dirtify = [&] {
isDirty = true;
for (const QMetaObject::Connection &con : std::as_const(connections)) {
QObject::disconnect(con);
}
connections.clear();
};
QObject::connect(qApp, &QGuiApplication::screenAdded, dirtify);
QObject::connect(qApp, &QGuiApplication::screenRemoved, dirtify);
const QList<QScreen *> screenList = QGuiApplication::screens();
QRegion region;
for (int i = 0; i < screenList.count(); ++i) {
const QScreen *screen = screenList.at(i);
connections << QObject::connect(screen, &QScreen::geometryChanged, dirtify);
const QRect geometry = screen->geometry();
const qreal dpr = screen->devicePixelRatio();
region += QRect(geometry.topLeft(), geometry.size() * dpr);
}
displayGeometry = region.boundingRect();
isDirty = false;
}
return displayGeometry;
}
static inline int displayWidth()
{
return displayGeometry().width();
}
static inline int displayHeight()
{
return displayGeometry().height();
}
// clang-format off
static const NET::Properties windowsProperties = NET::ClientList | NET::ClientListStacking |
NET::Supported |
NET::NumberOfDesktops |
NET::DesktopGeometry |
NET::DesktopViewport |
NET::CurrentDesktop |
NET::DesktopNames |
NET::ActiveWindow |
NET::WorkArea;
static const NET::Properties2 windowsProperties2 = NET::WM2ShowingDesktop;
// ClientList and ClientListStacking is not per-window information, but a desktop information,
// so track it even with only INFO_BASIC
static const NET::Properties desktopProperties = NET::ClientList | NET::ClientListStacking |
NET::Supported |
NET::NumberOfDesktops |
NET::DesktopGeometry |
NET::DesktopViewport |
NET::CurrentDesktop |
NET::DesktopNames |
NET::ActiveWindow |
NET::WorkArea;
static const NET::Properties2 desktopProperties2 = NET::WM2ShowingDesktop;
// clang-format on
MainThreadInstantiator::MainThreadInstantiator(KX11Extras::FilterInfo _what)
: QObject()
, m_what(_what)
{
}
NETEventFilter *MainThreadInstantiator::createNETEventFilter()
{
return new NETEventFilter(m_what);
}
NETEventFilter::NETEventFilter(KX11Extras::FilterInfo _what)
: NETRootInfo(QX11Info::connection(),
_what >= KX11Extras::INFO_WINDOWS ? windowsProperties : desktopProperties,
_what >= KX11Extras::INFO_WINDOWS ? windowsProperties2 : desktopProperties2,
QX11Info::appScreen(),
false)
, QAbstractNativeEventFilter()
, strutSignalConnected(false)
, compositingEnabled(false)
, haveXfixes(false)
, what(_what)
, winId(XCB_WINDOW_NONE)
, m_appRootWindow(QX11Info::appRootWindow())
{
QCoreApplication::instance()->installNativeEventFilter(this);
int errorBase;
if ((haveXfixes = XFixesQueryExtension(QX11Info::display(), &xfixesEventBase, &errorBase))) {
create_atoms();
winId = xcb_generate_id(QX11Info::connection());
uint32_t values[] = {true, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
xcb_create_window(QX11Info::connection(),
XCB_COPY_FROM_PARENT,
winId,
m_appRootWindow,
0,
0,
1,
1,
0,
XCB_WINDOW_CLASS_INPUT_ONLY,
XCB_COPY_FROM_PARENT,
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
values);
XFixesSelectSelectionInput(QX11Info::display(),
winId,
net_wm_cm,
XFixesSetSelectionOwnerNotifyMask | XFixesSelectionWindowDestroyNotifyMask | XFixesSelectionClientCloseNotifyMask);
compositingEnabled = XGetSelectionOwner(QX11Info::display(), net_wm_cm) != None;
}
}
NETEventFilter::~NETEventFilter()
{
if (QX11Info::connection() && winId != XCB_WINDOW_NONE) {
xcb_destroy_window(QX11Info::connection(), winId);
winId = XCB_WINDOW_NONE;
}
}
// not virtual, but it's called directly only from init()
void NETEventFilter::activate()
{
NETRootInfo::activate();
updateStackingOrder();
}
bool NETEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *)
{
if (eventType != "xcb_generic_event_t") {
// only interested in XCB events of course
return false;
}
return nativeEventFilter(reinterpret_cast<xcb_generic_event_t *>(message));
}
bool NETEventFilter::nativeEventFilter(xcb_generic_event_t *ev)
{
KWindowSystem *s_q = KWindowSystem::self();
const uint8_t eventType = ev->response_type & ~0x80;
if (eventType == xfixesEventBase + XCB_XFIXES_SELECTION_NOTIFY) {
xcb_xfixes_selection_notify_event_t *event = reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(ev);
if (event->window == winId) {
bool haveOwner = event->owner != XCB_WINDOW_NONE;
if (compositingEnabled != haveOwner) {
compositingEnabled = haveOwner;
Q_EMIT KX11Extras::self()->compositingChanged(compositingEnabled);
}
return true;
}
// Qt compresses XFixesSelectionNotifyEvents without caring about the actual window
// gui/kernel/qapplication_x11.cpp
// until that can be assumed fixed, we also react on events on the root (caused by Qts own compositing tracker)
if (event->window == m_appRootWindow) {
if (event->selection == net_wm_cm) {
bool haveOwner = event->owner != XCB_WINDOW_NONE;
if (compositingEnabled != haveOwner) {
compositingEnabled = haveOwner;
Q_EMIT KX11Extras::self()->compositingChanged(compositingEnabled);
}
// NOTICE this is not our event, we just randomly captured it from Qt -> pass on
return false;
}
}
return false;
}
xcb_window_t eventWindow = XCB_WINDOW_NONE;
switch (eventType) {
case XCB_CLIENT_MESSAGE:
eventWindow = reinterpret_cast<xcb_client_message_event_t *>(ev)->window;
break;
case XCB_PROPERTY_NOTIFY:
eventWindow = reinterpret_cast<xcb_property_notify_event_t *>(ev)->window;
break;
case XCB_CONFIGURE_NOTIFY:
eventWindow = reinterpret_cast<xcb_configure_notify_event_t *>(ev)->window;
break;
}
if (eventWindow == m_appRootWindow) {
int old_current_desktop = currentDesktop();
xcb_window_t old_active_window = activeWindow();
int old_number_of_desktops = numberOfDesktops();
bool old_showing_desktop = showingDesktop();
NET::Properties props;
NET::Properties2 props2;
NETRootInfo::event(ev, &props, &props2);
if ((props & CurrentDesktop) && currentDesktop() != old_current_desktop) {
Q_EMIT KX11Extras::self()->currentDesktopChanged(currentDesktop());
}
if ((props & DesktopViewport) && mapViewport() && currentDesktop() != old_current_desktop) {
Q_EMIT KX11Extras::self()->currentDesktopChanged(currentDesktop());
}
if ((props & ActiveWindow) && activeWindow() != old_active_window) {
Q_EMIT KX11Extras::self()->activeWindowChanged(activeWindow());
}
if (props & DesktopNames) {
Q_EMIT KX11Extras::self()->desktopNamesChanged();
}
if ((props & NumberOfDesktops) && numberOfDesktops() != old_number_of_desktops) {
Q_EMIT KX11Extras::self()->numberOfDesktopsChanged(numberOfDesktops());
}
if ((props & DesktopGeometry) && mapViewport() && numberOfDesktops() != old_number_of_desktops) {
Q_EMIT KX11Extras::self()->numberOfDesktopsChanged(numberOfDesktops());
}
if (props & WorkArea) {
Q_EMIT KX11Extras::self()->workAreaChanged();
}
if (props & ClientListStacking) {
updateStackingOrder();
Q_EMIT KX11Extras::self()->stackingOrderChanged();
}
if ((props2 & WM2ShowingDesktop) && showingDesktop() != old_showing_desktop) {
Q_EMIT s_q->showingDesktopChanged(showingDesktop());
}
} else if (windows.contains(eventWindow)) {
NETWinInfo ni(QX11Info::connection(), eventWindow, m_appRootWindow, NET::Properties(), NET::Properties2());
NET::Properties dirtyProperties;
NET::Properties2 dirtyProperties2;
ni.event(ev, &dirtyProperties, &dirtyProperties2);
if (eventType == XCB_PROPERTY_NOTIFY) {
xcb_property_notify_event_t *event = reinterpret_cast<xcb_property_notify_event_t *>(ev);
if (event->atom == XCB_ATOM_WM_HINTS) {
dirtyProperties |= NET::WMIcon; // support for old icons
} else if (event->atom == XCB_ATOM_WM_NAME) {
dirtyProperties |= NET::WMName; // support for old name
} else if (event->atom == XCB_ATOM_WM_ICON_NAME) {
dirtyProperties |= NET::WMIconName; // support for old iconic name
}
}
if (mapViewport() && (dirtyProperties & (NET::WMState | NET::WMGeometry))) {
/* geometry change -> possible viewport change
* state change -> possible NET::Sticky change
*/
dirtyProperties |= NET::WMDesktop;
}
if ((dirtyProperties & NET::WMStrut) != 0) {
removeStrutWindow(eventWindow);
if (!possibleStrutWindows.contains(eventWindow)) {
possibleStrutWindows.append(eventWindow);
}
}
if (dirtyProperties || dirtyProperties2) {
Q_EMIT KX11Extras::self()->windowChanged(eventWindow, dirtyProperties, dirtyProperties2);
if ((dirtyProperties & NET::WMStrut) != 0) {
Q_EMIT KX11Extras::self()->strutChanged();
}
}
}
return false;
}
bool NETEventFilter::removeStrutWindow(WId w)
{
for (QList<StrutData>::Iterator it = strutWindows.begin(); it != strutWindows.end(); ++it) {
if ((*it).window == w) {
strutWindows.erase(it);
return true;
}
}
return false;
}
void NETEventFilter::updateStackingOrder()
{
stackingOrder.clear();
for (int i = 0; i < clientListStackingCount(); i++) {
stackingOrder.append(clientListStacking()[i]);
}
}
void NETEventFilter::addClient(xcb_window_t w)
{
if ((what >= KX11Extras::INFO_WINDOWS)) {
xcb_connection_t *c = QX11Info::connection();
UniqueCPointer<xcb_get_window_attributes_reply_t> attr(xcb_get_window_attributes_reply(c, xcb_get_window_attributes_unchecked(c, w), nullptr));
uint32_t events = XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY;
if (attr) {
events = events | attr->your_event_mask;
}
xcb_change_window_attributes(c, w, XCB_CW_EVENT_MASK, &events);
}
bool emit_strutChanged = false;
if (strutSignalConnected) {
NETWinInfo info(QX11Info::connection(), w, QX11Info::appRootWindow(), NET::WMStrut | NET::WMDesktop, NET::Properties2());
NETStrut strut = info.strut();
if (strut.left || strut.top || strut.right || strut.bottom) {
strutWindows.append(StrutData(w, strut, info.desktop()));
emit_strutChanged = true;
}
} else {
possibleStrutWindows.append(w);
}
windows.append(w);
Q_EMIT KX11Extras::self()->windowAdded(w);
if (emit_strutChanged) {
Q_EMIT KX11Extras::self()->strutChanged();
}
}
void NETEventFilter::removeClient(xcb_window_t w)
{
bool emit_strutChanged = removeStrutWindow(w);
if (strutSignalConnected && possibleStrutWindows.contains(w)) {
NETWinInfo info(QX11Info::connection(), w, QX11Info::appRootWindow(), NET::WMStrut, NET::Properties2());
NETStrut strut = info.strut();
if (strut.left || strut.top || strut.right || strut.bottom) {
emit_strutChanged = true;
}
}
possibleStrutWindows.removeAll(w);
windows.removeAll(w);
Q_EMIT KX11Extras::self()->windowRemoved(w);
if (emit_strutChanged) {
Q_EMIT KX11Extras::self()->strutChanged();
}
}
bool NETEventFilter::mapViewport()
{
// compiz claims support even though it doesn't use virtual desktops :(
// if( isSupported( NET::DesktopViewport ) && !isSupported( NET::NumberOfDesktops ))
// this test is duplicated in KWindowSystem::mapViewport()
if (isSupported(NET::DesktopViewport) && numberOfDesktops(true) <= 1
&& (desktopGeometry().width > displayWidth() || desktopGeometry().height > displayHeight())) {
return true;
}
return false;
}
static bool atoms_created = false;
static Atom _wm_protocols;
static Atom _wm_change_state;
static Atom kwm_utf8_string;
static void create_atoms()
{
if (!atoms_created) {
const int max = 20;
Atom *atoms[max];
const char *names[max];
Atom atoms_return[max];
int n = 0;
atoms[n] = &_wm_protocols;
names[n++] = "WM_PROTOCOLS";
atoms[n] = &_wm_change_state;
names[n++] = "WM_CHANGE_STATE";
atoms[n] = &kwm_utf8_string;
names[n++] = "UTF8_STRING";
char net_wm_cm_name[100];
sprintf(net_wm_cm_name, "_NET_WM_CM_S%d", QX11Info::appScreen());
atoms[n] = &net_wm_cm;
names[n++] = net_wm_cm_name;
// we need a const_cast for the shitty X API
XInternAtoms(QX11Info::display(), const_cast<char **>(names), n, false, atoms_return);
for (int i = 0; i < n; i++) {
*atoms[i] = atoms_return[i];
}
atoms_created = True;
}
}
#define CHECK_X11 \
if (!KWindowSystem::isPlatformX11()) { \
qCWarning(LOG_KWINDOWSYSTEM) << Q_FUNC_INFO << "may only be used on X11"; \
return {}; \
}
#define CHECK_X11_VOID \
if (!KWindowSystem::isPlatformX11()) { \
qCWarning(LOG_KWINDOWSYSTEM) << Q_FUNC_INFO << "may only be used on X11"; \
return; \
}
// WARNING
// you have to call s_d_func() again after calling this function if you want a valid pointer!
void KX11Extras::init(FilterInfo what)
{
NETEventFilter *const s_d = s_d_func();
if (what >= INFO_WINDOWS) {
what = INFO_WINDOWS;
} else {
what = INFO_BASIC;
}
if (!s_d || s_d->what < what) {
const bool wasCompositing = s_d ? s_d->compositingEnabled : false;
MainThreadInstantiator instantiator(what);
NETEventFilter *filter;
if (instantiator.thread() == QCoreApplication::instance()->thread()) {
filter = instantiator.createNETEventFilter();
} else {
// the instantiator is not in the main app thread, which implies
// we are being called in a thread that is not the main app thread
// so we move the instantiator to the main app thread and invoke
// the method with a blocking call
instantiator.moveToThread(QCoreApplication::instance()->thread());
QMetaObject::invokeMethod(&instantiator, "createNETEventFilter", Qt::BlockingQueuedConnection, Q_RETURN_ARG(NETEventFilter *, filter));
}
d.reset(filter);
d->activate();
if (wasCompositing != s_d_func()->compositingEnabled) {
Q_EMIT KX11Extras::self()->compositingChanged(s_d_func()->compositingEnabled);
}
}
}
KX11Extras *KX11Extras::self()
{
static KX11Extras instance;
return &instance;
}
QList<WId> KX11Extras::windows()
{
CHECK_X11
KX11Extras::self()->init(INFO_BASIC);
return KX11Extras::self()->s_d_func()->windows;
}
bool KX11Extras::hasWId(WId w)
{
CHECK_X11
return windows().contains(w);
}
QList<WId> KX11Extras::stackingOrder()
{
CHECK_X11
KX11Extras::self()->init(INFO_BASIC);
return KX11Extras::self()->s_d_func()->stackingOrder;
}
WId KX11Extras::activeWindow()
{
CHECK_X11
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
if (s_d) {
return s_d->activeWindow();
}
NETRootInfo info(QX11Info::connection(), NET::ActiveWindow, NET::Properties2(), QX11Info::appScreen());
return info.activeWindow();
}
void KX11Extras::activateWindow(WId win, long time)
{
CHECK_X11_VOID
NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen());
if (time == 0) {
time = QX11Info::appUserTime();
}
info.setActiveWindow(win, NET::FromApplication, time, QGuiApplication::focusWindow() ? QGuiApplication::focusWindow()->winId() : 0);
}
void KX11Extras::forceActiveWindow(WId win, long time)
{
CHECK_X11_VOID
NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen());
if (time == 0) {
time = QX11Info::appTime();
}
info.setActiveWindow(win, NET::FromTool, time, 0);
}
void KX11Extras::forceActiveWindow(QWindow *win, long time)
{
CHECK_X11_VOID
forceActiveWindow(win->winId(), time);
}
bool KX11Extras::compositingActive()
{
CHECK_X11
KX11Extras::self()->init(INFO_BASIC);
if (KX11Extras::self()->s_d_func()->haveXfixes) {
return KX11Extras::self()->s_d_func()->compositingEnabled;
} else {
create_atoms();
return XGetSelectionOwner(QX11Info::display(), net_wm_cm);
}
}
int KX11Extras::currentDesktop()
{
CHECK_X11
if (!QX11Info::connection()) {
return 1;
}
if (mapViewport()) {
KX11Extras::self()->init(INFO_BASIC);
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
NETPoint p = s_d->desktopViewport(s_d->currentDesktop(true));
return KX11Extras::self()->viewportToDesktop(QPoint(p.x, p.y) / qApp->devicePixelRatio());
}
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
if (s_d) {
return s_d->currentDesktop(true);
}
NETRootInfo info(QX11Info::connection(), NET::CurrentDesktop, NET::Properties2(), QX11Info::appScreen());
return info.currentDesktop(true);
}
int KX11Extras::numberOfDesktops()
{
CHECK_X11
if (!QX11Info::connection()) {
return 1;
}
if (mapViewport()) {
KX11Extras::self()->init(INFO_BASIC);
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
NETSize s = s_d->desktopGeometry();
return s.width / displayWidth() * s.height / displayHeight();
}
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
if (s_d) {
return s_d->numberOfDesktops(true);
}
NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops, NET::Properties2(), QX11Info::appScreen());
return info.numberOfDesktops(true);
}
void KX11Extras::setCurrentDesktop(int desktop)
{
CHECK_X11_VOID
if (mapViewport()) {
KX11Extras::self()->init(INFO_BASIC);
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen());
QPoint pos = KX11Extras::self()->desktopToViewport(desktop, true);
NETPoint p;
p.x = pos.x();
p.y = pos.y();
info.setDesktopViewport(s_d->currentDesktop(true), p);
return;
}
NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen());
info.setCurrentDesktop(desktop, true);
}
void KX11Extras::setOnAllDesktops(WId win, bool b)
{
CHECK_X11_VOID
if (mapViewport()) {
if (b) {
setState(win, NET::Sticky);
} else {
clearState(win, NET::Sticky);
}
return;
}
NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMDesktop, NET::Properties2());
if (b) {
info.setDesktop(NETWinInfo::OnAllDesktops, true);
} else if (info.desktop(true) == NETWinInfo::OnAllDesktops) {
NETRootInfo rinfo(QX11Info::connection(), NET::CurrentDesktop, NET::Properties2(), QX11Info::appScreen());
info.setDesktop(rinfo.currentDesktop(true), true);
}
}
void KX11Extras::setOnDesktop(WId win, int desktop)
{
CHECK_X11_VOID
if (mapViewport()) {
if (desktop == NET::OnAllDesktops) {
return setOnAllDesktops(win, true);
} else {
clearState(win, NET::Sticky);
}
KX11Extras::self()->init(INFO_BASIC);
QPoint p = KX11Extras::self()->desktopToViewport(desktop, false);
Window dummy;
int x;
int y;
unsigned int w;
unsigned int h;
unsigned int b;
unsigned int dp;
XGetGeometry(QX11Info::display(), win, &dummy, &x, &y, &w, &h, &b, &dp);
// get global position
XTranslateCoordinates(QX11Info::display(), win, QX11Info::appRootWindow(), 0, 0, &x, &y, &dummy);
x += w / 2; // center
y += h / 2;
// transform to coordinates on the current "desktop"
x = x % displayWidth();
y = y % displayHeight();
if (x < 0) {
x = x + displayWidth();
}
if (y < 0) {
y = y + displayHeight();
}
x += p.x(); // move to given "desktop"
y += p.y();
x -= w / 2; // from center back to topleft
y -= h / 2;
p = KX11Extras::self()->constrainViewportRelativePosition(QPoint(x, y));
int flags = (NET::FromTool << 12) | (0x03 << 8) | 10; // from tool(?), x/y, static gravity
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
s_d->moveResizeWindowRequest(win, flags, p.x(), p.y(), w, h);
return;
}
NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMDesktop, NET::Properties2());
info.setDesktop(desktop, true);
}
void KX11Extras::setOnActivities(WId win, const QStringList &activities)
{
CHECK_X11_VOID
NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::WM2Activities);
info.setActivities(activities.join(QLatin1Char(',')).toLatin1().constData());
}
QPixmap KX11Extras::icon(WId win, int width, int height, bool scale)
{
CHECK_X11
return icon(win, width, height, scale, NETWM | WMHints | ClassHint | XApp);
}
QPixmap iconFromNetWinInfo(int width, int height, bool scale, int flags, NETWinInfo *info)
{
QPixmap result;
if (!info) {
return result;
}
if (flags & KX11Extras::NETWM) {
NETIcon ni = info->icon(width, height);
if (ni.data && ni.size.width > 0 && ni.size.height > 0) {
QImage img((uchar *)ni.data, (int)ni.size.width, (int)ni.size.height, QImage::Format_ARGB32);
if (scale && width > 0 && height > 0 && img.size() != QSize(width, height) && !img.isNull()) {
img = img.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
if (!img.isNull()) {
result = QPixmap::fromImage(img);
}
return result;
}
}
if (flags & KX11Extras::WMHints) {
xcb_pixmap_t p = info->icccmIconPixmap();
xcb_pixmap_t p_mask = info->icccmIconPixmapMask();
if (p != XCB_PIXMAP_NONE) {
QPixmap pm = KXUtils::createPixmapFromHandle(info->xcbConnection(), p, p_mask);
if (scale && width > 0 && height > 0 && !pm.isNull() //
&& (pm.width() != width || pm.height() != height)) {
result = QPixmap::fromImage(pm.toImage().scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else {
result = pm;
}
}
}
// Since width can be any arbitrary size, but the icons cannot,
// take the nearest value for best results (ignoring 22 pixel
// icons as they don't exist for apps):
int iconWidth;
if (width < 24) {
iconWidth = 16;
} else if (width < 40) {
iconWidth = 32;
} else if (width < 56) {
iconWidth = 48;
} else if (width < 96) {
iconWidth = 64;
} else if (width < 192) {
iconWidth = 128;
} else {
iconWidth = 256;
}
if (flags & KX11Extras::ClassHint) {
// Try to load the icon from the classhint if the app didn't specify
// its own:
if (result.isNull()) {
const QIcon icon = QIcon::fromTheme(QString::fromUtf8(info->windowClassClass()).toLower());
const QPixmap pm = icon.isNull() ? QPixmap() : icon.pixmap(iconWidth, iconWidth);
if (scale && !pm.isNull()) {
result = QPixmap::fromImage(pm.toImage().scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else {
result = pm;
}
}
}
if (flags & KX11Extras::XApp) {
// If the icon is still a null pixmap, load the icon for X applications
// as a last resort:
if (result.isNull()) {
const QIcon icon = QIcon::fromTheme(QStringLiteral("xorg"));
const QPixmap pm = icon.isNull() ? QPixmap() : icon.pixmap(iconWidth, iconWidth);
if (scale && !pm.isNull()) {
result = QPixmap::fromImage(pm.toImage().scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else {
result = pm;
}
}
}
return result;
}
QPixmap KX11Extras::icon(WId win, int width, int height, bool scale, int flags)
{
CHECK_X11
NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMIcon, NET::WM2WindowClass | NET::WM2IconPixmap);
return iconFromNetWinInfo(width, height, scale, flags, &info);
}
QPixmap KX11Extras::icon(WId win, int width, int height, bool scale, int flags, NETWinInfo *info)
{
// No CHECK_X11 here, kwin_wayland calls this to get the icon for XWayland windows
width *= qGuiApp->devicePixelRatio();
height *= qGuiApp->devicePixelRatio();
if (info) {
return iconFromNetWinInfo(width, height, scale, flags, info);
}
CHECK_X11
NETWinInfo newInfo(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMIcon, NET::WM2WindowClass | NET::WM2IconPixmap);
return iconFromNetWinInfo(width, height, scale, flags, &newInfo);
}
// enum values for ICCCM 4.1.2.4 and 4.1.4, defined to not depend on xcb-icccm
enum {
_ICCCM_WM_STATE_WITHDRAWN = 0,
_ICCCM_WM_STATE_NORMAL = 1,
_ICCCM_WM_STATE_ICONIC = 3,
};
void KX11Extras::minimizeWindow(WId win)
{
CHECK_X11_VOID
create_atoms();
// as described in ICCCM 4.1.4
KXcbEvent<xcb_client_message_event_t> ev;
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = win;
ev.type = _wm_change_state;
ev.format = 32;
ev.data.data32[0] = _ICCCM_WM_STATE_ICONIC;
ev.data.data32[1] = 0;
ev.data.data32[2] = 0;
ev.data.data32[3] = 0;
ev.data.data32[4] = 0;
xcb_send_event(QX11Info::connection(),
false,
QX11Info::appRootWindow(),
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
ev.buffer());
}
void KX11Extras::unminimizeWindow(WId win)
{
CHECK_X11_VOID
xcb_map_window(QX11Info::connection(), win);
}
QRect KX11Extras::workArea(int desktop)
{
CHECK_X11
KX11Extras::self()->init(INFO_BASIC);
int desk = (desktop > 0 && desktop <= (int)KX11Extras::self()->s_d_func()->numberOfDesktops()) ? desktop : currentDesktop();
if (desk <= 0) {
return displayGeometry() / qApp->devicePixelRatio();
}
NETRect r = KX11Extras::self()->s_d_func()->workArea(desk);
if (r.size.width <= 0 || r.size.height <= 0) { // not set
return displayGeometry() / qApp->devicePixelRatio();
}
return QRect(r.pos.x, r.pos.y, r.size.width, r.size.height) / qApp->devicePixelRatio();
}
QRect KX11Extras::workArea(const QList<WId> &exclude, int desktop)
{
CHECK_X11
KX11Extras::self()->init(INFO_WINDOWS); // invalidates s_d_func's return value
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
QRect all = displayGeometry();
QRect a = all;
if (desktop == -1) {
desktop = s_d->currentDesktop();
}
QList<WId>::ConstIterator it1;
for (it1 = s_d->windows.constBegin(); it1 != s_d->windows.constEnd(); ++it1) {
if (exclude.contains(*it1)) {
continue;
}
// Kicker (very) extensively calls this function, causing hundreds of roundtrips just
// to repeatedly find out struts of all windows. Therefore strut values for strut
// windows are cached here.
NETStrut strut;
auto it2 = s_d->strutWindows.begin();
for (; it2 != s_d->strutWindows.end(); ++it2) {
if ((*it2).window == *it1) {
break;
}
}
if (it2 != s_d->strutWindows.end()) {
if (!((*it2).desktop == desktop || (*it2).desktop == NETWinInfo::OnAllDesktops)) {
continue;
}
strut = (*it2).strut;
} else if (s_d->possibleStrutWindows.contains(*it1)) {
NETWinInfo info(QX11Info::connection(), (*it1), QX11Info::appRootWindow(), NET::WMStrut | NET::WMDesktop, NET::Properties2());
strut = info.strut();
s_d->possibleStrutWindows.removeAll(*it1);
s_d->strutWindows.append(NETEventFilter::StrutData(*it1, info.strut(), info.desktop()));
if (!(info.desktop() == desktop || info.desktop() == NETWinInfo::OnAllDesktops)) {
continue;
}
} else {
continue; // not a strut window
}
QRect r = all;
if (strut.left > 0) {
r.setLeft(r.left() + (int)strut.left);
}
if (strut.top > 0) {
r.setTop(r.top() + (int)strut.top);
}
if (strut.right > 0) {
r.setRight(r.right() - (int)strut.right);
}
if (strut.bottom > 0) {
r.setBottom(r.bottom() - (int)strut.bottom);
}
a = a.intersected(r);
}
return a / qApp->devicePixelRatio();
}
QString KX11Extras::desktopName(int desktop)
{
CHECK_X11
KX11Extras::self()->init(INFO_BASIC);
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
bool isDesktopSane = (desktop > 0 && desktop <= (int)s_d->numberOfDesktops());
const char *name = s_d->desktopName(isDesktopSane ? desktop : currentDesktop());
if (name && name[0]) {
return QString::fromUtf8(name);
}
return KWindowSystem::tr("Desktop %1").arg(desktop);
}
void KX11Extras::setDesktopName(int desktop, const QString &name)
{
CHECK_X11_VOID
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
if (desktop <= 0 || desktop > (int)numberOfDesktops()) {
desktop = currentDesktop();
}
if (s_d) {
s_d->setDesktopName(desktop, name.toUtf8().constData());
return;
}
NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen());
info.setDesktopName(desktop, name.toUtf8().constData());
}
QString KX11Extras::readNameProperty(WId win, unsigned long atom)
{
CHECK_X11
XTextProperty tp;
char **text = nullptr;
int count;
QString result;
if (XGetTextProperty(QX11Info::display(), win, &tp, atom) != 0 && tp.value != nullptr) {
create_atoms();
if (tp.encoding == kwm_utf8_string) {
result = QString::fromUtf8((const char *)tp.value);
} else if (XmbTextPropertyToTextList(QX11Info::display(), &tp, &text, &count) == Success && text != nullptr && count > 0) {
result = QString::fromLocal8Bit(text[0]);
} else if (tp.encoding == XA_STRING) {
result = QString::fromLocal8Bit((const char *)tp.value);
}
if (text != nullptr) {
XFreeStringList(text);
}
XFree(tp.value);
}
return result;
}
bool KX11Extras::mapViewport()
{
CHECK_X11
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
if (s_d) {
return s_d->mapViewport();
}
// Handle case of not having a QGuiApplication
if (!QX11Info::connection()) {
return false;
}
// avoid creating KWindowSystemPrivate
NETRootInfo infos(QX11Info::connection(), NET::Supported, NET::Properties2(), QX11Info::appScreen());
if (!infos.isSupported(NET::DesktopViewport)) {
return false;
}
NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::CurrentDesktop | NET::DesktopGeometry, NET::Properties2(), QX11Info::appScreen());
if (info.numberOfDesktops(true) <= 1 && (info.desktopGeometry().width > displayWidth() || info.desktopGeometry().height > displayHeight())) {
return true;
}
return false;
}
int KX11Extras::viewportWindowToDesktop(const QRect &rect)
{
CHECK_X11
const QRect r = rect / qApp->devicePixelRatio();
KX11Extras::self()->init(INFO_BASIC);
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
QPoint p = r.center();
// make absolute
p = QPoint(p.x() + s_d->desktopViewport(s_d->currentDesktop(true)).x, p.y() + s_d->desktopViewport(s_d->currentDesktop(true)).y);
NETSize s = s_d->desktopGeometry();
QSize vs(displayWidth(), displayHeight());
int xs = s.width / vs.width();
int x = p.x() < 0 ? 0 : p.x() >= s.width ? xs - 1 : p.x() / vs.width();
int ys = s.height / vs.height();
int y = p.y() < 0 ? 0 : p.y() >= s.height ? ys - 1 : p.y() / vs.height();
return y * xs + x + 1;
}
void KX11Extras::setExtendedStrut(WId win,
qreal left_width,
qreal left_start,
qreal left_end,
qreal right_width,
qreal right_start,
qreal right_end,
qreal top_width,
qreal top_start,
qreal top_end,
qreal bottom_width,
qreal bottom_start,
qreal bottom_end)
{
CHECK_X11_VOID
const qreal dpr = qApp->devicePixelRatio();
NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
NETExtendedStrut strut;
strut.left_width = std::lround(left_width * dpr);
strut.right_width = std::lround(right_width * dpr);
strut.top_width = std::lround(top_width * dpr);
strut.bottom_width = std::lround(bottom_width * dpr);
strut.left_start = std::lround(left_start * dpr);
strut.left_end = std::lround(left_end * dpr);
strut.right_start = std::lround(right_start * dpr);
strut.right_end = std::lround(right_end * dpr);
strut.top_start = std::lround(top_start * dpr);
strut.top_end = std::lround(top_end * dpr);
strut.bottom_start = std::lround(bottom_start * dpr);
strut.bottom_end = std::lround(bottom_end * dpr);
info.setExtendedStrut(strut);
NETStrut oldstrut;
oldstrut.left = std::lround(left_width * dpr);
oldstrut.right = std::lround(right_width * dpr);
oldstrut.top = std::lround(top_width * dpr);
oldstrut.bottom = std::lround(bottom_width * dpr);
info.setStrut(oldstrut);
}
void KX11Extras::setStrut(WId win, qreal left, qreal right, qreal top, qreal bottom)
{
CHECK_X11_VOID
const qreal dpr = qApp->devicePixelRatio();
int w = displayWidth();
int h = displayHeight();
setExtendedStrut(win,
std::lround(left * dpr),
0,
std::lround(left * dpr) != 0 ? w : 0,
std::lround(right * dpr),
0,
std::lround(right * dpr) != 0 ? w : 0,
std::lround(top * dpr),
0,
std::lround(top * dpr) != 0 ? h : 0,
std::lround(bottom * dpr),
0,
std::lround(bottom * dpr) != 0 ? h : 0);
}
// optimalization - create private only when needed and only for what is needed
void KX11Extras::connectNotify(const QMetaMethod &signal)
{
CHECK_X11_VOID
FilterInfo what = INFO_BASIC;
if (signal == QMetaMethod::fromSignal(&KX11Extras::workAreaChanged)) {
what = INFO_WINDOWS;
} else if (signal == QMetaMethod::fromSignal(&KX11Extras::strutChanged)) {
what = INFO_WINDOWS;
} else if (signal == QMetaMethod::fromSignal(&KX11Extras::windowChanged)) {
what = INFO_WINDOWS;
}
init(what);
NETEventFilter *const s_d = s_d_func();
if (!s_d->strutSignalConnected && signal == QMetaMethod::fromSignal(&KX11Extras::strutChanged)) {
s_d->strutSignalConnected = true;
}
QObject::connectNotify(signal);
}
void KX11Extras::setType(WId win, NET::WindowType windowType)
{
CHECK_X11_VOID
NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
info.setWindowType(windowType);
}
void KX11Extras::setState(WId win, NET::States state)
{
CHECK_X11_VOID
NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
info.setState(state, state);
}
void KX11Extras::clearState(WId win, NET::States state)
{
CHECK_X11_VOID
NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
info.setState(NET::States(), state);
}
int KX11Extras::viewportToDesktop(const QPoint &p)
{
CHECK_X11
KX11Extras::self()->init(INFO_BASIC);
NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
NETSize s = s_d->desktopGeometry();
QSize vs(displayWidth(), displayHeight());
int xs = s.width / vs.width();
int x = p.x() < 0 ? 0 : p.x() >= s.width ? xs - 1 : p.x() / vs.width();
int ys = s.height / vs.height();
int y = p.y() < 0 ? 0 : p.y() >= s.height ? ys - 1 : p.y() / vs.height();
return y * xs + x + 1;
}
QPoint KX11Extras::constrainViewportRelativePosition(const QPoint &pos)
{
CHECK_X11
init(INFO_BASIC);
NETEventFilter *const s_d = s_d_func();
NETSize s = s_d->desktopGeometry();
NETPoint c = s_d->desktopViewport(s_d->currentDesktop(true));
int x = (pos.x() + c.x) % s.width;
int y = (pos.y() + c.y) % s.height;
if (x < 0) {
x += s.width;
}
if (y < 0) {
y += s.height;
}
return QPoint(x - c.x, y - c.y);
}
QPoint KX11Extras::desktopToViewport(int desktop, bool absolute)
{
CHECK_X11
init(INFO_BASIC);
NETEventFilter *const s_d = s_d_func();
NETSize s = s_d->desktopGeometry();
QSize vs(displayWidth(), displayHeight());
int xs = s.width / vs.width();
int ys = s.height / vs.height();
if (desktop <= 0 || desktop > xs * ys) {
return QPoint(0, 0);
}
--desktop;
QPoint ret(vs.width() * (desktop % xs), vs.height() * (desktop / xs));
if (!absolute) {
ret = QPoint(ret.x() - s_d->desktopViewport(s_d->currentDesktop(true)).x, ret.y() - s_d->desktopViewport(s_d->currentDesktop(true)).y);
if (ret.x() >= s.width) {
ret.setX(ret.x() - s.width);
}
if (ret.x() < 0) {
ret.setX(ret.x() + s.width);
}
if (ret.y() >= s.height) {
ret.setY(ret.y() - s.height);
}
if (ret.y() < 0) {
ret.setY(ret.y() + s.height);
}
}
return ret;
}
bool KX11Extras::showingDesktop()
{
KX11Extras::self()->init(INFO_BASIC);
return KX11Extras::self()->s_d_func()->showingDesktop();
}
void KX11Extras::setShowingDesktop(bool showing)
{
NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::WM2ShowingDesktop, QX11Info::appScreen());
info.setShowingDesktop(showing);
}
#include "kx11extras.moc"
#include "moc_kx11extras.cpp"