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 |
39 | inline QRect operator/(const QRect &rectangle, qreal factor) |
40 | { |
41 | return QRect(rectangle.topLeft() / factor, rectangle.size() / factor); |
42 | } |
43 | |
44 | class MainThreadInstantiator : public QObject |
45 | { |
46 | Q_OBJECT |
47 | |
48 | public: |
49 | MainThreadInstantiator(KX11Extras::FilterInfo _what); |
50 | Q_INVOKABLE NETEventFilter *createNETEventFilter(); |
51 | |
52 | private: |
53 | KX11Extras::FilterInfo m_what; |
54 | }; |
55 | |
56 | class NETEventFilter : public NETRootInfo, public QAbstractNativeEventFilter |
57 | { |
58 | public: |
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 | |
90 | protected: |
91 | void addClient(xcb_window_t) override; |
92 | void removeClient(xcb_window_t) override; |
93 | |
94 | private: |
95 | bool nativeEventFilter(xcb_generic_event_t *event); |
96 | xcb_window_t winId; |
97 | xcb_window_t m_appRootWindow; |
98 | }; |
99 | |
100 | static Atom net_wm_cm; |
101 | static void create_atoms(); |
102 | |
103 | static 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 | |
136 | static inline int displayWidth() |
137 | { |
138 | return displayGeometry().width(); |
139 | } |
140 | |
141 | static inline int displayHeight() |
142 | { |
143 | return displayGeometry().height(); |
144 | } |
145 | |
146 | // clang-format off |
147 | static 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; |
156 | static 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 |
160 | static 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; |
169 | static const NET::Properties2 desktopProperties2 = NET::WM2ShowingDesktop; |
170 | // clang-format on |
171 | |
172 | MainThreadInstantiator::MainThreadInstantiator(KX11Extras::FilterInfo _what) |
173 | : QObject() |
174 | , m_what(_what) |
175 | { |
176 | } |
177 | |
178 | NETEventFilter *MainThreadInstantiator::createNETEventFilter() |
179 | { |
180 | return new NETEventFilter(m_what); |
181 | } |
182 | |
183 | 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 | |
225 | NETEventFilter::~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() |
234 | void NETEventFilter::activate() |
235 | { |
236 | NETRootInfo::activate(); |
237 | updateStackingOrder(); |
238 | } |
239 | |
240 | bool 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 | |
249 | bool 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 | |
370 | bool 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 | |
381 | void NETEventFilter::updateStackingOrder() |
382 | { |
383 | stackingOrder.clear(); |
384 | for (int i = 0; i < clientListStackingCount(); i++) { |
385 | stackingOrder.append(t: clientListStacking()[i]); |
386 | } |
387 | } |
388 | |
389 | void 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 | |
422 | void 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 | |
441 | bool 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 | |
454 | static bool atoms_created = false; |
455 | |
456 | static Atom _wm_protocols; |
457 | static Atom _wm_change_state; |
458 | static Atom kwm_utf8_string; |
459 | |
460 | static 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! |
507 | void KX11Extras::(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 | |
539 | KX11Extras *KX11Extras::() |
540 | { |
541 | static KX11Extras instance; |
542 | return &instance; |
543 | } |
544 | |
545 | QList<WId> KX11Extras::() |
546 | { |
547 | CHECK_X11 |
548 | KX11Extras::self()->init(what: INFO_BASIC); |
549 | return KX11Extras::self()->s_d_func()->windows; |
550 | } |
551 | |
552 | bool KX11Extras::(WId w) |
553 | { |
554 | CHECK_X11 |
555 | return windows().contains(t: w); |
556 | } |
557 | |
558 | QList<WId> KX11Extras::() |
559 | { |
560 | CHECK_X11 |
561 | KX11Extras::self()->init(what: INFO_BASIC); |
562 | return KX11Extras::self()->s_d_func()->stackingOrder; |
563 | } |
564 | |
565 | WId KX11Extras::() |
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 | |
576 | void KX11Extras::(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 | |
586 | void KX11Extras::(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 | |
596 | void KX11Extras::(QWindow *win, long time) |
597 | { |
598 | CHECK_X11_VOID |
599 | forceActiveWindow(win: win->winId(), time); |
600 | } |
601 | |
602 | bool KX11Extras::() |
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 | |
614 | int KX11Extras::() |
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 | |
636 | int KX11Extras::() |
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 | |
658 | void KX11Extras::(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 | |
676 | void KX11Extras::(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 | |
696 | void KX11Extras::(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 | |
742 | void KX11Extras::(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 | |
749 | QPixmap KX11Extras::(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 | |
755 | QPixmap 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 | |
838 | QPixmap KX11Extras::(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 | |
845 | QPixmap KX11Extras::(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 |
862 | enum { |
863 | _ICCCM_WM_STATE_WITHDRAWN = 0, |
864 | _ICCCM_WM_STATE_NORMAL = 1, |
865 | _ICCCM_WM_STATE_ICONIC = 3, |
866 | }; |
867 | |
868 | void KX11Extras::(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 | |
890 | void KX11Extras::(WId win) |
891 | { |
892 | CHECK_X11_VOID |
893 | xcb_map_window(c: QX11Info::connection(), window: win); |
894 | } |
895 | |
896 | QRect KX11Extras::(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 | |
913 | QRect KX11Extras::(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 | |
981 | QString KX11Extras::(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 | |
997 | void KX11Extras::(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 | |
1015 | QString KX11Extras::(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 | |
1040 | bool KX11Extras::() |
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 | |
1065 | int KX11Extras::(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 | |
1084 | void KX11Extras::(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 | |
1124 | void KX11Extras::(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 |
1147 | void KX11Extras::(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 | |
1167 | void KX11Extras::(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 | |
1174 | void KX11Extras::(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 | |
1181 | void KX11Extras::(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 | |
1188 | int KX11Extras::(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 | |
1202 | QPoint KX11Extras::(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 | |
1220 | QPoint KX11Extras::(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 | |
1252 | bool KX11Extras::() |
1253 | { |
1254 | KX11Extras::self()->init(what: INFO_BASIC); |
1255 | return KX11Extras::self()->s_d_func()->showingDesktop(); |
1256 | } |
1257 | |
1258 | void KX11Extras::(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 | |