1/*
2 This file is part of the KDE libraries
3 Copyright (C) 1999 Matthias Ettrich (ettrich@kde.org)
4 Copyright (C) 2007 Lubos Lunak (l.lunak@kde.org)
5 Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library. If not, see <http://www.gnu.org/licenses/>.
19*/
20
21#include "kwindowinfo_p_x11.h"
22#include "kwindowsystem.h"
23
24#include <QDebug>
25#include <netwm.h>
26#include <kxerrorhandler_p.h>
27#include <QX11Info>
28#include <X11/Xatom.h>
29
30#include <xcb/res.h>
31
32static bool haveXRes()
33{
34 static bool s_checked = false;
35 static bool s_haveXRes = false;
36 if (!s_checked) {
37 auto cookie = xcb_res_query_version(QX11Info::connection(), XCB_RES_MAJOR_VERSION, XCB_RES_MINOR_VERSION);
38 QScopedPointer<xcb_res_query_version_reply_t, QScopedPointerPodDeleter >reply(xcb_res_query_version_reply(QX11Info::connection(), cookie, nullptr));
39 s_haveXRes = !reply.isNull();
40 s_checked = true;
41 }
42 return s_haveXRes;
43}
44
45// KWindowSystem::info() should be updated too if something has to be changed here
46KWindowInfoPrivateX11::KWindowInfoPrivateX11(WId _win, NET::Properties properties, NET::Properties2 properties2)
47 : KWindowInfoPrivate(_win, properties, properties2)
48 , KWindowInfoPrivateDesktopFileNameExtension()
49 , KWindowInfoPrivatePidExtension()
50{
51 installDesktopFileNameExtension(this);
52 installPidExtension(this);
53
54 KXErrorHandler handler;
55 if (properties & NET::WMVisibleIconName) {
56 properties |= NET::WMIconName | NET::WMVisibleName; // force, in case it will be used as a fallback
57 }
58 if (properties & NET::WMVisibleName) {
59 properties |= NET::WMName; // force, in case it will be used as a fallback
60 }
61 if (properties2 & NET::WM2ExtendedStrut) {
62 properties |= NET::WMStrut; // will be used as fallback
63 }
64 if (properties & NET::WMWindowType) {
65 properties2 |= NET::WM2TransientFor; // will be used when type is not set
66 }
67 if ((properties & NET::WMDesktop) && KWindowSystem::mapViewport()) {
68 properties |= NET::WMGeometry; // for viewports, the desktop (workspace) is determined from the geometry
69 }
70 properties |= NET::XAWMState; // force to get error detection for valid()
71 m_info.reset(new NETWinInfo(QX11Info::connection(), _win, QX11Info::appRootWindow(), properties, properties2));
72 if (properties & NET::WMName) {
73 if (m_info->name() && m_info->name()[ 0 ] != '\0') {
74 m_name = QString::fromUtf8(m_info->name());
75 } else {
76 m_name = KWindowSystem::readNameProperty(_win, XA_WM_NAME);
77 }
78 }
79 if (properties & NET::WMIconName) {
80 if (m_info->iconName() && m_info->iconName()[ 0 ] != '\0') {
81 m_iconic_name = QString::fromUtf8(m_info->iconName());
82 } else {
83 m_iconic_name = KWindowSystem::readNameProperty(_win, XA_WM_ICON_NAME);
84 }
85 }
86 if (properties & (NET::WMGeometry | NET::WMFrameExtents)) {
87 NETRect frame, geom;
88 m_info->kdeGeometry(frame, geom);
89 m_geometry.setRect(geom.pos.x, geom.pos.y, geom.size.width, geom.size.height);
90 m_frame_geometry.setRect(frame.pos.x, frame.pos.y, frame.size.width, frame.size.height);
91 }
92 m_valid = !handler.error(false); // no sync - NETWinInfo did roundtrips
93
94 if (haveXRes()) {
95 xcb_res_client_id_spec_t specs;
96 specs.client = win();
97 specs.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID;
98 auto cookie = xcb_res_query_client_ids(QX11Info::connection(), 1, &specs);
99
100 QScopedPointer<xcb_res_query_client_ids_reply_t, QScopedPointerPodDeleter> reply( xcb_res_query_client_ids_reply(QX11Info::connection(), cookie, nullptr));
101 if (reply && xcb_res_query_client_ids_ids_length(reply.data()) > 0) {
102 uint32_t pid = *xcb_res_client_id_value_value((xcb_res_query_client_ids_ids_iterator(reply.data()).data));
103 m_pid = pid;
104 }
105 }
106}
107
108KWindowInfoPrivateX11::~KWindowInfoPrivateX11()
109{
110}
111
112bool KWindowInfoPrivateX11::valid(bool withdrawn_is_valid) const
113{
114 if (!m_valid) {
115 return false;
116 }
117 if (!withdrawn_is_valid && mappingState() == NET::Withdrawn) {
118 return false;
119 }
120 return true;
121}
122
123NET::States KWindowInfoPrivateX11::state() const
124{
125#if !defined(KDE_NO_WARNING_OUTPUT)
126 if (!(m_info->passedProperties() & NET::WMState)) {
127 qWarning() << "Pass NET::WMState to KWindowInfo";
128 }
129#endif
130 return m_info->state();
131}
132
133NET::MappingState KWindowInfoPrivateX11::mappingState() const
134{
135#if !defined(KDE_NO_WARNING_OUTPUT)
136 if (!(m_info->passedProperties() & NET::XAWMState)) {
137 qWarning() << "Pass NET::XAWMState to KWindowInfo";
138 }
139#endif
140 return m_info->mappingState();
141}
142
143NETExtendedStrut KWindowInfoPrivateX11::extendedStrut() const
144{
145#if !defined(KDE_NO_WARNING_OUTPUT)
146 if (!(m_info->passedProperties2() & NET::WM2ExtendedStrut)) {
147 qWarning() << "Pass NET::WM2ExtendedStrut to KWindowInfo";
148 }
149#endif
150 NETExtendedStrut ext = m_info->extendedStrut();
151 NETStrut str = m_info->strut();
152 if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0
153 && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) {
154 // build extended from simple
155 if (str.left != 0) {
156 ext.left_width = str.left;
157 ext.left_start = 0;
158 ext.left_end = XDisplayHeight(QX11Info::display(), DefaultScreen(QX11Info::display()));
159 }
160 if (str.right != 0) {
161 ext.right_width = str.right;
162 ext.right_start = 0;
163 ext.right_end = XDisplayHeight(QX11Info::display(), DefaultScreen(QX11Info::display()));
164 }
165 if (str.top != 0) {
166 ext.top_width = str.top;
167 ext.top_start = 0;
168 ext.top_end = XDisplayWidth(QX11Info::display(), DefaultScreen(QX11Info::display()));
169 }
170 if (str.bottom != 0) {
171 ext.bottom_width = str.bottom;
172 ext.bottom_start = 0;
173 ext.bottom_end = XDisplayWidth(QX11Info::display(), DefaultScreen(QX11Info::display()));
174 }
175 }
176 return ext;
177}
178
179NET::WindowType KWindowInfoPrivateX11::windowType(NET::WindowTypes supported_types) const
180{
181#if !defined(KDE_NO_WARNING_OUTPUT)
182 if (!(m_info->passedProperties() & NET::WMWindowType)) {
183 qWarning() << "Pass NET::WMWindowType to KWindowInfo";
184 }
185#endif
186 if (!m_info->hasWindowType()) { // fallback, per spec recommendation
187 if (transientFor() != XCB_WINDOW_NONE) { // dialog
188 if (supported_types & NET::DialogMask) {
189 return NET::Dialog;
190 }
191 } else {
192 if (supported_types & NET::NormalMask) {
193 return NET::Normal;
194 }
195 }
196 }
197 return m_info->windowType(supported_types);
198}
199
200QString KWindowInfoPrivateX11::visibleNameWithState() const
201{
202 QString s = visibleName();
203 if (isMinimized()) {
204 s.prepend(QLatin1Char('('));
205 s.append(QLatin1Char(')'));
206 }
207 return s;
208}
209
210QString KWindowInfoPrivateX11::visibleName() const
211{
212#if !defined(KDE_NO_WARNING_OUTPUT)
213 if (!(m_info->passedProperties() & NET::WMVisibleName)) {
214 qWarning() << "Pass NET::WMVisibleName to KWindowInfo";
215 }
216#endif
217 return m_info->visibleName() && m_info->visibleName()[ 0 ] != '\0'
218 ? QString::fromUtf8(m_info->visibleName()) : name();
219}
220
221QString KWindowInfoPrivateX11::name() const
222{
223#if !defined(KDE_NO_WARNING_OUTPUT)
224 if (!(m_info->passedProperties() & NET::WMName)) {
225 qWarning() << "Pass NET::WMName to KWindowInfo";
226 }
227#endif
228 return m_name;
229}
230
231QString KWindowInfoPrivateX11::visibleIconNameWithState() const
232{
233 QString s = visibleIconName();
234 if (isMinimized()) {
235 s.prepend(QLatin1Char('('));
236 s.append(QLatin1Char(')'));
237 }
238 return s;
239}
240
241QString KWindowInfoPrivateX11::visibleIconName() const
242{
243#if !defined(KDE_NO_WARNING_OUTPUT)
244 if (!(m_info->passedProperties() & NET::WMVisibleIconName)) {
245 qWarning() << "Pass NET::WMVisibleIconName to KWindowInfo";
246 }
247#endif
248 if (m_info->visibleIconName() && m_info->visibleIconName()[ 0 ] != '\0') {
249 return QString::fromUtf8(m_info->visibleIconName());
250 }
251 if (m_info->iconName() && m_info->iconName()[ 0 ] != '\0') {
252 return QString::fromUtf8(m_info->iconName());
253 }
254 if (!m_iconic_name.isEmpty()) {
255 return m_iconic_name;
256 }
257 return visibleName();
258}
259
260QString KWindowInfoPrivateX11::iconName() const
261{
262#if !defined(KDE_NO_WARNING_OUTPUT)
263 if (!(m_info->passedProperties() & NET::WMIconName)) {
264 qWarning() << "Pass NET::WMIconName to KWindowInfo";
265 }
266#endif
267 if (m_info->iconName() && m_info->iconName()[ 0 ] != '\0') {
268 return QString::fromUtf8(m_info->iconName());
269 }
270 if (!m_iconic_name.isEmpty()) {
271 return m_iconic_name;
272 }
273 return name();
274}
275
276bool KWindowInfoPrivateX11::isOnDesktop(int _desktop) const
277{
278#if !defined(KDE_NO_WARNING_OUTPUT)
279 if (!(m_info->passedProperties() & NET::WMDesktop)) {
280 qWarning() << "Pass NET::WMDesktop to KWindowInfo";
281 }
282#endif
283 if (KWindowSystem::mapViewport()) {
284 if (onAllDesktops()) {
285 return true;
286 }
287 return KWindowSystem::viewportWindowToDesktop(m_geometry) == _desktop;
288 }
289 return m_info->desktop() == _desktop || m_info->desktop() == NET::OnAllDesktops;
290}
291
292bool KWindowInfoPrivateX11::onAllDesktops() const
293{
294#if !defined(KDE_NO_WARNING_OUTPUT)
295 if (!(m_info->passedProperties() & NET::WMDesktop)) {
296 qWarning() << "Pass NET::WMDesktop to KWindowInfo";
297 }
298#endif
299 if (KWindowSystem::mapViewport()) {
300 if (m_info->passedProperties() & NET::WMState) {
301 return m_info->state() & NET::Sticky;
302 }
303 NETWinInfo info(QX11Info::connection(), win(), QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
304 return info.state() & NET::Sticky;
305 }
306 return m_info->desktop() == NET::OnAllDesktops;
307}
308
309int KWindowInfoPrivateX11::desktop() const
310{
311#if !defined(KDE_NO_WARNING_OUTPUT)
312 if (!(m_info->passedProperties() & NET::WMDesktop)) {
313 qWarning() << "Pass NET::WMDesktop to KWindowInfo";
314 }
315#endif
316 if (KWindowSystem::mapViewport()) {
317 if (onAllDesktops()) {
318 return NET::OnAllDesktops;
319 }
320 return KWindowSystem::viewportWindowToDesktop(m_geometry);
321 }
322 return m_info->desktop();
323}
324
325QStringList KWindowInfoPrivateX11::activities() const
326{
327#if !defined(KDE_NO_WARNING_OUTPUT)
328 if (!(m_info->passedProperties2() & NET::WM2Activities)) {
329 qWarning() << "Pass NET::WM2Activities to KWindowInfo";
330 }
331#endif
332
333 const QStringList result = QString::fromLatin1(m_info->activities()).split(
334 QLatin1Char(','), QString::SkipEmptyParts);
335
336 return result.contains(QStringLiteral(KDE_ALL_ACTIVITIES_UUID)) ?
337 QStringList() : result;
338}
339
340QRect KWindowInfoPrivateX11::geometry() const
341{
342#if !defined(KDE_NO_WARNING_OUTPUT)
343 if (!(m_info->passedProperties() & NET::WMGeometry)) {
344 qWarning() << "Pass NET::WMGeometry to KWindowInfo";
345 }
346#endif
347 return m_geometry;
348}
349
350QRect KWindowInfoPrivateX11::frameGeometry() const
351{
352#if !defined(KDE_NO_WARNING_OUTPUT)
353 if (!(m_info->passedProperties() & NET::WMFrameExtents)) {
354 qWarning() << "Pass NET::WMFrameExtents to KWindowInfo";
355 }
356#endif
357 return m_frame_geometry;
358}
359
360WId KWindowInfoPrivateX11::transientFor() const
361{
362#if !defined(KDE_NO_WARNING_OUTPUT)
363 if (!(m_info->passedProperties2() & NET::WM2TransientFor)) {
364 qWarning() << "Pass NET::WM2TransientFor to KWindowInfo";
365 }
366#endif
367 return m_info->transientFor();
368}
369
370WId KWindowInfoPrivateX11::groupLeader() const
371{
372#if !defined(KDE_NO_WARNING_OUTPUT)
373 if (!(m_info->passedProperties2() & NET::WM2GroupLeader)) {
374 qWarning() << "Pass NET::WM2GroupLeader to KWindowInfo";
375 }
376#endif
377 return m_info->groupLeader();
378}
379
380QByteArray KWindowInfoPrivateX11::windowClassClass() const
381{
382#if !defined(KDE_NO_WARNING_OUTPUT)
383 if (!(m_info->passedProperties2() & NET::WM2WindowClass)) {
384 qWarning() << "Pass NET::WM2WindowClass to KWindowInfo";
385 }
386#endif
387 return m_info->windowClassClass();
388}
389
390QByteArray KWindowInfoPrivateX11::windowClassName() const
391{
392#if !defined(KDE_NO_WARNING_OUTPUT)
393 if (!(m_info->passedProperties2() & NET::WM2WindowClass)) {
394 qWarning() << "Pass NET::WM2WindowClass to KWindowInfo";
395 }
396#endif
397 return m_info->windowClassName();
398}
399
400QByteArray KWindowInfoPrivateX11::windowRole() const
401{
402#if !defined(KDE_NO_WARNING_OUTPUT)
403 if (!(m_info->passedProperties2() & NET::WM2WindowRole)) {
404 qWarning() << "Pass NET::WM2WindowRole to KWindowInfo";
405 }
406#endif
407 return m_info->windowRole();
408}
409
410QByteArray KWindowInfoPrivateX11::clientMachine() const
411{
412#if !defined(KDE_NO_WARNING_OUTPUT)
413 if (!(m_info->passedProperties2() & NET::WM2ClientMachine)) {
414 qWarning() << "Pass NET::WM2ClientMachine to KWindowInfo";
415 }
416#endif
417 return m_info->clientMachine();
418}
419
420bool KWindowInfoPrivateX11::actionSupported(NET::Action action) const
421{
422#if !defined(KDE_NO_WARNING_OUTPUT)
423 if (!(m_info->passedProperties2() & NET::WM2AllowedActions)) {
424 qWarning() << "Pass NET::WM2AllowedActions to KWindowInfo";
425 }
426#endif
427 if (KWindowSystem::allowedActionsSupported()) {
428 return m_info->allowedActions() & action;
429 } else {
430 return true; // no idea if it's supported or not -> pretend it is
431 }
432}
433
434// see NETWM spec section 7.6
435bool KWindowInfoPrivateX11::isMinimized() const
436{
437 if (mappingState() != NET::Iconic) {
438 return false;
439 }
440 // NETWM 1.2 compliant WM - uses NET::Hidden for minimized windows
441 if ((state() & NET::Hidden) != 0
442 && (state() & NET::Shaded) == 0) { // shaded may have NET::Hidden too
443 return true;
444 }
445 // older WMs use WithdrawnState for other virtual desktops
446 // and IconicState only for minimized
447 return KWindowSystem::icccmCompliantMappingState() ? false : true;
448}
449
450QByteArray KWindowInfoPrivateX11::desktopFileName() const
451{
452#if !defined(KDE_NO_WARNING_OUTPUT)
453 if (!(m_info->passedProperties2() & NET::WM2DesktopFileName)) {
454 qWarning() << "Pass NET::WM2DesktopFileName to KWindowInfo";
455 }
456#endif
457 return QByteArray(m_info->desktopFileName());
458}
459
460int KWindowInfoPrivateX11::pid() const
461{
462 // If pid is found using the XRes extension use that instead.
463 // It is more reliable than the app reporting it's own PID as apps
464 // within an app namespace are unable to do so correctly
465 if (m_pid > 0) {
466 return m_pid;
467 }
468
469#if !defined(KDE_NO_WARNING_OUTPUT)
470 if (!(m_info->passedProperties() & NET::WMPid)) {
471 qWarning() << "Pass NET::WMPid to KWindowInfo";
472 }
473#endif
474
475 return m_info->pid();
476}
477