1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999 Matthias Ettrich <ettrich@kde.org>
4 SPDX-FileCopyrightText: 2007 Lubos Lunak <l.lunak@kde.org>
5 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.1-or-later
8*/
9
10#include "kx11extras.h"
11
12// clang-format off
13#include <kxerrorhandler_p.h>
14#include <fixx11h.h>
15#include <kxutils_p.h>
16// clang-format on
17
18#include "cptr_p.h"
19#include "kwindowsystem.h"
20#include "kwindowsystem_debug.h"
21#include "netwm.h"
22#include "kxcbevent_p.h"
23
24#include <QAbstractNativeEventFilter>
25#include <QGuiApplication>
26#include <QMetaMethod>
27#include <QRect>
28#include <QScreen>
29#include <private/qtx11extras_p.h>
30
31#include <X11/Xatom.h>
32#include <X11/Xutil.h>
33#include <X11/extensions/Xfixes.h>
34#include <xcb/xcb.h>
35#include <xcb/xfixes.h>
36
37// QPoint and QSize all have handy / operators which are useful for scaling, positions and sizes for high DPI support
38// QRect does not, so we create one for internal purposes within this class
39inline QRect operator/(const QRect &rectangle, qreal factor)
40{
41 return QRect(rectangle.topLeft() / factor, rectangle.size() / factor);
42}
43
44class MainThreadInstantiator : public QObject
45{
46 Q_OBJECT
47
48public:
49 MainThreadInstantiator(KX11Extras::FilterInfo _what);
50 Q_INVOKABLE NETEventFilter *createNETEventFilter();
51
52private:
53 KX11Extras::FilterInfo m_what;
54};
55
56class NETEventFilter : public NETRootInfo, public QAbstractNativeEventFilter
57{
58public:
59 NETEventFilter(KX11Extras::FilterInfo _what);
60 ~NETEventFilter() override;
61 void activate();
62 QList<WId> windows;
63 QList<WId> stackingOrder;
64
65 struct StrutData {
66 StrutData(WId window_, const NETStrut &strut_, int desktop_)
67 : window(window_)
68 , strut(strut_)
69 , desktop(desktop_)
70 {
71 }
72 WId window;
73 NETStrut strut;
74 int desktop;
75 };
76 QList<StrutData> strutWindows;
77 QList<WId> possibleStrutWindows;
78 bool strutSignalConnected;
79 bool compositingEnabled;
80 bool haveXfixes;
81 KX11Extras::FilterInfo what;
82 int xfixesEventBase;
83 bool mapViewport();
84
85 bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override;
86
87 void updateStackingOrder();
88 bool removeStrutWindow(WId);
89
90protected:
91 void addClient(xcb_window_t) override;
92 void removeClient(xcb_window_t) override;
93
94private:
95 bool nativeEventFilter(xcb_generic_event_t *event);
96 xcb_window_t winId;
97 xcb_window_t m_appRootWindow;
98};
99
100static Atom net_wm_cm;
101static void create_atoms();
102
103static inline const QRect &displayGeometry()
104{
105 static QRect displayGeometry;
106 static bool isDirty = true;
107
108 if (isDirty) {
109 static QList<QMetaObject::Connection> connections;
110 auto dirtify = [&] {
111 isDirty = true;
112 for (const QMetaObject::Connection &con : std::as_const(t&: connections)) {
113 QObject::disconnect(con);
114 }
115 connections.clear();
116 };
117
118 QObject::connect(qApp, signal: &QGuiApplication::screenAdded, slot&: dirtify);
119 QObject::connect(qApp, signal: &QGuiApplication::screenRemoved, slot&: dirtify);
120 const QList<QScreen *> screenList = QGuiApplication::screens();
121 QRegion region;
122 for (int i = 0; i < screenList.count(); ++i) {
123 const QScreen *screen = screenList.at(i);
124 connections << QObject::connect(sender: screen, signal: &QScreen::geometryChanged, slot&: dirtify);
125 const QRect geometry = screen->geometry();
126 const qreal dpr = screen->devicePixelRatio();
127 region += QRect(geometry.topLeft(), geometry.size() * dpr);
128 }
129 displayGeometry = region.boundingRect();
130 isDirty = false;
131 }
132
133 return displayGeometry;
134}
135
136static inline int displayWidth()
137{
138 return displayGeometry().width();
139}
140
141static inline int displayHeight()
142{
143 return displayGeometry().height();
144}
145
146// clang-format off
147static const NET::Properties windowsProperties = NET::ClientList | NET::ClientListStacking |
148 NET::Supported |
149 NET::NumberOfDesktops |
150 NET::DesktopGeometry |
151 NET::DesktopViewport |
152 NET::CurrentDesktop |
153 NET::DesktopNames |
154 NET::ActiveWindow |
155 NET::WorkArea;
156static const NET::Properties2 windowsProperties2 = NET::WM2ShowingDesktop;
157
158// ClientList and ClientListStacking is not per-window information, but a desktop information,
159// so track it even with only INFO_BASIC
160static const NET::Properties desktopProperties = NET::ClientList | NET::ClientListStacking |
161 NET::Supported |
162 NET::NumberOfDesktops |
163 NET::DesktopGeometry |
164 NET::DesktopViewport |
165 NET::CurrentDesktop |
166 NET::DesktopNames |
167 NET::ActiveWindow |
168 NET::WorkArea;
169static const NET::Properties2 desktopProperties2 = NET::WM2ShowingDesktop;
170// clang-format on
171
172MainThreadInstantiator::MainThreadInstantiator(KX11Extras::FilterInfo _what)
173 : QObject()
174 , m_what(_what)
175{
176}
177
178NETEventFilter *MainThreadInstantiator::createNETEventFilter()
179{
180 return new NETEventFilter(m_what);
181}
182
183NETEventFilter::NETEventFilter(KX11Extras::FilterInfo _what)
184 : NETRootInfo(QX11Info::connection(),
185 _what >= KX11Extras::INFO_WINDOWS ? windowsProperties : desktopProperties,
186 _what >= KX11Extras::INFO_WINDOWS ? windowsProperties2 : desktopProperties2,
187 QX11Info::appScreen(),
188 false)
189 , QAbstractNativeEventFilter()
190 , strutSignalConnected(false)
191 , compositingEnabled(false)
192 , haveXfixes(false)
193 , what(_what)
194 , winId(XCB_WINDOW_NONE)
195 , m_appRootWindow(QX11Info::appRootWindow())
196{
197 QCoreApplication::instance()->installNativeEventFilter(filterObj: this);
198
199 int errorBase;
200 if ((haveXfixes = XFixesQueryExtension(dpy: QX11Info::display(), event_base_return: &xfixesEventBase, error_base_return: &errorBase))) {
201 create_atoms();
202 winId = xcb_generate_id(c: QX11Info::connection());
203 uint32_t values[] = {true, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
204 xcb_create_window(c: QX11Info::connection(),
205 XCB_COPY_FROM_PARENT,
206 wid: winId,
207 parent: m_appRootWindow,
208 x: 0,
209 y: 0,
210 width: 1,
211 height: 1,
212 border_width: 0,
213 class: XCB_WINDOW_CLASS_INPUT_ONLY,
214 XCB_COPY_FROM_PARENT,
215 value_mask: XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
216 value_list: values);
217 XFixesSelectSelectionInput(dpy: QX11Info::display(),
218 win: winId,
219 selection: net_wm_cm,
220 XFixesSetSelectionOwnerNotifyMask | XFixesSelectionWindowDestroyNotifyMask | XFixesSelectionClientCloseNotifyMask);
221 compositingEnabled = XGetSelectionOwner(QX11Info::display(), net_wm_cm) != None;
222 }
223}
224
225NETEventFilter::~NETEventFilter()
226{
227 if (QX11Info::connection() && winId != XCB_WINDOW_NONE) {
228 xcb_destroy_window(c: QX11Info::connection(), window: winId);
229 winId = XCB_WINDOW_NONE;
230 }
231}
232
233// not virtual, but it's called directly only from init()
234void NETEventFilter::activate()
235{
236 NETRootInfo::activate();
237 updateStackingOrder();
238}
239
240bool NETEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *)
241{
242 if (eventType != "xcb_generic_event_t") {
243 // only interested in XCB events of course
244 return false;
245 }
246 return nativeEventFilter(event: reinterpret_cast<xcb_generic_event_t *>(message));
247}
248
249bool NETEventFilter::nativeEventFilter(xcb_generic_event_t *ev)
250{
251 KWindowSystem *s_q = KWindowSystem::self();
252 const uint8_t eventType = ev->response_type & ~0x80;
253
254 if (eventType == xfixesEventBase + XCB_XFIXES_SELECTION_NOTIFY) {
255 xcb_xfixes_selection_notify_event_t *event = reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(ev);
256 if (event->window == winId) {
257 bool haveOwner = event->owner != XCB_WINDOW_NONE;
258 if (compositingEnabled != haveOwner) {
259 compositingEnabled = haveOwner;
260 Q_EMIT KX11Extras::self()->compositingChanged(enabled: compositingEnabled);
261 }
262 return true;
263 }
264 // Qt compresses XFixesSelectionNotifyEvents without caring about the actual window
265 // gui/kernel/qapplication_x11.cpp
266 // until that can be assumed fixed, we also react on events on the root (caused by Qts own compositing tracker)
267 if (event->window == m_appRootWindow) {
268 if (event->selection == net_wm_cm) {
269 bool haveOwner = event->owner != XCB_WINDOW_NONE;
270 if (compositingEnabled != haveOwner) {
271 compositingEnabled = haveOwner;
272 Q_EMIT KX11Extras::self()->compositingChanged(enabled: compositingEnabled);
273 }
274 // NOTICE this is not our event, we just randomly captured it from Qt -> pass on
275 return false;
276 }
277 }
278 return false;
279 }
280
281 xcb_window_t eventWindow = XCB_WINDOW_NONE;
282 switch (eventType) {
283 case XCB_CLIENT_MESSAGE:
284 eventWindow = reinterpret_cast<xcb_client_message_event_t *>(ev)->window;
285 break;
286 case XCB_PROPERTY_NOTIFY:
287 eventWindow = reinterpret_cast<xcb_property_notify_event_t *>(ev)->window;
288 break;
289 case XCB_CONFIGURE_NOTIFY:
290 eventWindow = reinterpret_cast<xcb_configure_notify_event_t *>(ev)->window;
291 break;
292 }
293
294 if (eventWindow == m_appRootWindow) {
295 int old_current_desktop = currentDesktop();
296 xcb_window_t old_active_window = activeWindow();
297 int old_number_of_desktops = numberOfDesktops();
298 bool old_showing_desktop = showingDesktop();
299 NET::Properties props;
300 NET::Properties2 props2;
301 NETRootInfo::event(event: ev, properties: &props, properties2: &props2);
302
303 if ((props & CurrentDesktop) && currentDesktop() != old_current_desktop) {
304 Q_EMIT KX11Extras::self()->currentDesktopChanged(desktop: currentDesktop());
305 }
306 if ((props & DesktopViewport) && mapViewport() && currentDesktop() != old_current_desktop) {
307 Q_EMIT KX11Extras::self()->currentDesktopChanged(desktop: currentDesktop());
308 }
309 if ((props & ActiveWindow) && activeWindow() != old_active_window) {
310 Q_EMIT KX11Extras::self()->activeWindowChanged(id: activeWindow());
311 }
312 if (props & DesktopNames) {
313 Q_EMIT KX11Extras::self()->desktopNamesChanged();
314 }
315 if ((props & NumberOfDesktops) && numberOfDesktops() != old_number_of_desktops) {
316 Q_EMIT KX11Extras::self()->numberOfDesktopsChanged(num: numberOfDesktops());
317 }
318 if ((props & DesktopGeometry) && mapViewport() && numberOfDesktops() != old_number_of_desktops) {
319 Q_EMIT KX11Extras::self()->numberOfDesktopsChanged(num: numberOfDesktops());
320 }
321 if (props & WorkArea) {
322 Q_EMIT KX11Extras::self()->workAreaChanged();
323 }
324 if (props & ClientListStacking) {
325 updateStackingOrder();
326 Q_EMIT KX11Extras::self()->stackingOrderChanged();
327 }
328 if ((props2 & WM2ShowingDesktop) && showingDesktop() != old_showing_desktop) {
329 Q_EMIT s_q->showingDesktopChanged(showing: showingDesktop());
330 }
331 } else if (windows.contains(t: eventWindow)) {
332 NETWinInfo ni(QX11Info::connection(), eventWindow, m_appRootWindow, NET::Properties(), NET::Properties2());
333 NET::Properties dirtyProperties;
334 NET::Properties2 dirtyProperties2;
335 ni.event(event: ev, properties: &dirtyProperties, properties2: &dirtyProperties2);
336 if (eventType == XCB_PROPERTY_NOTIFY) {
337 xcb_property_notify_event_t *event = reinterpret_cast<xcb_property_notify_event_t *>(ev);
338 if (event->atom == XCB_ATOM_WM_HINTS) {
339 dirtyProperties |= NET::WMIcon; // support for old icons
340 } else if (event->atom == XCB_ATOM_WM_NAME) {
341 dirtyProperties |= NET::WMName; // support for old name
342 } else if (event->atom == XCB_ATOM_WM_ICON_NAME) {
343 dirtyProperties |= NET::WMIconName; // support for old iconic name
344 }
345 }
346 if (mapViewport() && (dirtyProperties & (NET::WMState | NET::WMGeometry))) {
347 /* geometry change -> possible viewport change
348 * state change -> possible NET::Sticky change
349 */
350 dirtyProperties |= NET::WMDesktop;
351 }
352 if ((dirtyProperties & NET::WMStrut) != 0) {
353 removeStrutWindow(eventWindow);
354 if (!possibleStrutWindows.contains(t: eventWindow)) {
355 possibleStrutWindows.append(t: eventWindow);
356 }
357 }
358 if (dirtyProperties || dirtyProperties2) {
359 Q_EMIT KX11Extras::self()->windowChanged(id: eventWindow, properties: dirtyProperties, properties2: dirtyProperties2);
360
361 if ((dirtyProperties & NET::WMStrut) != 0) {
362 Q_EMIT KX11Extras::self()->strutChanged();
363 }
364 }
365 }
366
367 return false;
368}
369
370bool NETEventFilter::removeStrutWindow(WId w)
371{
372 for (QList<StrutData>::Iterator it = strutWindows.begin(); it != strutWindows.end(); ++it) {
373 if ((*it).window == w) {
374 strutWindows.erase(pos: it);
375 return true;
376 }
377 }
378 return false;
379}
380
381void NETEventFilter::updateStackingOrder()
382{
383 stackingOrder.clear();
384 for (int i = 0; i < clientListStackingCount(); i++) {
385 stackingOrder.append(t: clientListStacking()[i]);
386 }
387}
388
389void NETEventFilter::addClient(xcb_window_t w)
390{
391 if ((what >= KX11Extras::INFO_WINDOWS)) {
392 xcb_connection_t *c = QX11Info::connection();
393 UniqueCPointer<xcb_get_window_attributes_reply_t> attr(xcb_get_window_attributes_reply(c, cookie: xcb_get_window_attributes_unchecked(c, window: w), e: nullptr));
394
395 uint32_t events = XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY;
396 if (attr) {
397 events = events | attr->your_event_mask;
398 }
399 xcb_change_window_attributes(c, window: w, value_mask: XCB_CW_EVENT_MASK, value_list: &events);
400 }
401
402 bool emit_strutChanged = false;
403
404 if (strutSignalConnected) {
405 NETWinInfo info(QX11Info::connection(), w, QX11Info::appRootWindow(), NET::WMStrut | NET::WMDesktop, NET::Properties2());
406 NETStrut strut = info.strut();
407 if (strut.left || strut.top || strut.right || strut.bottom) {
408 strutWindows.append(t: StrutData(w, strut, info.desktop()));
409 emit_strutChanged = true;
410 }
411 } else {
412 possibleStrutWindows.append(t: w);
413 }
414
415 windows.append(t: w);
416 Q_EMIT KX11Extras::self()->windowAdded(id: w);
417 if (emit_strutChanged) {
418 Q_EMIT KX11Extras::self()->strutChanged();
419 }
420}
421
422void NETEventFilter::removeClient(xcb_window_t w)
423{
424 bool emit_strutChanged = removeStrutWindow(w);
425 if (strutSignalConnected && possibleStrutWindows.contains(t: w)) {
426 NETWinInfo info(QX11Info::connection(), w, QX11Info::appRootWindow(), NET::WMStrut, NET::Properties2());
427 NETStrut strut = info.strut();
428 if (strut.left || strut.top || strut.right || strut.bottom) {
429 emit_strutChanged = true;
430 }
431 }
432
433 possibleStrutWindows.removeAll(t: w);
434 windows.removeAll(t: w);
435 Q_EMIT KX11Extras::self()->windowRemoved(id: w);
436 if (emit_strutChanged) {
437 Q_EMIT KX11Extras::self()->strutChanged();
438 }
439}
440
441bool NETEventFilter::mapViewport()
442{
443 // compiz claims support even though it doesn't use virtual desktops :(
444 // if( isSupported( NET::DesktopViewport ) && !isSupported( NET::NumberOfDesktops ))
445
446 // this test is duplicated in KWindowSystem::mapViewport()
447 if (isSupported(property: NET::DesktopViewport) && numberOfDesktops(ignore_viewport: true) <= 1
448 && (desktopGeometry().width > displayWidth() || desktopGeometry().height > displayHeight())) {
449 return true;
450 }
451 return false;
452}
453
454static bool atoms_created = false;
455
456static Atom _wm_protocols;
457static Atom _wm_change_state;
458static Atom kwm_utf8_string;
459
460static void create_atoms()
461{
462 if (!atoms_created) {
463 const int max = 20;
464 Atom *atoms[max];
465 const char *names[max];
466 Atom atoms_return[max];
467 int n = 0;
468
469 atoms[n] = &_wm_protocols;
470 names[n++] = "WM_PROTOCOLS";
471
472 atoms[n] = &_wm_change_state;
473 names[n++] = "WM_CHANGE_STATE";
474
475 atoms[n] = &kwm_utf8_string;
476 names[n++] = "UTF8_STRING";
477
478 char net_wm_cm_name[100];
479 sprintf(s: net_wm_cm_name, format: "_NET_WM_CM_S%d", QX11Info::appScreen());
480 atoms[n] = &net_wm_cm;
481 names[n++] = net_wm_cm_name;
482
483 // we need a const_cast for the shitty X API
484 XInternAtoms(QX11Info::display(), const_cast<char **>(names), n, false, atoms_return);
485 for (int i = 0; i < n; i++) {
486 *atoms[i] = atoms_return[i];
487 }
488
489 atoms_created = True;
490 }
491}
492
493#define CHECK_X11 \
494 if (!KWindowSystem::isPlatformX11()) { \
495 qCWarning(LOG_KWINDOWSYSTEM) << Q_FUNC_INFO << "may only be used on X11"; \
496 return {}; \
497 }
498
499#define CHECK_X11_VOID \
500 if (!KWindowSystem::isPlatformX11()) { \
501 qCWarning(LOG_KWINDOWSYSTEM) << Q_FUNC_INFO << "may only be used on X11"; \
502 return; \
503 }
504
505// WARNING
506// you have to call s_d_func() again after calling this function if you want a valid pointer!
507void KX11Extras::init(FilterInfo what)
508{
509 NETEventFilter *const s_d = s_d_func();
510
511 if (what >= INFO_WINDOWS) {
512 what = INFO_WINDOWS;
513 } else {
514 what = INFO_BASIC;
515 }
516
517 if (!s_d || s_d->what < what) {
518 const bool wasCompositing = s_d ? s_d->compositingEnabled : false;
519 MainThreadInstantiator instantiator(what);
520 NETEventFilter *filter;
521 if (instantiator.thread() == QCoreApplication::instance()->thread()) {
522 filter = instantiator.createNETEventFilter();
523 } else {
524 // the instantiator is not in the main app thread, which implies
525 // we are being called in a thread that is not the main app thread
526 // so we move the instantiator to the main app thread and invoke
527 // the method with a blocking call
528 instantiator.moveToThread(thread: QCoreApplication::instance()->thread());
529 QMetaObject::invokeMethod(obj: &instantiator, member: "createNETEventFilter", c: Qt::BlockingQueuedConnection, Q_RETURN_ARG(NETEventFilter *, filter));
530 }
531 d.reset(p: filter);
532 d->activate();
533 if (wasCompositing != s_d_func()->compositingEnabled) {
534 Q_EMIT KX11Extras::self()->compositingChanged(enabled: s_d_func()->compositingEnabled);
535 }
536 }
537}
538
539KX11Extras *KX11Extras::self()
540{
541 static KX11Extras instance;
542 return &instance;
543}
544
545QList<WId> KX11Extras::windows()
546{
547 CHECK_X11
548 KX11Extras::self()->init(what: INFO_BASIC);
549 return KX11Extras::self()->s_d_func()->windows;
550}
551
552bool KX11Extras::hasWId(WId w)
553{
554 CHECK_X11
555 return windows().contains(t: w);
556}
557
558QList<WId> KX11Extras::stackingOrder()
559{
560 CHECK_X11
561 KX11Extras::self()->init(what: INFO_BASIC);
562 return KX11Extras::self()->s_d_func()->stackingOrder;
563}
564
565WId KX11Extras::activeWindow()
566{
567 CHECK_X11
568 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
569 if (s_d) {
570 return s_d->activeWindow();
571 }
572 NETRootInfo info(QX11Info::connection(), NET::ActiveWindow, NET::Properties2(), QX11Info::appScreen());
573 return info.activeWindow();
574}
575
576void KX11Extras::activateWindow(WId win, long time)
577{
578 CHECK_X11_VOID
579 NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen());
580 if (time == 0) {
581 time = QX11Info::appUserTime();
582 }
583 info.setActiveWindow(window: win, src: NET::FromApplication, timestamp: time, active_window: QGuiApplication::focusWindow() ? QGuiApplication::focusWindow()->winId() : 0);
584}
585
586void KX11Extras::forceActiveWindow(WId win, long time)
587{
588 CHECK_X11_VOID
589 NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen());
590 if (time == 0) {
591 time = QX11Info::appTime();
592 }
593 info.setActiveWindow(window: win, src: NET::FromTool, timestamp: time, active_window: 0);
594}
595
596void KX11Extras::forceActiveWindow(QWindow *win, long time)
597{
598 CHECK_X11_VOID
599 forceActiveWindow(win: win->winId(), time);
600}
601
602bool KX11Extras::compositingActive()
603{
604 CHECK_X11
605 KX11Extras::self()->init(what: INFO_BASIC);
606 if (KX11Extras::self()->s_d_func()->haveXfixes) {
607 return KX11Extras::self()->s_d_func()->compositingEnabled;
608 } else {
609 create_atoms();
610 return XGetSelectionOwner(QX11Info::display(), net_wm_cm);
611 }
612}
613
614int KX11Extras::currentDesktop()
615{
616 CHECK_X11
617 if (!QX11Info::connection()) {
618 return 1;
619 }
620
621 if (mapViewport()) {
622 KX11Extras::self()->init(what: INFO_BASIC);
623 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
624 NETPoint p = s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true));
625 return KX11Extras::self()->viewportToDesktop(pos: QPoint(p.x, p.y) / qApp->devicePixelRatio());
626 }
627
628 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
629 if (s_d) {
630 return s_d->currentDesktop(ignore_viewport: true);
631 }
632 NETRootInfo info(QX11Info::connection(), NET::CurrentDesktop, NET::Properties2(), QX11Info::appScreen());
633 return info.currentDesktop(ignore_viewport: true);
634}
635
636int KX11Extras::numberOfDesktops()
637{
638 CHECK_X11
639 if (!QX11Info::connection()) {
640 return 1;
641 }
642
643 if (mapViewport()) {
644 KX11Extras::self()->init(what: INFO_BASIC);
645 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
646 NETSize s = s_d->desktopGeometry();
647 return s.width / displayWidth() * s.height / displayHeight();
648 }
649
650 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
651 if (s_d) {
652 return s_d->numberOfDesktops(ignore_viewport: true);
653 }
654 NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops, NET::Properties2(), QX11Info::appScreen());
655 return info.numberOfDesktops(ignore_viewport: true);
656}
657
658void KX11Extras::setCurrentDesktop(int desktop)
659{
660 CHECK_X11_VOID
661 if (mapViewport()) {
662 KX11Extras::self()->init(what: INFO_BASIC);
663 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
664 NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen());
665 QPoint pos = KX11Extras::self()->desktopToViewport(desktop, absolute: true);
666 NETPoint p;
667 p.x = pos.x();
668 p.y = pos.y();
669 info.setDesktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true), viewport: p);
670 return;
671 }
672 NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen());
673 info.setCurrentDesktop(desktop, ignore_viewport: true);
674}
675
676void KX11Extras::setOnAllDesktops(WId win, bool b)
677{
678 CHECK_X11_VOID
679 if (mapViewport()) {
680 if (b) {
681 setState(win, state: NET::Sticky);
682 } else {
683 clearState(win, state: NET::Sticky);
684 }
685 return;
686 }
687 NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMDesktop, NET::Properties2());
688 if (b) {
689 info.setDesktop(desktop: NETWinInfo::OnAllDesktops, ignore_viewport: true);
690 } else if (info.desktop(ignore_viewport: true) == NETWinInfo::OnAllDesktops) {
691 NETRootInfo rinfo(QX11Info::connection(), NET::CurrentDesktop, NET::Properties2(), QX11Info::appScreen());
692 info.setDesktop(desktop: rinfo.currentDesktop(ignore_viewport: true), ignore_viewport: true);
693 }
694}
695
696void KX11Extras::setOnDesktop(WId win, int desktop)
697{
698 CHECK_X11_VOID
699 if (mapViewport()) {
700 if (desktop == NET::OnAllDesktops) {
701 return setOnAllDesktops(win, b: true);
702 } else {
703 clearState(win, state: NET::Sticky);
704 }
705 KX11Extras::self()->init(what: INFO_BASIC);
706 QPoint p = KX11Extras::self()->desktopToViewport(desktop, absolute: false);
707 Window dummy;
708 int x;
709 int y;
710 unsigned int w;
711 unsigned int h;
712 unsigned int b;
713 unsigned int dp;
714 XGetGeometry(QX11Info::display(), win, &dummy, &x, &y, &w, &h, &b, &dp);
715 // get global position
716 XTranslateCoordinates(QX11Info::display(), win, QX11Info::appRootWindow(), 0, 0, &x, &y, &dummy);
717 x += w / 2; // center
718 y += h / 2;
719 // transform to coordinates on the current "desktop"
720 x = x % displayWidth();
721 y = y % displayHeight();
722 if (x < 0) {
723 x = x + displayWidth();
724 }
725 if (y < 0) {
726 y = y + displayHeight();
727 }
728 x += p.x(); // move to given "desktop"
729 y += p.y();
730 x -= w / 2; // from center back to topleft
731 y -= h / 2;
732 p = KX11Extras::self()->constrainViewportRelativePosition(pos: QPoint(x, y));
733 int flags = (NET::FromTool << 12) | (0x03 << 8) | 10; // from tool(?), x/y, static gravity
734 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
735 s_d->moveResizeWindowRequest(window: win, flags, x: p.x(), y: p.y(), width: w, height: h);
736 return;
737 }
738 NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMDesktop, NET::Properties2());
739 info.setDesktop(desktop, ignore_viewport: true);
740}
741
742void KX11Extras::setOnActivities(WId win, const QStringList &activities)
743{
744 CHECK_X11_VOID
745 NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::WM2Activities);
746 info.setActivities(activities.join(sep: QLatin1Char(',')).toLatin1().constData());
747}
748
749QPixmap KX11Extras::icon(WId win, int width, int height, bool scale)
750{
751 CHECK_X11
752 return icon(win, width, height, scale, flags: NETWM | WMHints | ClassHint | XApp);
753}
754
755QPixmap iconFromNetWinInfo(int width, int height, bool scale, int flags, NETWinInfo *info)
756{
757 QPixmap result;
758 if (!info) {
759 return result;
760 }
761 if (flags & KX11Extras::NETWM) {
762 NETIcon ni = info->icon(width, height);
763 if (ni.data && ni.size.width > 0 && ni.size.height > 0) {
764 QImage img((uchar *)ni.data, (int)ni.size.width, (int)ni.size.height, QImage::Format_ARGB32);
765 if (scale && width > 0 && height > 0 && img.size() != QSize(width, height) && !img.isNull()) {
766 img = img.scaled(w: width, h: height, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation);
767 }
768 if (!img.isNull()) {
769 result = QPixmap::fromImage(image: img);
770 }
771 return result;
772 }
773 }
774
775 if (flags & KX11Extras::WMHints) {
776 xcb_pixmap_t p = info->icccmIconPixmap();
777 xcb_pixmap_t p_mask = info->icccmIconPixmapMask();
778
779 if (p != XCB_PIXMAP_NONE) {
780 QPixmap pm = KXUtils::createPixmapFromHandle(c: info->xcbConnection(), pixmap: p, mask: p_mask);
781 if (scale && width > 0 && height > 0 && !pm.isNull() //
782 && (pm.width() != width || pm.height() != height)) {
783 result = QPixmap::fromImage(image: pm.toImage().scaled(w: width, h: height, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation));
784 } else {
785 result = pm;
786 }
787 }
788 }
789
790 // Since width can be any arbitrary size, but the icons cannot,
791 // take the nearest value for best results (ignoring 22 pixel
792 // icons as they don't exist for apps):
793 int iconWidth;
794 if (width < 24) {
795 iconWidth = 16;
796 } else if (width < 40) {
797 iconWidth = 32;
798 } else if (width < 56) {
799 iconWidth = 48;
800 } else if (width < 96) {
801 iconWidth = 64;
802 } else if (width < 192) {
803 iconWidth = 128;
804 } else {
805 iconWidth = 256;
806 }
807
808 if (flags & KX11Extras::ClassHint) {
809 // Try to load the icon from the classhint if the app didn't specify
810 // its own:
811 if (result.isNull()) {
812 const QIcon icon = QIcon::fromTheme(name: QString::fromUtf8(utf8: info->windowClassClass()).toLower());
813 const QPixmap pm = icon.isNull() ? QPixmap() : icon.pixmap(w: iconWidth, h: iconWidth);
814 if (scale && !pm.isNull()) {
815 result = QPixmap::fromImage(image: pm.toImage().scaled(w: width, h: height, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation));
816 } else {
817 result = pm;
818 }
819 }
820 }
821
822 if (flags & KX11Extras::XApp) {
823 // If the icon is still a null pixmap, load the icon for X applications
824 // as a last resort:
825 if (result.isNull()) {
826 const QIcon icon = QIcon::fromTheme(QStringLiteral("xorg"));
827 const QPixmap pm = icon.isNull() ? QPixmap() : icon.pixmap(w: iconWidth, h: iconWidth);
828 if (scale && !pm.isNull()) {
829 result = QPixmap::fromImage(image: pm.toImage().scaled(w: width, h: height, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation));
830 } else {
831 result = pm;
832 }
833 }
834 }
835 return result;
836}
837
838QPixmap KX11Extras::icon(WId win, int width, int height, bool scale, int flags)
839{
840 CHECK_X11
841 NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMIcon, NET::WM2WindowClass | NET::WM2IconPixmap);
842 return iconFromNetWinInfo(width, height, scale, flags, info: &info);
843}
844
845QPixmap KX11Extras::icon(WId win, int width, int height, bool scale, int flags, NETWinInfo *info)
846{
847 // No CHECK_X11 here, kwin_wayland calls this to get the icon for XWayland windows
848 width *= qGuiApp->devicePixelRatio();
849 height *= qGuiApp->devicePixelRatio();
850
851 if (info) {
852 return iconFromNetWinInfo(width, height, scale, flags, info);
853 }
854 CHECK_X11
855
856 NETWinInfo newInfo(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMIcon, NET::WM2WindowClass | NET::WM2IconPixmap);
857
858 return iconFromNetWinInfo(width, height, scale, flags, info: &newInfo);
859}
860
861// enum values for ICCCM 4.1.2.4 and 4.1.4, defined to not depend on xcb-icccm
862enum {
863 _ICCCM_WM_STATE_WITHDRAWN = 0,
864 _ICCCM_WM_STATE_NORMAL = 1,
865 _ICCCM_WM_STATE_ICONIC = 3,
866};
867
868void KX11Extras::minimizeWindow(WId win)
869{
870 CHECK_X11_VOID
871 create_atoms();
872 // as described in ICCCM 4.1.4
873 KXcbEvent<xcb_client_message_event_t> ev;
874 ev.response_type = XCB_CLIENT_MESSAGE;
875 ev.window = win;
876 ev.type = _wm_change_state;
877 ev.format = 32;
878 ev.data.data32[0] = _ICCCM_WM_STATE_ICONIC;
879 ev.data.data32[1] = 0;
880 ev.data.data32[2] = 0;
881 ev.data.data32[3] = 0;
882 ev.data.data32[4] = 0;
883 xcb_send_event(c: QX11Info::connection(),
884 propagate: false,
885 destination: QX11Info::appRootWindow(),
886 event_mask: XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
887 event: ev.buffer());
888}
889
890void KX11Extras::unminimizeWindow(WId win)
891{
892 CHECK_X11_VOID
893 xcb_map_window(c: QX11Info::connection(), window: win);
894}
895
896QRect KX11Extras::workArea(int desktop)
897{
898 CHECK_X11
899 KX11Extras::self()->init(what: INFO_BASIC);
900 int desk = (desktop > 0 && desktop <= (int)KX11Extras::self()->s_d_func()->numberOfDesktops()) ? desktop : currentDesktop();
901 if (desk <= 0) {
902 return displayGeometry() / qApp->devicePixelRatio();
903 }
904
905 NETRect r = KX11Extras::self()->s_d_func()->workArea(desktop: desk);
906 if (r.size.width <= 0 || r.size.height <= 0) { // not set
907 return displayGeometry() / qApp->devicePixelRatio();
908 }
909
910 return QRect(r.pos.x, r.pos.y, r.size.width, r.size.height) / qApp->devicePixelRatio();
911}
912
913QRect KX11Extras::workArea(const QList<WId> &exclude, int desktop)
914{
915 CHECK_X11
916 KX11Extras::self()->init(what: INFO_WINDOWS); // invalidates s_d_func's return value
917 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
918
919 QRect all = displayGeometry();
920 QRect a = all;
921
922 if (desktop == -1) {
923 desktop = s_d->currentDesktop();
924 }
925
926 QList<WId>::ConstIterator it1;
927 for (it1 = s_d->windows.constBegin(); it1 != s_d->windows.constEnd(); ++it1) {
928 if (exclude.contains(t: *it1)) {
929 continue;
930 }
931
932 // Kicker (very) extensively calls this function, causing hundreds of roundtrips just
933 // to repeatedly find out struts of all windows. Therefore strut values for strut
934 // windows are cached here.
935 NETStrut strut;
936 auto it2 = s_d->strutWindows.begin();
937 for (; it2 != s_d->strutWindows.end(); ++it2) {
938 if ((*it2).window == *it1) {
939 break;
940 }
941 }
942
943 if (it2 != s_d->strutWindows.end()) {
944 if (!((*it2).desktop == desktop || (*it2).desktop == NETWinInfo::OnAllDesktops)) {
945 continue;
946 }
947
948 strut = (*it2).strut;
949 } else if (s_d->possibleStrutWindows.contains(t: *it1)) {
950 NETWinInfo info(QX11Info::connection(), (*it1), QX11Info::appRootWindow(), NET::WMStrut | NET::WMDesktop, NET::Properties2());
951 strut = info.strut();
952 s_d->possibleStrutWindows.removeAll(t: *it1);
953 s_d->strutWindows.append(t: NETEventFilter::StrutData(*it1, info.strut(), info.desktop()));
954
955 if (!(info.desktop() == desktop || info.desktop() == NETWinInfo::OnAllDesktops)) {
956 continue;
957 }
958 } else {
959 continue; // not a strut window
960 }
961
962 QRect r = all;
963 if (strut.left > 0) {
964 r.setLeft(r.left() + (int)strut.left);
965 }
966 if (strut.top > 0) {
967 r.setTop(r.top() + (int)strut.top);
968 }
969 if (strut.right > 0) {
970 r.setRight(r.right() - (int)strut.right);
971 }
972 if (strut.bottom > 0) {
973 r.setBottom(r.bottom() - (int)strut.bottom);
974 }
975
976 a = a.intersected(other: r);
977 }
978 return a / qApp->devicePixelRatio();
979}
980
981QString KX11Extras::desktopName(int desktop)
982{
983 CHECK_X11
984 KX11Extras::self()->init(what: INFO_BASIC);
985 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
986
987 bool isDesktopSane = (desktop > 0 && desktop <= (int)s_d->numberOfDesktops());
988 const char *name = s_d->desktopName(desktop: isDesktopSane ? desktop : currentDesktop());
989
990 if (name && name[0]) {
991 return QString::fromUtf8(utf8: name);
992 }
993
994 return KWindowSystem::tr(s: "Desktop %1").arg(a: desktop);
995}
996
997void KX11Extras::setDesktopName(int desktop, const QString &name)
998{
999 CHECK_X11_VOID
1000 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
1001
1002 if (desktop <= 0 || desktop > (int)numberOfDesktops()) {
1003 desktop = currentDesktop();
1004 }
1005
1006 if (s_d) {
1007 s_d->setDesktopName(desktop, desktopName: name.toUtf8().constData());
1008 return;
1009 }
1010
1011 NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen());
1012 info.setDesktopName(desktop, desktopName: name.toUtf8().constData());
1013}
1014
1015QString KX11Extras::readNameProperty(WId win, unsigned long atom)
1016{
1017 CHECK_X11
1018 XTextProperty tp;
1019 char **text = nullptr;
1020 int count;
1021 QString result;
1022 if (XGetTextProperty(QX11Info::display(), win, &tp, atom) != 0 && tp.value != nullptr) {
1023 create_atoms();
1024
1025 if (tp.encoding == kwm_utf8_string) {
1026 result = QString::fromUtf8(utf8: (const char *)tp.value);
1027 } else if (XmbTextPropertyToTextList(display: QX11Info::display(), text_prop: &tp, list_return: &text, count_return: &count) == Success && text != nullptr && count > 0) {
1028 result = QString::fromLocal8Bit(ba: text[0]);
1029 } else if (tp.encoding == XA_STRING) {
1030 result = QString::fromLocal8Bit(ba: (const char *)tp.value);
1031 }
1032 if (text != nullptr) {
1033 XFreeStringList(text);
1034 }
1035 XFree(tp.value);
1036 }
1037 return result;
1038}
1039
1040bool KX11Extras::mapViewport()
1041{
1042 CHECK_X11
1043 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
1044 if (s_d) {
1045 return s_d->mapViewport();
1046 }
1047
1048 // Handle case of not having a QGuiApplication
1049 if (!QX11Info::connection()) {
1050 return false;
1051 }
1052
1053 // avoid creating KWindowSystemPrivate
1054 NETRootInfo infos(QX11Info::connection(), NET::Supported, NET::Properties2(), QX11Info::appScreen());
1055 if (!infos.isSupported(property: NET::DesktopViewport)) {
1056 return false;
1057 }
1058 NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::CurrentDesktop | NET::DesktopGeometry, NET::Properties2(), QX11Info::appScreen());
1059 if (info.numberOfDesktops(ignore_viewport: true) <= 1 && (info.desktopGeometry().width > displayWidth() || info.desktopGeometry().height > displayHeight())) {
1060 return true;
1061 }
1062 return false;
1063}
1064
1065int KX11Extras::viewportWindowToDesktop(const QRect &rect)
1066{
1067 CHECK_X11
1068 const QRect r = rect / qApp->devicePixelRatio();
1069
1070 KX11Extras::self()->init(what: INFO_BASIC);
1071 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
1072 QPoint p = r.center();
1073 // make absolute
1074 p = QPoint(p.x() + s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true)).x, p.y() + s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true)).y);
1075 NETSize s = s_d->desktopGeometry();
1076 QSize vs(displayWidth(), displayHeight());
1077 int xs = s.width / vs.width();
1078 int x = p.x() < 0 ? 0 : p.x() >= s.width ? xs - 1 : p.x() / vs.width();
1079 int ys = s.height / vs.height();
1080 int y = p.y() < 0 ? 0 : p.y() >= s.height ? ys - 1 : p.y() / vs.height();
1081 return y * xs + x + 1;
1082}
1083
1084void KX11Extras::setExtendedStrut(WId win,
1085 qreal left_width,
1086 qreal left_start,
1087 qreal left_end,
1088 qreal right_width,
1089 qreal right_start,
1090 qreal right_end,
1091 qreal top_width,
1092 qreal top_start,
1093 qreal top_end,
1094 qreal bottom_width,
1095 qreal bottom_start,
1096 qreal bottom_end)
1097{
1098 CHECK_X11_VOID
1099 const qreal dpr = qApp->devicePixelRatio();
1100
1101 NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
1102 NETExtendedStrut strut;
1103 strut.left_width = std::lround(x: left_width * dpr);
1104 strut.right_width = std::lround(x: right_width * dpr);
1105 strut.top_width = std::lround(x: top_width * dpr);
1106 strut.bottom_width = std::lround(x: bottom_width * dpr);
1107 strut.left_start = std::lround(x: left_start * dpr);
1108 strut.left_end = std::lround(x: left_end * dpr);
1109 strut.right_start = std::lround(x: right_start * dpr);
1110 strut.right_end = std::lround(x: right_end * dpr);
1111 strut.top_start = std::lround(x: top_start * dpr);
1112 strut.top_end = std::lround(x: top_end * dpr);
1113 strut.bottom_start = std::lround(x: bottom_start * dpr);
1114 strut.bottom_end = std::lround(x: bottom_end * dpr);
1115 info.setExtendedStrut(strut);
1116 NETStrut oldstrut;
1117 oldstrut.left = std::lround(x: left_width * dpr);
1118 oldstrut.right = std::lround(x: right_width * dpr);
1119 oldstrut.top = std::lround(x: top_width * dpr);
1120 oldstrut.bottom = std::lround(x: bottom_width * dpr);
1121 info.setStrut(oldstrut);
1122}
1123
1124void KX11Extras::setStrut(WId win, qreal left, qreal right, qreal top, qreal bottom)
1125{
1126 CHECK_X11_VOID
1127 const qreal dpr = qApp->devicePixelRatio();
1128
1129 int w = displayWidth();
1130 int h = displayHeight();
1131 setExtendedStrut(win,
1132 left_width: std::lround(x: left * dpr),
1133 left_start: 0,
1134 left_end: std::lround(x: left * dpr) != 0 ? w : 0,
1135 right_width: std::lround(x: right * dpr),
1136 right_start: 0,
1137 right_end: std::lround(x: right * dpr) != 0 ? w : 0,
1138 top_width: std::lround(x: top * dpr),
1139 top_start: 0,
1140 top_end: std::lround(x: top * dpr) != 0 ? h : 0,
1141 bottom_width: std::lround(x: bottom * dpr),
1142 bottom_start: 0,
1143 bottom_end: std::lround(x: bottom * dpr) != 0 ? h : 0);
1144}
1145
1146// optimalization - create private only when needed and only for what is needed
1147void KX11Extras::connectNotify(const QMetaMethod &signal)
1148{
1149 CHECK_X11_VOID
1150 FilterInfo what = INFO_BASIC;
1151 if (signal == QMetaMethod::fromSignal(signal: &KX11Extras::workAreaChanged)) {
1152 what = INFO_WINDOWS;
1153 } else if (signal == QMetaMethod::fromSignal(signal: &KX11Extras::strutChanged)) {
1154 what = INFO_WINDOWS;
1155 } else if (signal == QMetaMethod::fromSignal(signal: &KX11Extras::windowChanged)) {
1156 what = INFO_WINDOWS;
1157 }
1158
1159 init(what);
1160 NETEventFilter *const s_d = s_d_func();
1161 if (!s_d->strutSignalConnected && signal == QMetaMethod::fromSignal(signal: &KX11Extras::strutChanged)) {
1162 s_d->strutSignalConnected = true;
1163 }
1164 QObject::connectNotify(signal);
1165}
1166
1167void KX11Extras::setType(WId win, NET::WindowType windowType)
1168{
1169 CHECK_X11_VOID
1170 NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
1171 info.setWindowType(windowType);
1172}
1173
1174void KX11Extras::setState(WId win, NET::States state)
1175{
1176 CHECK_X11_VOID
1177 NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
1178 info.setState(state, mask: state);
1179}
1180
1181void KX11Extras::clearState(WId win, NET::States state)
1182{
1183 CHECK_X11_VOID
1184 NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
1185 info.setState(state: NET::States(), mask: state);
1186}
1187
1188int KX11Extras::viewportToDesktop(const QPoint &p)
1189{
1190 CHECK_X11
1191 KX11Extras::self()->init(what: INFO_BASIC);
1192 NETEventFilter *const s_d = KX11Extras::self()->s_d_func();
1193 NETSize s = s_d->desktopGeometry();
1194 QSize vs(displayWidth(), displayHeight());
1195 int xs = s.width / vs.width();
1196 int x = p.x() < 0 ? 0 : p.x() >= s.width ? xs - 1 : p.x() / vs.width();
1197 int ys = s.height / vs.height();
1198 int y = p.y() < 0 ? 0 : p.y() >= s.height ? ys - 1 : p.y() / vs.height();
1199 return y * xs + x + 1;
1200}
1201
1202QPoint KX11Extras::constrainViewportRelativePosition(const QPoint &pos)
1203{
1204 CHECK_X11
1205 init(what: INFO_BASIC);
1206 NETEventFilter *const s_d = s_d_func();
1207 NETSize s = s_d->desktopGeometry();
1208 NETPoint c = s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true));
1209 int x = (pos.x() + c.x) % s.width;
1210 int y = (pos.y() + c.y) % s.height;
1211 if (x < 0) {
1212 x += s.width;
1213 }
1214 if (y < 0) {
1215 y += s.height;
1216 }
1217 return QPoint(x - c.x, y - c.y);
1218}
1219
1220QPoint KX11Extras::desktopToViewport(int desktop, bool absolute)
1221{
1222 CHECK_X11
1223 init(what: INFO_BASIC);
1224 NETEventFilter *const s_d = s_d_func();
1225 NETSize s = s_d->desktopGeometry();
1226 QSize vs(displayWidth(), displayHeight());
1227 int xs = s.width / vs.width();
1228 int ys = s.height / vs.height();
1229 if (desktop <= 0 || desktop > xs * ys) {
1230 return QPoint(0, 0);
1231 }
1232 --desktop;
1233 QPoint ret(vs.width() * (desktop % xs), vs.height() * (desktop / xs));
1234 if (!absolute) {
1235 ret = QPoint(ret.x() - s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true)).x, ret.y() - s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true)).y);
1236 if (ret.x() >= s.width) {
1237 ret.setX(ret.x() - s.width);
1238 }
1239 if (ret.x() < 0) {
1240 ret.setX(ret.x() + s.width);
1241 }
1242 if (ret.y() >= s.height) {
1243 ret.setY(ret.y() - s.height);
1244 }
1245 if (ret.y() < 0) {
1246 ret.setY(ret.y() + s.height);
1247 }
1248 }
1249 return ret;
1250}
1251
1252bool KX11Extras::showingDesktop()
1253{
1254 KX11Extras::self()->init(what: INFO_BASIC);
1255 return KX11Extras::self()->s_d_func()->showingDesktop();
1256}
1257
1258void KX11Extras::setShowingDesktop(bool showing)
1259{
1260 NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::WM2ShowingDesktop, QX11Info::appScreen());
1261 info.setShowingDesktop(showing);
1262}
1263
1264#include "kx11extras.moc"
1265#include "moc_kx11extras.cpp"
1266

source code of kwindowsystem/src/kx11extras.cpp