File indexing completed on 2024-04-28 07:48:31
0001 /* 0002 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "kwindowinfo.h" 0008 #include "kwindowsystem.h" 0009 #include "kwindowsystem_debug.h" 0010 #include "kx11extras.h" 0011 #include "netwm.h" 0012 0013 #include <config-kwindowsystem.h> 0014 0015 #include "private/qtx11extras_p.h" 0016 #include <QDebug> 0017 #include <QRect> 0018 0019 #include "kxerrorhandler_p.h" 0020 #include <X11/Xatom.h> 0021 #include <X11/Xlib.h> 0022 #include <xcb/res.h> 0023 0024 #include "cptr_p.h" 0025 0026 static bool haveXRes() 0027 { 0028 static bool s_checked = false; 0029 static bool s_haveXRes = false; 0030 if (!s_checked) { 0031 auto cookie = xcb_res_query_version(QX11Info::connection(), XCB_RES_MAJOR_VERSION, XCB_RES_MINOR_VERSION); 0032 UniqueCPointer<xcb_res_query_version_reply_t> reply(xcb_res_query_version_reply(QX11Info::connection(), cookie, nullptr)); 0033 s_haveXRes = reply != nullptr; 0034 s_checked = true; 0035 } 0036 return s_haveXRes; 0037 } 0038 0039 class Q_DECL_HIDDEN KWindowInfoPrivate : public QSharedData 0040 { 0041 public: 0042 WId window; 0043 NET::Properties properties; 0044 NET::Properties2 properties2; 0045 0046 std::unique_ptr<NETWinInfo> m_info; 0047 QString m_name; 0048 QString m_iconic_name; 0049 QRect m_geometry; 0050 QRect m_frame_geometry; 0051 int m_pid = -1; // real PID from XResources. Valid if > 0 0052 bool m_valid = false; 0053 }; 0054 0055 KWindowInfo::KWindowInfo(WId window, NET::Properties properties, NET::Properties2 properties2) 0056 : d(new KWindowInfoPrivate) 0057 { 0058 d->window = window; 0059 d->properties = properties; 0060 d->properties2 = properties2; 0061 0062 if (!KWindowSystem::isPlatformX11()) { 0063 return; 0064 } 0065 0066 KXErrorHandler handler; 0067 if (properties & NET::WMVisibleIconName) { 0068 properties |= NET::WMIconName | NET::WMVisibleName; // force, in case it will be used as a fallback 0069 } 0070 if (properties & NET::WMVisibleName) { 0071 properties |= NET::WMName; // force, in case it will be used as a fallback 0072 } 0073 if (properties2 & NET::WM2ExtendedStrut) { 0074 properties |= NET::WMStrut; // will be used as fallback 0075 } 0076 if (properties & NET::WMWindowType) { 0077 properties2 |= NET::WM2TransientFor; // will be used when type is not set 0078 } 0079 if ((properties & NET::WMDesktop) && KX11Extras::mapViewport()) { 0080 properties |= NET::WMGeometry; // for viewports, the desktop (workspace) is determined from the geometry 0081 } 0082 properties |= NET::XAWMState; // force to get error detection for valid() 0083 d->m_info.reset(new NETWinInfo(QX11Info::connection(), d->window, QX11Info::appRootWindow(), properties, properties2)); 0084 if (properties & NET::WMName) { 0085 if (d->m_info->name() && d->m_info->name()[0] != '\0') { 0086 d->m_name = QString::fromUtf8(d->m_info->name()); 0087 } else { 0088 d->m_name = KX11Extras::readNameProperty(d->window, XA_WM_NAME); 0089 } 0090 } 0091 if (properties & NET::WMIconName) { 0092 if (d->m_info->iconName() && d->m_info->iconName()[0] != '\0') { 0093 d->m_iconic_name = QString::fromUtf8(d->m_info->iconName()); 0094 } else { 0095 d->m_iconic_name = KX11Extras::readNameProperty(d->window, XA_WM_ICON_NAME); 0096 } 0097 } 0098 if (properties & (NET::WMGeometry | NET::WMFrameExtents)) { 0099 NETRect frame; 0100 NETRect geom; 0101 d->m_info->kdeGeometry(frame, geom); 0102 d->m_geometry.setRect(geom.pos.x, geom.pos.y, geom.size.width, geom.size.height); 0103 d->m_frame_geometry.setRect(frame.pos.x, frame.pos.y, frame.size.width, frame.size.height); 0104 } 0105 d->m_valid = !handler.error(false); // no sync - NETWinInfo did roundtrips 0106 0107 if (haveXRes()) { 0108 xcb_res_client_id_spec_t specs; 0109 specs.client = win(); 0110 specs.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID; 0111 auto cookie = xcb_res_query_client_ids(QX11Info::connection(), 1, &specs); 0112 0113 UniqueCPointer<xcb_res_query_client_ids_reply_t> reply(xcb_res_query_client_ids_reply(QX11Info::connection(), cookie, nullptr)); 0114 if (reply && xcb_res_query_client_ids_ids_length(reply.get()) > 0) { 0115 uint32_t pid = *xcb_res_client_id_value_value((xcb_res_query_client_ids_ids_iterator(reply.get()).data)); 0116 d->m_pid = pid; 0117 } 0118 } 0119 } 0120 0121 KWindowInfo::KWindowInfo(const KWindowInfo &other) 0122 : d(other.d) 0123 { 0124 } 0125 0126 KWindowInfo::~KWindowInfo() 0127 { 0128 } 0129 0130 KWindowInfo &KWindowInfo::operator=(const KWindowInfo &other) 0131 { 0132 if (d != other.d) { 0133 d = other.d; 0134 } 0135 return *this; 0136 } 0137 0138 bool KWindowInfo::valid(bool withdrawn_is_valid) const 0139 { 0140 if (!KWindowSystem::isPlatformX11()) { 0141 return false; 0142 } 0143 0144 if (!d->m_valid) { 0145 return false; 0146 } 0147 if (!withdrawn_is_valid && mappingState() == NET::Withdrawn) { 0148 return false; 0149 } 0150 return true; 0151 } 0152 0153 WId KWindowInfo::win() const 0154 { 0155 return d->window; 0156 } 0157 0158 #define CHECK_X11 \ 0159 if (!KWindowSystem::isPlatformX11()) { \ 0160 qCWarning(LOG_KWINDOWSYSTEM) << "KWindowInfo is only functional when running on X11"; \ 0161 return {}; \ 0162 } 0163 0164 NET::States KWindowInfo::state() const 0165 { 0166 CHECK_X11 0167 #if !defined(KDE_NO_WARNING_OUTPUT) 0168 if (!(d->m_info->passedProperties() & NET::WMState)) { 0169 qWarning() << "Pass NET::WMState to KWindowInfo"; 0170 } 0171 #endif 0172 return d->m_info->state(); 0173 } 0174 0175 bool KWindowInfo::hasState(NET::States s) const 0176 { 0177 CHECK_X11 0178 return (state() & s) == s; 0179 } 0180 0181 bool KWindowInfo::icccmCompliantMappingState() const 0182 { 0183 CHECK_X11 0184 static enum { noidea, yes, no } wm_is_1_2_compliant = noidea; 0185 if (wm_is_1_2_compliant == noidea) { 0186 NETRootInfo info(QX11Info::connection(), NET::Supported, NET::Properties2(), QX11Info::appScreen()); 0187 wm_is_1_2_compliant = info.isSupported(NET::Hidden) ? yes : no; 0188 } 0189 return wm_is_1_2_compliant == yes; 0190 } 0191 0192 // see NETWM spec section 7.6 0193 bool KWindowInfo::isMinimized() const 0194 { 0195 CHECK_X11 0196 if (mappingState() != NET::Iconic) { 0197 return false; 0198 } 0199 // NETWM 1.2 compliant WM - uses NET::Hidden for minimized windows 0200 if ((state() & NET::Hidden) != 0 && (state() & NET::Shaded) == 0) { // shaded may have NET::Hidden too 0201 return true; 0202 } 0203 // older WMs use WithdrawnState for other virtual desktops 0204 // and IconicState only for minimized 0205 return icccmCompliantMappingState() ? false : true; 0206 } 0207 0208 NET::MappingState KWindowInfo::mappingState() const 0209 { 0210 CHECK_X11 0211 #if !defined(KDE_NO_WARNING_OUTPUT) 0212 if (!(d->m_info->passedProperties() & NET::XAWMState)) { 0213 qWarning() << "Pass NET::XAWMState to KWindowInfo"; 0214 } 0215 #endif 0216 return d->m_info->mappingState(); 0217 } 0218 0219 NETExtendedStrut KWindowInfo::extendedStrut() const 0220 { 0221 CHECK_X11 0222 #if !defined(KDE_NO_WARNING_OUTPUT) 0223 if (!(d->m_info->passedProperties2() & NET::WM2ExtendedStrut)) { 0224 qWarning() << "Pass NET::WM2ExtendedStrut to KWindowInfo"; 0225 } 0226 #endif 0227 NETExtendedStrut ext = d->m_info->extendedStrut(); 0228 NETStrut str = d->m_info->strut(); 0229 if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 0230 && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) { 0231 // build extended from simple 0232 if (str.left != 0) { 0233 ext.left_width = str.left; 0234 ext.left_start = 0; 0235 ext.left_end = XDisplayHeight(QX11Info::display(), DefaultScreen(QX11Info::display())); 0236 } 0237 if (str.right != 0) { 0238 ext.right_width = str.right; 0239 ext.right_start = 0; 0240 ext.right_end = XDisplayHeight(QX11Info::display(), DefaultScreen(QX11Info::display())); 0241 } 0242 if (str.top != 0) { 0243 ext.top_width = str.top; 0244 ext.top_start = 0; 0245 ext.top_end = XDisplayWidth(QX11Info::display(), DefaultScreen(QX11Info::display())); 0246 } 0247 if (str.bottom != 0) { 0248 ext.bottom_width = str.bottom; 0249 ext.bottom_start = 0; 0250 ext.bottom_end = XDisplayWidth(QX11Info::display(), DefaultScreen(QX11Info::display())); 0251 } 0252 } 0253 return ext; 0254 } 0255 0256 NET::WindowType KWindowInfo::windowType(NET::WindowTypes supported_types) const 0257 { 0258 CHECK_X11 0259 #if !defined(KDE_NO_WARNING_OUTPUT) 0260 if (!(d->m_info->passedProperties() & NET::WMWindowType)) { 0261 qWarning() << "Pass NET::WMWindowType to KWindowInfo"; 0262 } 0263 #endif 0264 if (!d->m_info->hasWindowType()) { // fallback, per spec recommendation 0265 if (transientFor() != XCB_WINDOW_NONE) { // dialog 0266 if (supported_types & NET::DialogMask) { 0267 return NET::Dialog; 0268 } 0269 } else { 0270 if (supported_types & NET::NormalMask) { 0271 return NET::Normal; 0272 } 0273 } 0274 } 0275 return d->m_info->windowType(supported_types); 0276 } 0277 0278 QString KWindowInfo::visibleName() const 0279 { 0280 CHECK_X11 0281 #if !defined(KDE_NO_WARNING_OUTPUT) 0282 if (!(d->m_info->passedProperties() & NET::WMVisibleName)) { 0283 qWarning() << "Pass NET::WMVisibleName to KWindowInfo"; 0284 } 0285 #endif 0286 return d->m_info->visibleName() && d->m_info->visibleName()[0] != '\0' ? QString::fromUtf8(d->m_info->visibleName()) : name(); 0287 } 0288 0289 QString KWindowInfo::visibleNameWithState() const 0290 { 0291 CHECK_X11 0292 QString s = visibleName(); 0293 if (isMinimized()) { 0294 s.prepend(QLatin1Char('(')); 0295 s.append(QLatin1Char(')')); 0296 } 0297 return s; 0298 } 0299 0300 QString KWindowInfo::name() const 0301 { 0302 CHECK_X11 0303 #if !defined(KDE_NO_WARNING_OUTPUT) 0304 if (!(d->m_info->passedProperties() & NET::WMName)) { 0305 qWarning() << "Pass NET::WMName to KWindowInfo"; 0306 } 0307 #endif 0308 return d->m_name; 0309 } 0310 0311 QString KWindowInfo::visibleIconName() const 0312 { 0313 CHECK_X11 0314 #if !defined(KDE_NO_WARNING_OUTPUT) 0315 if (!(d->m_info->passedProperties() & NET::WMVisibleIconName)) { 0316 qWarning() << "Pass NET::WMVisibleIconName to KWindowInfo"; 0317 } 0318 #endif 0319 if (d->m_info->visibleIconName() && d->m_info->visibleIconName()[0] != '\0') { 0320 return QString::fromUtf8(d->m_info->visibleIconName()); 0321 } 0322 if (d->m_info->iconName() && d->m_info->iconName()[0] != '\0') { 0323 return QString::fromUtf8(d->m_info->iconName()); 0324 } 0325 if (!d->m_iconic_name.isEmpty()) { 0326 return d->m_iconic_name; 0327 } 0328 return visibleName(); 0329 } 0330 0331 QString KWindowInfo::visibleIconNameWithState() const 0332 { 0333 CHECK_X11 0334 QString s = visibleIconName(); 0335 if (isMinimized()) { 0336 s.prepend(QLatin1Char('(')); 0337 s.append(QLatin1Char(')')); 0338 } 0339 return s; 0340 } 0341 0342 QString KWindowInfo::iconName() const 0343 { 0344 CHECK_X11 0345 #if !defined(KDE_NO_WARNING_OUTPUT) 0346 if (!(d->m_info->passedProperties() & NET::WMIconName)) { 0347 qWarning() << "Pass NET::WMIconName to KWindowInfo"; 0348 } 0349 #endif 0350 if (d->m_info->iconName() && d->m_info->iconName()[0] != '\0') { 0351 return QString::fromUtf8(d->m_info->iconName()); 0352 } 0353 if (!d->m_iconic_name.isEmpty()) { 0354 return d->m_iconic_name; 0355 } 0356 return name(); 0357 } 0358 0359 bool KWindowInfo::isOnCurrentDesktop() const 0360 { 0361 CHECK_X11 0362 return isOnDesktop(KX11Extras::currentDesktop()); 0363 } 0364 0365 bool KWindowInfo::isOnDesktop(int desktop) const 0366 { 0367 CHECK_X11 0368 #if !defined(KDE_NO_WARNING_OUTPUT) 0369 if (!(d->m_info->passedProperties() & NET::WMDesktop)) { 0370 qWarning() << "Pass NET::WMDesktop to KWindowInfo"; 0371 } 0372 #endif 0373 if (KX11Extras::mapViewport()) { 0374 if (onAllDesktops()) { 0375 return true; 0376 } 0377 return KX11Extras::viewportWindowToDesktop(d->m_geometry) == desktop; 0378 } 0379 return d->m_info->desktop() == desktop || d->m_info->desktop() == NET::OnAllDesktops; 0380 } 0381 0382 bool KWindowInfo::onAllDesktops() const 0383 { 0384 CHECK_X11 0385 #if !defined(KDE_NO_WARNING_OUTPUT) 0386 if (!(d->m_info->passedProperties() & NET::WMDesktop)) { 0387 qWarning() << "Pass NET::WMDesktop to KWindowInfo"; 0388 } 0389 #endif 0390 if (KX11Extras::mapViewport()) { 0391 if (d->m_info->passedProperties() & NET::WMState) { 0392 return d->m_info->state() & NET::Sticky; 0393 } 0394 NETWinInfo info(QX11Info::connection(), win(), QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); 0395 return info.state() & NET::Sticky; 0396 } 0397 return d->m_info->desktop() == NET::OnAllDesktops; 0398 } 0399 0400 int KWindowInfo::desktop() const 0401 { 0402 CHECK_X11 0403 #if !defined(KDE_NO_WARNING_OUTPUT) 0404 if (!(d->m_info->passedProperties() & NET::WMDesktop)) { 0405 qWarning() << "Pass NET::WMDesktop to KWindowInfo"; 0406 } 0407 #endif 0408 if (KX11Extras::mapViewport()) { 0409 if (onAllDesktops()) { 0410 return NET::OnAllDesktops; 0411 } 0412 return KX11Extras::viewportWindowToDesktop(d->m_geometry); 0413 } 0414 return d->m_info->desktop(); 0415 } 0416 0417 QStringList KWindowInfo::activities() const 0418 { 0419 CHECK_X11 0420 #if !defined(KDE_NO_WARNING_OUTPUT) 0421 if (!(d->m_info->passedProperties2() & NET::WM2Activities)) { 0422 qWarning() << "Pass NET::WM2Activities to KWindowInfo"; 0423 } 0424 #endif 0425 0426 const QStringList result = QString::fromLatin1(d->m_info->activities()).split(QLatin1Char(','), Qt::SkipEmptyParts); 0427 0428 return result.contains(QStringLiteral(KDE_ALL_ACTIVITIES_UUID)) ? QStringList() : result; 0429 } 0430 0431 QRect KWindowInfo::geometry() const 0432 { 0433 CHECK_X11 0434 #if !defined(KDE_NO_WARNING_OUTPUT) 0435 if (!(d->m_info->passedProperties() & NET::WMGeometry)) { 0436 qWarning() << "Pass NET::WMGeometry to KWindowInfo"; 0437 } 0438 #endif 0439 return d->m_geometry; 0440 } 0441 0442 QRect KWindowInfo::frameGeometry() const 0443 { 0444 CHECK_X11 0445 #if !defined(KDE_NO_WARNING_OUTPUT) 0446 if (!(d->m_info->passedProperties() & NET::WMFrameExtents)) { 0447 qWarning() << "Pass NET::WMFrameExtents to KWindowInfo"; 0448 } 0449 #endif 0450 return d->m_frame_geometry; 0451 } 0452 0453 WId KWindowInfo::transientFor() const 0454 { 0455 CHECK_X11 0456 #if !defined(KDE_NO_WARNING_OUTPUT) 0457 if (!(d->m_info->passedProperties2() & NET::WM2TransientFor)) { 0458 qWarning() << "Pass NET::WM2TransientFor to KWindowInfo"; 0459 } 0460 #endif 0461 return d->m_info->transientFor(); 0462 } 0463 0464 WId KWindowInfo::groupLeader() const 0465 { 0466 CHECK_X11 0467 #if !defined(KDE_NO_WARNING_OUTPUT) 0468 if (!(d->m_info->passedProperties2() & NET::WM2GroupLeader)) { 0469 qWarning() << "Pass NET::WM2GroupLeader to KWindowInfo"; 0470 } 0471 #endif 0472 return d->m_info->groupLeader(); 0473 } 0474 0475 QByteArray KWindowInfo::windowClassClass() const 0476 { 0477 CHECK_X11 0478 #if !defined(KDE_NO_WARNING_OUTPUT) 0479 if (!(d->m_info->passedProperties2() & NET::WM2WindowClass)) { 0480 qWarning() << "Pass NET::WM2WindowClass to KWindowInfo"; 0481 } 0482 #endif 0483 return d->m_info->windowClassClass(); 0484 } 0485 0486 QByteArray KWindowInfo::windowClassName() const 0487 { 0488 CHECK_X11 0489 #if !defined(KDE_NO_WARNING_OUTPUT) 0490 if (!(d->m_info->passedProperties2() & NET::WM2WindowClass)) { 0491 qWarning() << "Pass NET::WM2WindowClass to KWindowInfo"; 0492 } 0493 #endif 0494 return d->m_info->windowClassName(); 0495 } 0496 0497 QByteArray KWindowInfo::windowRole() const 0498 { 0499 CHECK_X11 0500 #if !defined(KDE_NO_WARNING_OUTPUT) 0501 if (!(d->m_info->passedProperties2() & NET::WM2WindowRole)) { 0502 qWarning() << "Pass NET::WM2WindowRole to KWindowInfo"; 0503 } 0504 #endif 0505 return d->m_info->windowRole(); 0506 } 0507 0508 QByteArray KWindowInfo::clientMachine() const 0509 { 0510 CHECK_X11 0511 #if !defined(KDE_NO_WARNING_OUTPUT) 0512 if (!(d->m_info->passedProperties2() & NET::WM2ClientMachine)) { 0513 qWarning() << "Pass NET::WM2ClientMachine to KWindowInfo"; 0514 } 0515 #endif 0516 return d->m_info->clientMachine(); 0517 } 0518 0519 bool KWindowInfo::allowedActionsSupported() const 0520 { 0521 CHECK_X11 0522 static enum { noidea, yes, no } wm_supports_allowed_actions = noidea; 0523 if (wm_supports_allowed_actions == noidea) { 0524 NETRootInfo info(QX11Info::connection(), NET::Supported, NET::Properties2(), QX11Info::appScreen()); 0525 wm_supports_allowed_actions = info.isSupported(NET::WM2AllowedActions) ? yes : no; 0526 } 0527 return wm_supports_allowed_actions == yes; 0528 } 0529 0530 bool KWindowInfo::actionSupported(NET::Action action) const 0531 { 0532 CHECK_X11 0533 #if !defined(KDE_NO_WARNING_OUTPUT) 0534 if (!(d->m_info->passedProperties2() & NET::WM2AllowedActions)) { 0535 qWarning() << "Pass NET::WM2AllowedActions to KWindowInfo"; 0536 } 0537 #endif 0538 if (allowedActionsSupported()) { 0539 return d->m_info->allowedActions() & action; 0540 } else { 0541 return true; // no idea if it's supported or not -> pretend it is 0542 } 0543 } 0544 0545 QByteArray KWindowInfo::desktopFileName() const 0546 { 0547 CHECK_X11 0548 #if !defined(KDE_NO_WARNING_OUTPUT) 0549 if (!(d->m_info->passedProperties2() & NET::WM2DesktopFileName)) { 0550 qWarning() << "Pass NET::WM2DesktopFileName to KWindowInfo"; 0551 } 0552 #endif 0553 return QByteArray(d->m_info->desktopFileName()); 0554 } 0555 0556 QByteArray KWindowInfo::gtkApplicationId() const 0557 { 0558 CHECK_X11 0559 #if !defined(KDE_NO_WARNING_OUTPUT) 0560 if (!(d->m_info->passedProperties2() & NET::WM2DesktopFileName)) { 0561 qWarning() << "Pass NET::WM2DesktopFileName to KWindowInfo"; 0562 } 0563 #endif 0564 return QByteArray(d->m_info->gtkApplicationId()); 0565 } 0566 0567 QByteArray KWindowInfo::applicationMenuServiceName() const 0568 { 0569 CHECK_X11 0570 #if !defined(KDE_NO_WARNING_OUTPUT) 0571 if (!(d->m_info->passedProperties2() & NET::WM2AppMenuObjectPath)) { 0572 qWarning() << "Pass NET::WM2AppMenuObjectPath to KWindowInfo"; 0573 } 0574 #endif 0575 return QByteArray(d->m_info->appMenuObjectPath()); 0576 } 0577 0578 QByteArray KWindowInfo::applicationMenuObjectPath() const 0579 { 0580 CHECK_X11 0581 #if !defined(KDE_NO_WARNING_OUTPUT) 0582 if (!(d->m_info->passedProperties2() & NET::WM2AppMenuServiceName)) { 0583 qWarning() << "Pass NET::WM2AppMenuServiceName to KWindowInfo"; 0584 } 0585 #endif 0586 return QByteArray(d->m_info->appMenuServiceName()); 0587 } 0588 0589 int KWindowInfo::pid() const 0590 { 0591 CHECK_X11 0592 // If pid is found using the XRes extension use that instead. 0593 // It is more reliable than the app reporting it's own PID as apps 0594 // within an app namespace are unable to do so correctly 0595 if (d->m_pid > 0) { 0596 return d->m_pid; 0597 } 0598 0599 #if !defined(KDE_NO_WARNING_OUTPUT) 0600 if (!(d->m_info->passedProperties() & NET::WMPid)) { 0601 qWarning() << "Pass NET::WMPid to KWindowInfo"; 0602 } 0603 #endif 0604 0605 return d->m_info->pid(); 0606 }