File indexing completed on 2024-05-05 16:19:51

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