/* SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "kwindowinfo.h" #include "kwindowsystem.h" #include "kwindowsystem_debug.h" #include "kx11extras.h" #include "netwm.h" #include #include "private/qtx11extras_p.h" #include #include #include "kxerrorhandler_p.h" #include #include #include #include "cptr_p.h" static bool haveXRes() { static bool s_checked = false; static bool s_haveXRes = false; if (!s_checked) { auto cookie = xcb_res_query_version(QX11Info::connection(), XCB_RES_MAJOR_VERSION, XCB_RES_MINOR_VERSION); UniqueCPointer reply(xcb_res_query_version_reply(QX11Info::connection(), cookie, nullptr)); s_haveXRes = reply != nullptr; s_checked = true; } return s_haveXRes; } class Q_DECL_HIDDEN KWindowInfoPrivate : public QSharedData { public: WId window; NET::Properties properties; NET::Properties2 properties2; std::unique_ptr m_info; QString m_name; QString m_iconic_name; QRect m_geometry; QRect m_frame_geometry; int m_pid = -1; // real PID from XResources. Valid if > 0 bool m_valid = false; }; KWindowInfo::KWindowInfo(WId window, NET::Properties properties, NET::Properties2 properties2) : d(new KWindowInfoPrivate) { d->window = window; d->properties = properties; d->properties2 = properties2; if (!KWindowSystem::isPlatformX11()) { return; } KXErrorHandler handler; if (properties & NET::WMVisibleIconName) { properties |= NET::WMIconName | NET::WMVisibleName; // force, in case it will be used as a fallback } if (properties & NET::WMVisibleName) { properties |= NET::WMName; // force, in case it will be used as a fallback } if (properties2 & NET::WM2ExtendedStrut) { properties |= NET::WMStrut; // will be used as fallback } if (properties & NET::WMWindowType) { properties2 |= NET::WM2TransientFor; // will be used when type is not set } if ((properties & NET::WMDesktop) && KX11Extras::mapViewport()) { properties |= NET::WMGeometry; // for viewports, the desktop (workspace) is determined from the geometry } properties |= NET::XAWMState; // force to get error detection for valid() d->m_info.reset(new NETWinInfo(QX11Info::connection(), d->window, QX11Info::appRootWindow(), properties, properties2)); if (properties & NET::WMName) { if (d->m_info->name() && d->m_info->name()[0] != '\0') { d->m_name = QString::fromUtf8(d->m_info->name()); } else { d->m_name = KX11Extras::readNameProperty(d->window, XA_WM_NAME); } } if (properties & NET::WMIconName) { if (d->m_info->iconName() && d->m_info->iconName()[0] != '\0') { d->m_iconic_name = QString::fromUtf8(d->m_info->iconName()); } else { d->m_iconic_name = KX11Extras::readNameProperty(d->window, XA_WM_ICON_NAME); } } if (properties & (NET::WMGeometry | NET::WMFrameExtents)) { NETRect frame; NETRect geom; d->m_info->kdeGeometry(frame, geom); d->m_geometry.setRect(geom.pos.x, geom.pos.y, geom.size.width, geom.size.height); d->m_frame_geometry.setRect(frame.pos.x, frame.pos.y, frame.size.width, frame.size.height); } d->m_valid = !handler.error(false); // no sync - NETWinInfo did roundtrips if (haveXRes()) { xcb_res_client_id_spec_t specs; specs.client = win(); specs.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID; auto cookie = xcb_res_query_client_ids(QX11Info::connection(), 1, &specs); UniqueCPointer reply(xcb_res_query_client_ids_reply(QX11Info::connection(), cookie, nullptr)); if (reply && xcb_res_query_client_ids_ids_length(reply.get()) > 0) { uint32_t pid = *xcb_res_client_id_value_value((xcb_res_query_client_ids_ids_iterator(reply.get()).data)); d->m_pid = pid; } } } KWindowInfo::KWindowInfo(const KWindowInfo &other) : d(other.d) { } KWindowInfo::~KWindowInfo() { } KWindowInfo &KWindowInfo::operator=(const KWindowInfo &other) { if (d != other.d) { d = other.d; } return *this; } bool KWindowInfo::valid(bool withdrawn_is_valid) const { if (!KWindowSystem::isPlatformX11()) { return false; } if (!d->m_valid) { return false; } if (!withdrawn_is_valid && mappingState() == NET::Withdrawn) { return false; } return true; } WId KWindowInfo::win() const { return d->window; } #define CHECK_X11 \ if (!KWindowSystem::isPlatformX11()) { \ qCWarning(LOG_KWINDOWSYSTEM) << "KWindowInfo is only functional when running on X11"; \ return {}; \ } NET::States KWindowInfo::state() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMState)) { qWarning() << "Pass NET::WMState to KWindowInfo"; } #endif return d->m_info->state(); } bool KWindowInfo::hasState(NET::States s) const { CHECK_X11 return (state() & s) == s; } bool KWindowInfo::icccmCompliantMappingState() const { CHECK_X11 static enum { noidea, yes, no } wm_is_1_2_compliant = noidea; if (wm_is_1_2_compliant == noidea) { NETRootInfo info(QX11Info::connection(), NET::Supported, NET::Properties2(), QX11Info::appScreen()); wm_is_1_2_compliant = info.isSupported(NET::Hidden) ? yes : no; } return wm_is_1_2_compliant == yes; } // see NETWM spec section 7.6 bool KWindowInfo::isMinimized() const { CHECK_X11 if (mappingState() != NET::Iconic) { return false; } // NETWM 1.2 compliant WM - uses NET::Hidden for minimized windows if ((state() & NET::Hidden) != 0 && (state() & NET::Shaded) == 0) { // shaded may have NET::Hidden too return true; } // older WMs use WithdrawnState for other virtual desktops // and IconicState only for minimized return icccmCompliantMappingState() ? false : true; } NET::MappingState KWindowInfo::mappingState() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::XAWMState)) { qWarning() << "Pass NET::XAWMState to KWindowInfo"; } #endif return d->m_info->mappingState(); } NETExtendedStrut KWindowInfo::extendedStrut() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2ExtendedStrut)) { qWarning() << "Pass NET::WM2ExtendedStrut to KWindowInfo"; } #endif NETExtendedStrut ext = d->m_info->extendedStrut(); NETStrut str = d->m_info->strut(); if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) { // build extended from simple if (str.left != 0) { ext.left_width = str.left; ext.left_start = 0; ext.left_end = XDisplayHeight(QX11Info::display(), DefaultScreen(QX11Info::display())); } if (str.right != 0) { ext.right_width = str.right; ext.right_start = 0; ext.right_end = XDisplayHeight(QX11Info::display(), DefaultScreen(QX11Info::display())); } if (str.top != 0) { ext.top_width = str.top; ext.top_start = 0; ext.top_end = XDisplayWidth(QX11Info::display(), DefaultScreen(QX11Info::display())); } if (str.bottom != 0) { ext.bottom_width = str.bottom; ext.bottom_start = 0; ext.bottom_end = XDisplayWidth(QX11Info::display(), DefaultScreen(QX11Info::display())); } } return ext; } NET::WindowType KWindowInfo::windowType(NET::WindowTypes supported_types) const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMWindowType)) { qWarning() << "Pass NET::WMWindowType to KWindowInfo"; } #endif if (!d->m_info->hasWindowType()) { // fallback, per spec recommendation if (transientFor() != XCB_WINDOW_NONE) { // dialog if (supported_types & NET::DialogMask) { return NET::Dialog; } } else { if (supported_types & NET::NormalMask) { return NET::Normal; } } } return d->m_info->windowType(supported_types); } QString KWindowInfo::visibleName() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMVisibleName)) { qWarning() << "Pass NET::WMVisibleName to KWindowInfo"; } #endif return d->m_info->visibleName() && d->m_info->visibleName()[0] != '\0' ? QString::fromUtf8(d->m_info->visibleName()) : name(); } QString KWindowInfo::visibleNameWithState() const { CHECK_X11 QString s = visibleName(); if (isMinimized()) { s.prepend(QLatin1Char('(')); s.append(QLatin1Char(')')); } return s; } QString KWindowInfo::name() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMName)) { qWarning() << "Pass NET::WMName to KWindowInfo"; } #endif return d->m_name; } QString KWindowInfo::visibleIconName() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMVisibleIconName)) { qWarning() << "Pass NET::WMVisibleIconName to KWindowInfo"; } #endif if (d->m_info->visibleIconName() && d->m_info->visibleIconName()[0] != '\0') { return QString::fromUtf8(d->m_info->visibleIconName()); } if (d->m_info->iconName() && d->m_info->iconName()[0] != '\0') { return QString::fromUtf8(d->m_info->iconName()); } if (!d->m_iconic_name.isEmpty()) { return d->m_iconic_name; } return visibleName(); } QString KWindowInfo::visibleIconNameWithState() const { CHECK_X11 QString s = visibleIconName(); if (isMinimized()) { s.prepend(QLatin1Char('(')); s.append(QLatin1Char(')')); } return s; } QString KWindowInfo::iconName() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMIconName)) { qWarning() << "Pass NET::WMIconName to KWindowInfo"; } #endif if (d->m_info->iconName() && d->m_info->iconName()[0] != '\0') { return QString::fromUtf8(d->m_info->iconName()); } if (!d->m_iconic_name.isEmpty()) { return d->m_iconic_name; } return name(); } bool KWindowInfo::isOnCurrentDesktop() const { CHECK_X11 return isOnDesktop(KX11Extras::currentDesktop()); } bool KWindowInfo::isOnDesktop(int desktop) const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMDesktop)) { qWarning() << "Pass NET::WMDesktop to KWindowInfo"; } #endif if (KX11Extras::mapViewport()) { if (onAllDesktops()) { return true; } return KX11Extras::viewportWindowToDesktop(d->m_geometry) == desktop; } return d->m_info->desktop() == desktop || d->m_info->desktop() == NET::OnAllDesktops; } bool KWindowInfo::onAllDesktops() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMDesktop)) { qWarning() << "Pass NET::WMDesktop to KWindowInfo"; } #endif if (KX11Extras::mapViewport()) { if (d->m_info->passedProperties() & NET::WMState) { return d->m_info->state() & NET::Sticky; } NETWinInfo info(QX11Info::connection(), win(), QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); return info.state() & NET::Sticky; } return d->m_info->desktop() == NET::OnAllDesktops; } int KWindowInfo::desktop() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMDesktop)) { qWarning() << "Pass NET::WMDesktop to KWindowInfo"; } #endif if (KX11Extras::mapViewport()) { if (onAllDesktops()) { return NET::OnAllDesktops; } return KX11Extras::viewportWindowToDesktop(d->m_geometry); } return d->m_info->desktop(); } QStringList KWindowInfo::activities() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2Activities)) { qWarning() << "Pass NET::WM2Activities to KWindowInfo"; } #endif const QStringList result = QString::fromLatin1(d->m_info->activities()).split(QLatin1Char(','), Qt::SkipEmptyParts); return result.contains(QStringLiteral(KDE_ALL_ACTIVITIES_UUID)) ? QStringList() : result; } QRect KWindowInfo::geometry() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMGeometry)) { qWarning() << "Pass NET::WMGeometry to KWindowInfo"; } #endif return d->m_geometry; } QRect KWindowInfo::frameGeometry() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMFrameExtents)) { qWarning() << "Pass NET::WMFrameExtents to KWindowInfo"; } #endif return d->m_frame_geometry; } WId KWindowInfo::transientFor() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2TransientFor)) { qWarning() << "Pass NET::WM2TransientFor to KWindowInfo"; } #endif return d->m_info->transientFor(); } WId KWindowInfo::groupLeader() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2GroupLeader)) { qWarning() << "Pass NET::WM2GroupLeader to KWindowInfo"; } #endif return d->m_info->groupLeader(); } QByteArray KWindowInfo::windowClassClass() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2WindowClass)) { qWarning() << "Pass NET::WM2WindowClass to KWindowInfo"; } #endif return d->m_info->windowClassClass(); } QByteArray KWindowInfo::windowClassName() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2WindowClass)) { qWarning() << "Pass NET::WM2WindowClass to KWindowInfo"; } #endif return d->m_info->windowClassName(); } QByteArray KWindowInfo::windowRole() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2WindowRole)) { qWarning() << "Pass NET::WM2WindowRole to KWindowInfo"; } #endif return d->m_info->windowRole(); } QByteArray KWindowInfo::clientMachine() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2ClientMachine)) { qWarning() << "Pass NET::WM2ClientMachine to KWindowInfo"; } #endif return d->m_info->clientMachine(); } bool KWindowInfo::allowedActionsSupported() const { CHECK_X11 static enum { noidea, yes, no } wm_supports_allowed_actions = noidea; if (wm_supports_allowed_actions == noidea) { NETRootInfo info(QX11Info::connection(), NET::Supported, NET::Properties2(), QX11Info::appScreen()); wm_supports_allowed_actions = info.isSupported(NET::WM2AllowedActions) ? yes : no; } return wm_supports_allowed_actions == yes; } bool KWindowInfo::actionSupported(NET::Action action) const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2AllowedActions)) { qWarning() << "Pass NET::WM2AllowedActions to KWindowInfo"; } #endif if (allowedActionsSupported()) { return d->m_info->allowedActions() & action; } else { return true; // no idea if it's supported or not -> pretend it is } } QByteArray KWindowInfo::desktopFileName() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2DesktopFileName)) { qWarning() << "Pass NET::WM2DesktopFileName to KWindowInfo"; } #endif return QByteArray(d->m_info->desktopFileName()); } QByteArray KWindowInfo::gtkApplicationId() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2DesktopFileName)) { qWarning() << "Pass NET::WM2DesktopFileName to KWindowInfo"; } #endif return QByteArray(d->m_info->gtkApplicationId()); } QByteArray KWindowInfo::applicationMenuServiceName() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2AppMenuServiceName)) { qWarning() << "Pass NET::WM2AppMenuServiceName to KWindowInfo"; } #endif return QByteArray(d->m_info->appMenuServiceName()); } QByteArray KWindowInfo::applicationMenuObjectPath() const { CHECK_X11 #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties2() & NET::WM2AppMenuObjectPath)) { qWarning() << "Pass NET::WM2AppMenuObjectPath to KWindowInfo"; } #endif return QByteArray(d->m_info->appMenuObjectPath()); } int KWindowInfo::pid() const { CHECK_X11 // If pid is found using the XRes extension use that instead. // It is more reliable than the app reporting it's own PID as apps // within an app namespace are unable to do so correctly if (d->m_pid > 0) { return d->m_pid; } #if !defined(KDE_NO_WARNING_OUTPUT) if (!(d->m_info->passedProperties() & NET::WMPid)) { qWarning() << "Pass NET::WMPid to KWindowInfo"; } #endif return d->m_info->pid(); }