File indexing completed on 2024-04-21 03:59:28

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 }