1/*
2 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "kwindowinfo.h"
8#include "kwindowsystem.h"
9#include "kwindowsystem_debug.h"
10#include "kx11extras.h"
11#include "netwm.h"
12
13#include <config-kwindowsystem.h>
14
15#include "private/qtx11extras_p.h"
16#include <QDebug>
17#include <QRect>
18
19#include "kxerrorhandler_p.h"
20#include <X11/Xatom.h>
21#include <X11/Xlib.h>
22#include <xcb/res.h>
23
24#include "cptr_p.h"
25
26static bool haveXRes()
27{
28 static bool s_checked = false;
29 static bool s_haveXRes = false;
30 if (!s_checked) {
31 auto cookie = xcb_res_query_version(c: QX11Info::connection(), XCB_RES_MAJOR_VERSION, XCB_RES_MINOR_VERSION);
32 UniqueCPointer<xcb_res_query_version_reply_t> reply(xcb_res_query_version_reply(c: QX11Info::connection(), cookie, e: nullptr));
33 s_haveXRes = reply != nullptr;
34 s_checked = true;
35 }
36 return s_haveXRes;
37}
38
39class Q_DECL_HIDDEN KWindowInfoPrivate : public QSharedData
40{
41public:
42 WId window;
43 NET::Properties properties;
44 NET::Properties2 properties2;
45
46 std::unique_ptr<NETWinInfo> m_info;
47 QString m_name;
48 QString m_iconic_name;
49 QRect m_geometry;
50 QRect m_frame_geometry;
51 int m_pid = -1; // real PID from XResources. Valid if > 0
52 bool m_valid = false;
53};
54
55KWindowInfo::KWindowInfo(WId window, NET::Properties properties, NET::Properties2 properties2)
56 : d(new KWindowInfoPrivate)
57{
58 d->window = window;
59 d->properties = properties;
60 d->properties2 = properties2;
61
62 if (!KWindowSystem::isPlatformX11()) {
63 return;
64 }
65
66 KXErrorHandler handler;
67 if (properties & NET::WMVisibleIconName) {
68 properties |= NET::WMIconName | NET::WMVisibleName; // force, in case it will be used as a fallback
69 }
70 if (properties & NET::WMVisibleName) {
71 properties |= NET::WMName; // force, in case it will be used as a fallback
72 }
73 if (properties2 & NET::WM2ExtendedStrut) {
74 properties |= NET::WMStrut; // will be used as fallback
75 }
76 if (properties & NET::WMWindowType) {
77 properties2 |= NET::WM2TransientFor; // will be used when type is not set
78 }
79 if ((properties & NET::WMDesktop) && KX11Extras::mapViewport()) {
80 properties |= NET::WMGeometry; // for viewports, the desktop (workspace) is determined from the geometry
81 }
82 properties |= NET::XAWMState; // force to get error detection for valid()
83 d->m_info.reset(p: new NETWinInfo(QX11Info::connection(), d->window, QX11Info::appRootWindow(), properties, properties2));
84 if (properties & NET::WMName) {
85 if (d->m_info->name() && d->m_info->name()[0] != '\0') {
86 d->m_name = QString::fromUtf8(utf8: d->m_info->name());
87 } else {
88 d->m_name = KX11Extras::readNameProperty(window: d->window, XA_WM_NAME);
89 }
90 }
91 if (properties & NET::WMIconName) {
92 if (d->m_info->iconName() && d->m_info->iconName()[0] != '\0') {
93 d->m_iconic_name = QString::fromUtf8(utf8: d->m_info->iconName());
94 } else {
95 d->m_iconic_name = KX11Extras::readNameProperty(window: d->window, XA_WM_ICON_NAME);
96 }
97 }
98 if (properties & (NET::WMGeometry | NET::WMFrameExtents)) {
99 NETRect frame;
100 NETRect geom;
101 d->m_info->kdeGeometry(frame, window&: geom);
102 d->m_geometry.setRect(ax: geom.pos.x, ay: geom.pos.y, aw: geom.size.width, ah: geom.size.height);
103 d->m_frame_geometry.setRect(ax: frame.pos.x, ay: frame.pos.y, aw: frame.size.width, ah: frame.size.height);
104 }
105 d->m_valid = !handler.error(sync: false); // no sync - NETWinInfo did roundtrips
106
107 if (haveXRes()) {
108 xcb_res_client_id_spec_t specs;
109 specs.client = win();
110 specs.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID;
111 auto cookie = xcb_res_query_client_ids(c: QX11Info::connection(), num_specs: 1, specs: &specs);
112
113 UniqueCPointer<xcb_res_query_client_ids_reply_t> reply(xcb_res_query_client_ids_reply(c: QX11Info::connection(), cookie, e: nullptr));
114 if (reply && xcb_res_query_client_ids_ids_length(R: reply.get()) > 0) {
115 uint32_t pid = *xcb_res_client_id_value_value(R: (xcb_res_query_client_ids_ids_iterator(R: reply.get()).data));
116 d->m_pid = pid;
117 }
118 }
119}
120
121KWindowInfo::KWindowInfo(const KWindowInfo &other)
122 : d(other.d)
123{
124}
125
126KWindowInfo::~KWindowInfo()
127{
128}
129
130KWindowInfo &KWindowInfo::operator=(const KWindowInfo &other)
131{
132 if (d != other.d) {
133 d = other.d;
134 }
135 return *this;
136}
137
138bool KWindowInfo::valid(bool withdrawn_is_valid) const
139{
140 if (!KWindowSystem::isPlatformX11()) {
141 return false;
142 }
143
144 if (!d->m_valid) {
145 return false;
146 }
147 if (!withdrawn_is_valid && mappingState() == NET::Withdrawn) {
148 return false;
149 }
150 return true;
151}
152
153WId KWindowInfo::win() const
154{
155 return d->window;
156}
157
158#define CHECK_X11 \
159 if (!KWindowSystem::isPlatformX11()) { \
160 qCWarning(LOG_KWINDOWSYSTEM) << "KWindowInfo is only functional when running on X11"; \
161 return {}; \
162 }
163
164NET::States KWindowInfo::state() const
165{
166 CHECK_X11
167#if !defined(KDE_NO_WARNING_OUTPUT)
168 if (!(d->m_info->passedProperties() & NET::WMState)) {
169 qWarning() << "Pass NET::WMState to KWindowInfo";
170 }
171#endif
172 return d->m_info->state();
173}
174
175bool KWindowInfo::hasState(NET::States s) const
176{
177 CHECK_X11
178 return (state() & s) == s;
179}
180
181bool KWindowInfo::icccmCompliantMappingState() const
182{
183 CHECK_X11
184 static enum { noidea, yes, no } wm_is_1_2_compliant = noidea;
185 if (wm_is_1_2_compliant == noidea) {
186 NETRootInfo info(QX11Info::connection(), NET::Supported, NET::Properties2(), QX11Info::appScreen());
187 wm_is_1_2_compliant = info.isSupported(state: NET::Hidden) ? yes : no;
188 }
189 return wm_is_1_2_compliant == yes;
190}
191
192// see NETWM spec section 7.6
193bool KWindowInfo::isMinimized() const
194{
195 CHECK_X11
196 if (mappingState() != NET::Iconic) {
197 return false;
198 }
199 // NETWM 1.2 compliant WM - uses NET::Hidden for minimized windows
200 if ((state() & NET::Hidden) != 0 && (state() & NET::Shaded) == 0) { // shaded may have NET::Hidden too
201 return true;
202 }
203 // older WMs use WithdrawnState for other virtual desktops
204 // and IconicState only for minimized
205 return icccmCompliantMappingState() ? false : true;
206}
207
208NET::MappingState KWindowInfo::mappingState() const
209{
210 CHECK_X11
211#if !defined(KDE_NO_WARNING_OUTPUT)
212 if (!(d->m_info->passedProperties() & NET::XAWMState)) {
213 qWarning() << "Pass NET::XAWMState to KWindowInfo";
214 }
215#endif
216 return d->m_info->mappingState();
217}
218
219NETExtendedStrut KWindowInfo::extendedStrut() const
220{
221 CHECK_X11
222#if !defined(KDE_NO_WARNING_OUTPUT)
223 if (!(d->m_info->passedProperties2() & NET::WM2ExtendedStrut)) {
224 qWarning() << "Pass NET::WM2ExtendedStrut to KWindowInfo";
225 }
226#endif
227 NETExtendedStrut ext = d->m_info->extendedStrut();
228 NETStrut str = d->m_info->strut();
229 if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0
230 && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) {
231 // build extended from simple
232 if (str.left != 0) {
233 ext.left_width = str.left;
234 ext.left_start = 0;
235 ext.left_end = XDisplayHeight(QX11Info::display(), DefaultScreen(QX11Info::display()));
236 }
237 if (str.right != 0) {
238 ext.right_width = str.right;
239 ext.right_start = 0;
240 ext.right_end = XDisplayHeight(QX11Info::display(), DefaultScreen(QX11Info::display()));
241 }
242 if (str.top != 0) {
243 ext.top_width = str.top;
244 ext.top_start = 0;
245 ext.top_end = XDisplayWidth(QX11Info::display(), DefaultScreen(QX11Info::display()));
246 }
247 if (str.bottom != 0) {
248 ext.bottom_width = str.bottom;
249 ext.bottom_start = 0;
250 ext.bottom_end = XDisplayWidth(QX11Info::display(), DefaultScreen(QX11Info::display()));
251 }
252 }
253 return ext;
254}
255
256NET::WindowType KWindowInfo::windowType(NET::WindowTypes supported_types) const
257{
258 CHECK_X11
259#if !defined(KDE_NO_WARNING_OUTPUT)
260 if (!(d->m_info->passedProperties() & NET::WMWindowType)) {
261 qWarning() << "Pass NET::WMWindowType to KWindowInfo";
262 }
263#endif
264 if (!d->m_info->hasWindowType()) { // fallback, per spec recommendation
265 if (transientFor() != XCB_WINDOW_NONE) { // dialog
266 if (supported_types & NET::DialogMask) {
267 return NET::Dialog;
268 }
269 } else {
270 if (supported_types & NET::NormalMask) {
271 return NET::Normal;
272 }
273 }
274 }
275 return d->m_info->windowType(supported_types);
276}
277
278QString KWindowInfo::visibleName() const
279{
280 CHECK_X11
281#if !defined(KDE_NO_WARNING_OUTPUT)
282 if (!(d->m_info->passedProperties() & NET::WMVisibleName)) {
283 qWarning() << "Pass NET::WMVisibleName to KWindowInfo";
284 }
285#endif
286 return d->m_info->visibleName() && d->m_info->visibleName()[0] != '\0' ? QString::fromUtf8(utf8: d->m_info->visibleName()) : name();
287}
288
289QString KWindowInfo::visibleNameWithState() const
290{
291 CHECK_X11
292 QString s = visibleName();
293 if (isMinimized()) {
294 s.prepend(c: QLatin1Char('('));
295 s.append(c: QLatin1Char(')'));
296 }
297 return s;
298}
299
300QString KWindowInfo::name() const
301{
302 CHECK_X11
303#if !defined(KDE_NO_WARNING_OUTPUT)
304 if (!(d->m_info->passedProperties() & NET::WMName)) {
305 qWarning() << "Pass NET::WMName to KWindowInfo";
306 }
307#endif
308 return d->m_name;
309}
310
311QString KWindowInfo::visibleIconName() const
312{
313 CHECK_X11
314#if !defined(KDE_NO_WARNING_OUTPUT)
315 if (!(d->m_info->passedProperties() & NET::WMVisibleIconName)) {
316 qWarning() << "Pass NET::WMVisibleIconName to KWindowInfo";
317 }
318#endif
319 if (d->m_info->visibleIconName() && d->m_info->visibleIconName()[0] != '\0') {
320 return QString::fromUtf8(utf8: d->m_info->visibleIconName());
321 }
322 if (d->m_info->iconName() && d->m_info->iconName()[0] != '\0') {
323 return QString::fromUtf8(utf8: d->m_info->iconName());
324 }
325 if (!d->m_iconic_name.isEmpty()) {
326 return d->m_iconic_name;
327 }
328 return visibleName();
329}
330
331QString KWindowInfo::visibleIconNameWithState() const
332{
333 CHECK_X11
334 QString s = visibleIconName();
335 if (isMinimized()) {
336 s.prepend(c: QLatin1Char('('));
337 s.append(c: QLatin1Char(')'));
338 }
339 return s;
340}
341
342QString KWindowInfo::iconName() const
343{
344 CHECK_X11
345#if !defined(KDE_NO_WARNING_OUTPUT)
346 if (!(d->m_info->passedProperties() & NET::WMIconName)) {
347 qWarning() << "Pass NET::WMIconName to KWindowInfo";
348 }
349#endif
350 if (d->m_info->iconName() && d->m_info->iconName()[0] != '\0') {
351 return QString::fromUtf8(utf8: d->m_info->iconName());
352 }
353 if (!d->m_iconic_name.isEmpty()) {
354 return d->m_iconic_name;
355 }
356 return name();
357}
358
359bool KWindowInfo::isOnCurrentDesktop() const
360{
361 CHECK_X11
362 return isOnDesktop(desktop: KX11Extras::currentDesktop());
363}
364
365bool KWindowInfo::isOnDesktop(int desktop) const
366{
367 CHECK_X11
368#if !defined(KDE_NO_WARNING_OUTPUT)
369 if (!(d->m_info->passedProperties() & NET::WMDesktop)) {
370 qWarning() << "Pass NET::WMDesktop to KWindowInfo";
371 }
372#endif
373 if (KX11Extras::mapViewport()) {
374 if (onAllDesktops()) {
375 return true;
376 }
377 return KX11Extras::viewportWindowToDesktop(r: d->m_geometry) == desktop;
378 }
379 return d->m_info->desktop() == desktop || d->m_info->desktop() == NET::OnAllDesktops;
380}
381
382bool KWindowInfo::onAllDesktops() const
383{
384 CHECK_X11
385#if !defined(KDE_NO_WARNING_OUTPUT)
386 if (!(d->m_info->passedProperties() & NET::WMDesktop)) {
387 qWarning() << "Pass NET::WMDesktop to KWindowInfo";
388 }
389#endif
390 if (KX11Extras::mapViewport()) {
391 if (d->m_info->passedProperties() & NET::WMState) {
392 return d->m_info->state() & NET::Sticky;
393 }
394 NETWinInfo info(QX11Info::connection(), win(), QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
395 return info.state() & NET::Sticky;
396 }
397 return d->m_info->desktop() == NET::OnAllDesktops;
398}
399
400int KWindowInfo::desktop() const
401{
402 CHECK_X11
403#if !defined(KDE_NO_WARNING_OUTPUT)
404 if (!(d->m_info->passedProperties() & NET::WMDesktop)) {
405 qWarning() << "Pass NET::WMDesktop to KWindowInfo";
406 }
407#endif
408 if (KX11Extras::mapViewport()) {
409 if (onAllDesktops()) {
410 return NET::OnAllDesktops;
411 }
412 return KX11Extras::viewportWindowToDesktop(r: d->m_geometry);
413 }
414 return d->m_info->desktop();
415}
416
417QStringList KWindowInfo::activities() const
418{
419 CHECK_X11
420#if !defined(KDE_NO_WARNING_OUTPUT)
421 if (!(d->m_info->passedProperties2() & NET::WM2Activities)) {
422 qWarning() << "Pass NET::WM2Activities to KWindowInfo";
423 }
424#endif
425
426 const QStringList result = QString::fromLatin1(ba: d->m_info->activities()).split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
427
428 return result.contains(QStringLiteral(KDE_ALL_ACTIVITIES_UUID)) ? QStringList() : result;
429}
430
431QRect KWindowInfo::geometry() const
432{
433 CHECK_X11
434#if !defined(KDE_NO_WARNING_OUTPUT)
435 if (!(d->m_info->passedProperties() & NET::WMGeometry)) {
436 qWarning() << "Pass NET::WMGeometry to KWindowInfo";
437 }
438#endif
439 return d->m_geometry;
440}
441
442QRect KWindowInfo::frameGeometry() const
443{
444 CHECK_X11
445#if !defined(KDE_NO_WARNING_OUTPUT)
446 if (!(d->m_info->passedProperties() & NET::WMFrameExtents)) {
447 qWarning() << "Pass NET::WMFrameExtents to KWindowInfo";
448 }
449#endif
450 return d->m_frame_geometry;
451}
452
453WId KWindowInfo::transientFor() const
454{
455 CHECK_X11
456#if !defined(KDE_NO_WARNING_OUTPUT)
457 if (!(d->m_info->passedProperties2() & NET::WM2TransientFor)) {
458 qWarning() << "Pass NET::WM2TransientFor to KWindowInfo";
459 }
460#endif
461 return d->m_info->transientFor();
462}
463
464WId KWindowInfo::groupLeader() const
465{
466 CHECK_X11
467#if !defined(KDE_NO_WARNING_OUTPUT)
468 if (!(d->m_info->passedProperties2() & NET::WM2GroupLeader)) {
469 qWarning() << "Pass NET::WM2GroupLeader to KWindowInfo";
470 }
471#endif
472 return d->m_info->groupLeader();
473}
474
475QByteArray KWindowInfo::windowClassClass() const
476{
477 CHECK_X11
478#if !defined(KDE_NO_WARNING_OUTPUT)
479 if (!(d->m_info->passedProperties2() & NET::WM2WindowClass)) {
480 qWarning() << "Pass NET::WM2WindowClass to KWindowInfo";
481 }
482#endif
483 return d->m_info->windowClassClass();
484}
485
486QByteArray KWindowInfo::windowClassName() const
487{
488 CHECK_X11
489#if !defined(KDE_NO_WARNING_OUTPUT)
490 if (!(d->m_info->passedProperties2() & NET::WM2WindowClass)) {
491 qWarning() << "Pass NET::WM2WindowClass to KWindowInfo";
492 }
493#endif
494 return d->m_info->windowClassName();
495}
496
497QByteArray KWindowInfo::windowRole() const
498{
499 CHECK_X11
500#if !defined(KDE_NO_WARNING_OUTPUT)
501 if (!(d->m_info->passedProperties2() & NET::WM2WindowRole)) {
502 qWarning() << "Pass NET::WM2WindowRole to KWindowInfo";
503 }
504#endif
505 return d->m_info->windowRole();
506}
507
508QByteArray KWindowInfo::clientMachine() const
509{
510 CHECK_X11
511#if !defined(KDE_NO_WARNING_OUTPUT)
512 if (!(d->m_info->passedProperties2() & NET::WM2ClientMachine)) {
513 qWarning() << "Pass NET::WM2ClientMachine to KWindowInfo";
514 }
515#endif
516 return d->m_info->clientMachine();
517}
518
519bool KWindowInfo::allowedActionsSupported() const
520{
521 CHECK_X11
522 static enum { noidea, yes, no } wm_supports_allowed_actions = noidea;
523 if (wm_supports_allowed_actions == noidea) {
524 NETRootInfo info(QX11Info::connection(), NET::Supported, NET::Properties2(), QX11Info::appScreen());
525 wm_supports_allowed_actions = info.isSupported(property: NET::WM2AllowedActions) ? yes : no;
526 }
527 return wm_supports_allowed_actions == yes;
528}
529
530bool KWindowInfo::actionSupported(NET::Action action) const
531{
532 CHECK_X11
533#if !defined(KDE_NO_WARNING_OUTPUT)
534 if (!(d->m_info->passedProperties2() & NET::WM2AllowedActions)) {
535 qWarning() << "Pass NET::WM2AllowedActions to KWindowInfo";
536 }
537#endif
538 if (allowedActionsSupported()) {
539 return d->m_info->allowedActions() & action;
540 } else {
541 return true; // no idea if it's supported or not -> pretend it is
542 }
543}
544
545QByteArray KWindowInfo::desktopFileName() const
546{
547 CHECK_X11
548#if !defined(KDE_NO_WARNING_OUTPUT)
549 if (!(d->m_info->passedProperties2() & NET::WM2DesktopFileName)) {
550 qWarning() << "Pass NET::WM2DesktopFileName to KWindowInfo";
551 }
552#endif
553 return QByteArray(d->m_info->desktopFileName());
554}
555
556QByteArray KWindowInfo::gtkApplicationId() const
557{
558 CHECK_X11
559#if !defined(KDE_NO_WARNING_OUTPUT)
560 if (!(d->m_info->passedProperties2() & NET::WM2DesktopFileName)) {
561 qWarning() << "Pass NET::WM2DesktopFileName to KWindowInfo";
562 }
563#endif
564 return QByteArray(d->m_info->gtkApplicationId());
565}
566
567QByteArray KWindowInfo::applicationMenuServiceName() const
568{
569 CHECK_X11
570#if !defined(KDE_NO_WARNING_OUTPUT)
571 if (!(d->m_info->passedProperties2() & NET::WM2AppMenuServiceName)) {
572 qWarning() << "Pass NET::WM2AppMenuServiceName to KWindowInfo";
573 }
574#endif
575 return QByteArray(d->m_info->appMenuServiceName());
576}
577
578QByteArray KWindowInfo::applicationMenuObjectPath() const
579{
580 CHECK_X11
581#if !defined(KDE_NO_WARNING_OUTPUT)
582 if (!(d->m_info->passedProperties2() & NET::WM2AppMenuObjectPath)) {
583 qWarning() << "Pass NET::WM2AppMenuObjectPath to KWindowInfo";
584 }
585#endif
586 return QByteArray(d->m_info->appMenuObjectPath());
587}
588
589int KWindowInfo::pid() const
590{
591 CHECK_X11
592 // If pid is found using the XRes extension use that instead.
593 // It is more reliable than the app reporting it's own PID as apps
594 // within an app namespace are unable to do so correctly
595 if (d->m_pid > 0) {
596 return d->m_pid;
597 }
598
599#if !defined(KDE_NO_WARNING_OUTPUT)
600 if (!(d->m_info->passedProperties() & NET::WMPid)) {
601 qWarning() << "Pass NET::WMPid to KWindowInfo";
602 }
603#endif
604
605 return d->m_info->pid();
606}
607

source code of kwindowsystem/src/kwindowinfo.cpp