| 1 | /* |
| 2 | This file is part of the KDE libraries |
| 3 | SPDX-FileCopyrightText: 1999 Matthias Ettrich <ettrich@kde.org> |
| 4 | SPDX-FileCopyrightText: 2007 Lubos Lunak <l.lunak@kde.org> |
| 5 | SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org> |
| 6 | |
| 7 | SPDX-License-Identifier: LGPL-2.1-or-later |
| 8 | */ |
| 9 | |
| 10 | #include "kx11extras.h" |
| 11 | |
| 12 | // clang-format off |
| 13 | #include <kxerrorhandler_p.h> |
| 14 | #include <fixx11h.h> |
| 15 | #include <kxutils_p.h> |
| 16 | // clang-format on |
| 17 | |
| 18 | #include "cptr_p.h" |
| 19 | #include "kwindowsystem.h" |
| 20 | #include "kwindowsystem_debug.h" |
| 21 | #include "netwm.h" |
| 22 | #include "kxcbevent_p.h" |
| 23 | |
| 24 | #include <QAbstractNativeEventFilter> |
| 25 | #include <QGuiApplication> |
| 26 | #include <QMetaMethod> |
| 27 | #include <QRect> |
| 28 | #include <QScreen> |
| 29 | #include <private/qtx11extras_p.h> |
| 30 | |
| 31 | #include <X11/Xatom.h> |
| 32 | #include <X11/Xutil.h> |
| 33 | #include <X11/extensions/Xfixes.h> |
| 34 | #include <xcb/xcb.h> |
| 35 | #include <xcb/xfixes.h> |
| 36 | |
| 37 | // QPoint and QSize all have handy / operators which are useful for scaling, positions and sizes for high DPI support |
| 38 | // QRect does not, so we create one for internal purposes within this class |
| 39 | inline QRect operator/(const QRect &rectangle, qreal factor) |
| 40 | { |
| 41 | return QRect(rectangle.topLeft() / factor, rectangle.size() / factor); |
| 42 | } |
| 43 | |
| 44 | class MainThreadInstantiator : public QObject |
| 45 | { |
| 46 | Q_OBJECT |
| 47 | |
| 48 | public: |
| 49 | MainThreadInstantiator(KX11Extras::FilterInfo _what); |
| 50 | Q_INVOKABLE NETEventFilter *createNETEventFilter(); |
| 51 | |
| 52 | private: |
| 53 | KX11Extras::FilterInfo m_what; |
| 54 | }; |
| 55 | |
| 56 | class NETEventFilter : public NETRootInfo, public QAbstractNativeEventFilter |
| 57 | { |
| 58 | public: |
| 59 | NETEventFilter(KX11Extras::FilterInfo _what); |
| 60 | ~NETEventFilter() override; |
| 61 | void activate(); |
| 62 | QList<WId> windows; |
| 63 | QList<WId> stackingOrder; |
| 64 | |
| 65 | struct StrutData { |
| 66 | StrutData(WId window_, const NETStrut &strut_, int desktop_) |
| 67 | : window(window_) |
| 68 | , strut(strut_) |
| 69 | , desktop(desktop_) |
| 70 | { |
| 71 | } |
| 72 | WId window; |
| 73 | NETStrut strut; |
| 74 | int desktop; |
| 75 | }; |
| 76 | QList<StrutData> strutWindows; |
| 77 | QList<WId> possibleStrutWindows; |
| 78 | bool strutSignalConnected; |
| 79 | bool compositingEnabled; |
| 80 | bool haveXfixes; |
| 81 | KX11Extras::FilterInfo what; |
| 82 | int xfixesEventBase; |
| 83 | bool mapViewport(); |
| 84 | |
| 85 | bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override; |
| 86 | |
| 87 | void updateStackingOrder(); |
| 88 | bool removeStrutWindow(WId); |
| 89 | |
| 90 | protected: |
| 91 | void addClient(xcb_window_t) override; |
| 92 | void removeClient(xcb_window_t) override; |
| 93 | |
| 94 | private: |
| 95 | bool nativeEventFilter(xcb_generic_event_t *event); |
| 96 | xcb_window_t winId; |
| 97 | xcb_window_t m_appRootWindow; |
| 98 | }; |
| 99 | |
| 100 | static Atom net_wm_cm; |
| 101 | static void create_atoms(); |
| 102 | |
| 103 | static inline const QRect &displayGeometry() |
| 104 | { |
| 105 | static QRect displayGeometry; |
| 106 | static bool isDirty = true; |
| 107 | |
| 108 | if (isDirty) { |
| 109 | static QList<QMetaObject::Connection> connections; |
| 110 | auto dirtify = [&] { |
| 111 | isDirty = true; |
| 112 | for (const QMetaObject::Connection &con : std::as_const(t&: connections)) { |
| 113 | QObject::disconnect(con); |
| 114 | } |
| 115 | connections.clear(); |
| 116 | }; |
| 117 | |
| 118 | QObject::connect(qApp, signal: &QGuiApplication::screenAdded, slot&: dirtify); |
| 119 | QObject::connect(qApp, signal: &QGuiApplication::screenRemoved, slot&: dirtify); |
| 120 | const QList<QScreen *> screenList = QGuiApplication::screens(); |
| 121 | QRegion region; |
| 122 | for (int i = 0; i < screenList.count(); ++i) { |
| 123 | const QScreen *screen = screenList.at(i); |
| 124 | connections << QObject::connect(sender: screen, signal: &QScreen::geometryChanged, slot&: dirtify); |
| 125 | const QRect geometry = screen->geometry(); |
| 126 | const qreal dpr = screen->devicePixelRatio(); |
| 127 | region += QRect(geometry.topLeft(), geometry.size() * dpr); |
| 128 | } |
| 129 | displayGeometry = region.boundingRect(); |
| 130 | isDirty = false; |
| 131 | } |
| 132 | |
| 133 | return displayGeometry; |
| 134 | } |
| 135 | |
| 136 | static inline int displayWidth() |
| 137 | { |
| 138 | return displayGeometry().width(); |
| 139 | } |
| 140 | |
| 141 | static inline int displayHeight() |
| 142 | { |
| 143 | return displayGeometry().height(); |
| 144 | } |
| 145 | |
| 146 | // clang-format off |
| 147 | static const NET::Properties windowsProperties = NET::ClientList | NET::ClientListStacking | |
| 148 | NET::Supported | |
| 149 | NET::NumberOfDesktops | |
| 150 | NET::DesktopGeometry | |
| 151 | NET::DesktopViewport | |
| 152 | NET::CurrentDesktop | |
| 153 | NET::DesktopNames | |
| 154 | NET::ActiveWindow | |
| 155 | NET::WorkArea; |
| 156 | static const NET::Properties2 windowsProperties2 = NET::WM2ShowingDesktop; |
| 157 | |
| 158 | // ClientList and ClientListStacking is not per-window information, but a desktop information, |
| 159 | // so track it even with only INFO_BASIC |
| 160 | static const NET::Properties desktopProperties = NET::ClientList | NET::ClientListStacking | |
| 161 | NET::Supported | |
| 162 | NET::NumberOfDesktops | |
| 163 | NET::DesktopGeometry | |
| 164 | NET::DesktopViewport | |
| 165 | NET::CurrentDesktop | |
| 166 | NET::DesktopNames | |
| 167 | NET::ActiveWindow | |
| 168 | NET::WorkArea; |
| 169 | static const NET::Properties2 desktopProperties2 = NET::WM2ShowingDesktop; |
| 170 | // clang-format on |
| 171 | |
| 172 | MainThreadInstantiator::MainThreadInstantiator(KX11Extras::FilterInfo _what) |
| 173 | : QObject() |
| 174 | , m_what(_what) |
| 175 | { |
| 176 | } |
| 177 | |
| 178 | NETEventFilter *MainThreadInstantiator::createNETEventFilter() |
| 179 | { |
| 180 | return new NETEventFilter(m_what); |
| 181 | } |
| 182 | |
| 183 | NETEventFilter::(KX11Extras::FilterInfo _what) |
| 184 | : NETRootInfo(QX11Info::connection(), |
| 185 | _what >= KX11Extras::INFO_WINDOWS ? windowsProperties : desktopProperties, |
| 186 | _what >= KX11Extras::INFO_WINDOWS ? windowsProperties2 : desktopProperties2, |
| 187 | QX11Info::appScreen(), |
| 188 | false) |
| 189 | , QAbstractNativeEventFilter() |
| 190 | , strutSignalConnected(false) |
| 191 | , compositingEnabled(false) |
| 192 | , haveXfixes(false) |
| 193 | , what(_what) |
| 194 | , winId(XCB_WINDOW_NONE) |
| 195 | , m_appRootWindow(QX11Info::appRootWindow()) |
| 196 | { |
| 197 | QCoreApplication::instance()->installNativeEventFilter(filterObj: this); |
| 198 | |
| 199 | int errorBase; |
| 200 | if ((haveXfixes = XFixesQueryExtension(dpy: QX11Info::display(), event_base_return: &xfixesEventBase, error_base_return: &errorBase))) { |
| 201 | create_atoms(); |
| 202 | winId = xcb_generate_id(c: QX11Info::connection()); |
| 203 | uint32_t values[] = {true, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY}; |
| 204 | xcb_create_window(c: QX11Info::connection(), |
| 205 | XCB_COPY_FROM_PARENT, |
| 206 | wid: winId, |
| 207 | parent: m_appRootWindow, |
| 208 | x: 0, |
| 209 | y: 0, |
| 210 | width: 1, |
| 211 | height: 1, |
| 212 | border_width: 0, |
| 213 | class: XCB_WINDOW_CLASS_INPUT_ONLY, |
| 214 | XCB_COPY_FROM_PARENT, |
| 215 | value_mask: XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK, |
| 216 | value_list: values); |
| 217 | XFixesSelectSelectionInput(dpy: QX11Info::display(), |
| 218 | win: winId, |
| 219 | selection: net_wm_cm, |
| 220 | XFixesSetSelectionOwnerNotifyMask | XFixesSelectionWindowDestroyNotifyMask | XFixesSelectionClientCloseNotifyMask); |
| 221 | compositingEnabled = XGetSelectionOwner(QX11Info::display(), net_wm_cm) != None; |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | NETEventFilter::~NETEventFilter() |
| 226 | { |
| 227 | if (QX11Info::connection() && winId != XCB_WINDOW_NONE) { |
| 228 | xcb_destroy_window(c: QX11Info::connection(), window: winId); |
| 229 | winId = XCB_WINDOW_NONE; |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | // not virtual, but it's called directly only from init() |
| 234 | void NETEventFilter::activate() |
| 235 | { |
| 236 | NETRootInfo::activate(); |
| 237 | updateStackingOrder(); |
| 238 | } |
| 239 | |
| 240 | bool NETEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) |
| 241 | { |
| 242 | if (eventType != "xcb_generic_event_t" ) { |
| 243 | // only interested in XCB events of course |
| 244 | return false; |
| 245 | } |
| 246 | return nativeEventFilter(event: reinterpret_cast<xcb_generic_event_t *>(message)); |
| 247 | } |
| 248 | |
| 249 | bool NETEventFilter::nativeEventFilter(xcb_generic_event_t *ev) |
| 250 | { |
| 251 | KWindowSystem *s_q = KWindowSystem::self(); |
| 252 | const uint8_t eventType = ev->response_type & ~0x80; |
| 253 | |
| 254 | if (eventType == xfixesEventBase + XCB_XFIXES_SELECTION_NOTIFY) { |
| 255 | xcb_xfixes_selection_notify_event_t *event = reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(ev); |
| 256 | if (event->window == winId) { |
| 257 | bool haveOwner = event->owner != XCB_WINDOW_NONE; |
| 258 | if (compositingEnabled != haveOwner) { |
| 259 | compositingEnabled = haveOwner; |
| 260 | Q_EMIT KX11Extras::self()->compositingChanged(enabled: compositingEnabled); |
| 261 | } |
| 262 | return true; |
| 263 | } |
| 264 | // Qt compresses XFixesSelectionNotifyEvents without caring about the actual window |
| 265 | // gui/kernel/qapplication_x11.cpp |
| 266 | // until that can be assumed fixed, we also react on events on the root (caused by Qts own compositing tracker) |
| 267 | if (event->window == m_appRootWindow) { |
| 268 | if (event->selection == net_wm_cm) { |
| 269 | bool haveOwner = event->owner != XCB_WINDOW_NONE; |
| 270 | if (compositingEnabled != haveOwner) { |
| 271 | compositingEnabled = haveOwner; |
| 272 | Q_EMIT KX11Extras::self()->compositingChanged(enabled: compositingEnabled); |
| 273 | } |
| 274 | // NOTICE this is not our event, we just randomly captured it from Qt -> pass on |
| 275 | return false; |
| 276 | } |
| 277 | } |
| 278 | return false; |
| 279 | } |
| 280 | |
| 281 | xcb_window_t eventWindow = XCB_WINDOW_NONE; |
| 282 | switch (eventType) { |
| 283 | case XCB_CLIENT_MESSAGE: |
| 284 | eventWindow = reinterpret_cast<xcb_client_message_event_t *>(ev)->window; |
| 285 | break; |
| 286 | case XCB_PROPERTY_NOTIFY: |
| 287 | eventWindow = reinterpret_cast<xcb_property_notify_event_t *>(ev)->window; |
| 288 | break; |
| 289 | case XCB_CONFIGURE_NOTIFY: |
| 290 | eventWindow = reinterpret_cast<xcb_configure_notify_event_t *>(ev)->window; |
| 291 | break; |
| 292 | } |
| 293 | |
| 294 | if (eventWindow == m_appRootWindow) { |
| 295 | int old_current_desktop = currentDesktop(); |
| 296 | xcb_window_t old_active_window = activeWindow(); |
| 297 | int old_number_of_desktops = numberOfDesktops(); |
| 298 | bool old_showing_desktop = showingDesktop(); |
| 299 | NET::Properties props; |
| 300 | NET::Properties2 props2; |
| 301 | NETRootInfo::event(event: ev, properties: &props, properties2: &props2); |
| 302 | |
| 303 | if ((props & CurrentDesktop) && currentDesktop() != old_current_desktop) { |
| 304 | Q_EMIT KX11Extras::self()->currentDesktopChanged(desktop: currentDesktop()); |
| 305 | } |
| 306 | if ((props & DesktopViewport) && mapViewport() && currentDesktop() != old_current_desktop) { |
| 307 | Q_EMIT KX11Extras::self()->currentDesktopChanged(desktop: currentDesktop()); |
| 308 | } |
| 309 | if ((props & ActiveWindow) && activeWindow() != old_active_window) { |
| 310 | Q_EMIT KX11Extras::self()->activeWindowChanged(id: activeWindow()); |
| 311 | } |
| 312 | if (props & DesktopNames) { |
| 313 | Q_EMIT KX11Extras::self()->desktopNamesChanged(); |
| 314 | } |
| 315 | if ((props & NumberOfDesktops) && numberOfDesktops() != old_number_of_desktops) { |
| 316 | Q_EMIT KX11Extras::self()->numberOfDesktopsChanged(num: numberOfDesktops()); |
| 317 | } |
| 318 | if ((props & DesktopGeometry) && mapViewport() && numberOfDesktops() != old_number_of_desktops) { |
| 319 | Q_EMIT KX11Extras::self()->numberOfDesktopsChanged(num: numberOfDesktops()); |
| 320 | } |
| 321 | if (props & WorkArea) { |
| 322 | Q_EMIT KX11Extras::self()->workAreaChanged(); |
| 323 | } |
| 324 | if (props & ClientListStacking) { |
| 325 | updateStackingOrder(); |
| 326 | Q_EMIT KX11Extras::self()->stackingOrderChanged(); |
| 327 | } |
| 328 | if ((props2 & WM2ShowingDesktop) && showingDesktop() != old_showing_desktop) { |
| 329 | Q_EMIT s_q->showingDesktopChanged(showing: showingDesktop()); |
| 330 | } |
| 331 | } else if (windows.contains(t: eventWindow)) { |
| 332 | NETWinInfo ni(QX11Info::connection(), eventWindow, m_appRootWindow, NET::Properties(), NET::Properties2()); |
| 333 | NET::Properties dirtyProperties; |
| 334 | NET::Properties2 dirtyProperties2; |
| 335 | ni.event(event: ev, properties: &dirtyProperties, properties2: &dirtyProperties2); |
| 336 | if (eventType == XCB_PROPERTY_NOTIFY) { |
| 337 | xcb_property_notify_event_t *event = reinterpret_cast<xcb_property_notify_event_t *>(ev); |
| 338 | if (event->atom == XCB_ATOM_WM_HINTS) { |
| 339 | dirtyProperties |= NET::WMIcon; // support for old icons |
| 340 | } else if (event->atom == XCB_ATOM_WM_NAME) { |
| 341 | dirtyProperties |= NET::WMName; // support for old name |
| 342 | } else if (event->atom == XCB_ATOM_WM_ICON_NAME) { |
| 343 | dirtyProperties |= NET::WMIconName; // support for old iconic name |
| 344 | } |
| 345 | } |
| 346 | if (mapViewport() && (dirtyProperties & (NET::WMState | NET::WMGeometry))) { |
| 347 | /* geometry change -> possible viewport change |
| 348 | * state change -> possible NET::Sticky change |
| 349 | */ |
| 350 | dirtyProperties |= NET::WMDesktop; |
| 351 | } |
| 352 | if ((dirtyProperties & NET::WMStrut) != 0) { |
| 353 | removeStrutWindow(eventWindow); |
| 354 | if (!possibleStrutWindows.contains(t: eventWindow)) { |
| 355 | possibleStrutWindows.append(t: eventWindow); |
| 356 | } |
| 357 | } |
| 358 | if (dirtyProperties || dirtyProperties2) { |
| 359 | Q_EMIT KX11Extras::self()->windowChanged(id: eventWindow, properties: dirtyProperties, properties2: dirtyProperties2); |
| 360 | |
| 361 | if ((dirtyProperties & NET::WMStrut) != 0) { |
| 362 | Q_EMIT KX11Extras::self()->strutChanged(); |
| 363 | } |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | return false; |
| 368 | } |
| 369 | |
| 370 | bool NETEventFilter::removeStrutWindow(WId w) |
| 371 | { |
| 372 | for (QList<StrutData>::Iterator it = strutWindows.begin(); it != strutWindows.end(); ++it) { |
| 373 | if ((*it).window == w) { |
| 374 | strutWindows.erase(pos: it); |
| 375 | return true; |
| 376 | } |
| 377 | } |
| 378 | return false; |
| 379 | } |
| 380 | |
| 381 | void NETEventFilter::updateStackingOrder() |
| 382 | { |
| 383 | stackingOrder.clear(); |
| 384 | for (int i = 0; i < clientListStackingCount(); i++) { |
| 385 | stackingOrder.append(t: clientListStacking()[i]); |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | void NETEventFilter::addClient(xcb_window_t w) |
| 390 | { |
| 391 | if ((what >= KX11Extras::INFO_WINDOWS)) { |
| 392 | xcb_connection_t *c = QX11Info::connection(); |
| 393 | UniqueCPointer<xcb_get_window_attributes_reply_t> attr(xcb_get_window_attributes_reply(c, cookie: xcb_get_window_attributes_unchecked(c, window: w), e: nullptr)); |
| 394 | |
| 395 | uint32_t events = XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY; |
| 396 | if (attr) { |
| 397 | events = events | attr->your_event_mask; |
| 398 | } |
| 399 | xcb_change_window_attributes(c, window: w, value_mask: XCB_CW_EVENT_MASK, value_list: &events); |
| 400 | } |
| 401 | |
| 402 | bool emit_strutChanged = false; |
| 403 | |
| 404 | if (strutSignalConnected) { |
| 405 | NETWinInfo info(QX11Info::connection(), w, QX11Info::appRootWindow(), NET::WMStrut | NET::WMDesktop, NET::Properties2()); |
| 406 | NETStrut strut = info.strut(); |
| 407 | if (strut.left || strut.top || strut.right || strut.bottom) { |
| 408 | strutWindows.append(t: StrutData(w, strut, info.desktop())); |
| 409 | emit_strutChanged = true; |
| 410 | } |
| 411 | } else { |
| 412 | possibleStrutWindows.append(t: w); |
| 413 | } |
| 414 | |
| 415 | windows.append(t: w); |
| 416 | Q_EMIT KX11Extras::self()->windowAdded(id: w); |
| 417 | if (emit_strutChanged) { |
| 418 | Q_EMIT KX11Extras::self()->strutChanged(); |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | void NETEventFilter::removeClient(xcb_window_t w) |
| 423 | { |
| 424 | bool emit_strutChanged = removeStrutWindow(w); |
| 425 | if (strutSignalConnected && possibleStrutWindows.contains(t: w)) { |
| 426 | NETWinInfo info(QX11Info::connection(), w, QX11Info::appRootWindow(), NET::WMStrut, NET::Properties2()); |
| 427 | NETStrut strut = info.strut(); |
| 428 | if (strut.left || strut.top || strut.right || strut.bottom) { |
| 429 | emit_strutChanged = true; |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | possibleStrutWindows.removeAll(t: w); |
| 434 | windows.removeAll(t: w); |
| 435 | Q_EMIT KX11Extras::self()->windowRemoved(id: w); |
| 436 | if (emit_strutChanged) { |
| 437 | Q_EMIT KX11Extras::self()->strutChanged(); |
| 438 | } |
| 439 | } |
| 440 | |
| 441 | bool NETEventFilter::mapViewport() |
| 442 | { |
| 443 | // compiz claims support even though it doesn't use virtual desktops :( |
| 444 | // if( isSupported( NET::DesktopViewport ) && !isSupported( NET::NumberOfDesktops )) |
| 445 | |
| 446 | // this test is duplicated in KWindowSystem::mapViewport() |
| 447 | if (isSupported(property: NET::DesktopViewport) && numberOfDesktops(ignore_viewport: true) <= 1 |
| 448 | && (desktopGeometry().width > displayWidth() || desktopGeometry().height > displayHeight())) { |
| 449 | return true; |
| 450 | } |
| 451 | return false; |
| 452 | } |
| 453 | |
| 454 | static bool atoms_created = false; |
| 455 | |
| 456 | static Atom _wm_protocols; |
| 457 | static Atom _wm_change_state; |
| 458 | static Atom kwm_utf8_string; |
| 459 | |
| 460 | static void create_atoms() |
| 461 | { |
| 462 | if (!atoms_created) { |
| 463 | const int max = 20; |
| 464 | Atom *atoms[max]; |
| 465 | const char *names[max]; |
| 466 | Atom atoms_return[max]; |
| 467 | int n = 0; |
| 468 | |
| 469 | atoms[n] = &_wm_protocols; |
| 470 | names[n++] = "WM_PROTOCOLS" ; |
| 471 | |
| 472 | atoms[n] = &_wm_change_state; |
| 473 | names[n++] = "WM_CHANGE_STATE" ; |
| 474 | |
| 475 | atoms[n] = &kwm_utf8_string; |
| 476 | names[n++] = "UTF8_STRING" ; |
| 477 | |
| 478 | char net_wm_cm_name[100]; |
| 479 | sprintf(s: net_wm_cm_name, format: "_NET_WM_CM_S%d" , QX11Info::appScreen()); |
| 480 | atoms[n] = &net_wm_cm; |
| 481 | names[n++] = net_wm_cm_name; |
| 482 | |
| 483 | // we need a const_cast for the shitty X API |
| 484 | XInternAtoms(QX11Info::display(), const_cast<char **>(names), n, false, atoms_return); |
| 485 | for (int i = 0; i < n; i++) { |
| 486 | *atoms[i] = atoms_return[i]; |
| 487 | } |
| 488 | |
| 489 | atoms_created = True; |
| 490 | } |
| 491 | } |
| 492 | |
| 493 | #define CHECK_X11 \ |
| 494 | if (!KWindowSystem::isPlatformX11()) { \ |
| 495 | qCWarning(LOG_KWINDOWSYSTEM) << Q_FUNC_INFO << "may only be used on X11"; \ |
| 496 | return {}; \ |
| 497 | } |
| 498 | |
| 499 | #define CHECK_X11_VOID \ |
| 500 | if (!KWindowSystem::isPlatformX11()) { \ |
| 501 | qCWarning(LOG_KWINDOWSYSTEM) << Q_FUNC_INFO << "may only be used on X11"; \ |
| 502 | return; \ |
| 503 | } |
| 504 | |
| 505 | // WARNING |
| 506 | // you have to call s_d_func() again after calling this function if you want a valid pointer! |
| 507 | void KX11Extras::(FilterInfo what) |
| 508 | { |
| 509 | NETEventFilter *const s_d = s_d_func(); |
| 510 | |
| 511 | if (what >= INFO_WINDOWS) { |
| 512 | what = INFO_WINDOWS; |
| 513 | } else { |
| 514 | what = INFO_BASIC; |
| 515 | } |
| 516 | |
| 517 | if (!s_d || s_d->what < what) { |
| 518 | const bool wasCompositing = s_d ? s_d->compositingEnabled : false; |
| 519 | MainThreadInstantiator instantiator(what); |
| 520 | NETEventFilter *filter; |
| 521 | if (instantiator.thread() == QCoreApplication::instance()->thread()) { |
| 522 | filter = instantiator.createNETEventFilter(); |
| 523 | } else { |
| 524 | // the instantiator is not in the main app thread, which implies |
| 525 | // we are being called in a thread that is not the main app thread |
| 526 | // so we move the instantiator to the main app thread and invoke |
| 527 | // the method with a blocking call |
| 528 | instantiator.moveToThread(thread: QCoreApplication::instance()->thread()); |
| 529 | QMetaObject::invokeMethod(obj: &instantiator, member: "createNETEventFilter" , c: Qt::BlockingQueuedConnection, Q_RETURN_ARG(NETEventFilter *, filter)); |
| 530 | } |
| 531 | d.reset(p: filter); |
| 532 | d->activate(); |
| 533 | if (wasCompositing != s_d_func()->compositingEnabled) { |
| 534 | Q_EMIT KX11Extras::self()->compositingChanged(enabled: s_d_func()->compositingEnabled); |
| 535 | } |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | KX11Extras *KX11Extras::() |
| 540 | { |
| 541 | static KX11Extras instance; |
| 542 | return &instance; |
| 543 | } |
| 544 | |
| 545 | QList<WId> KX11Extras::() |
| 546 | { |
| 547 | CHECK_X11 |
| 548 | KX11Extras::self()->init(what: INFO_BASIC); |
| 549 | return KX11Extras::self()->s_d_func()->windows; |
| 550 | } |
| 551 | |
| 552 | bool KX11Extras::(WId w) |
| 553 | { |
| 554 | CHECK_X11 |
| 555 | return windows().contains(t: w); |
| 556 | } |
| 557 | |
| 558 | QList<WId> KX11Extras::() |
| 559 | { |
| 560 | CHECK_X11 |
| 561 | KX11Extras::self()->init(what: INFO_BASIC); |
| 562 | return KX11Extras::self()->s_d_func()->stackingOrder; |
| 563 | } |
| 564 | |
| 565 | WId KX11Extras::() |
| 566 | { |
| 567 | CHECK_X11 |
| 568 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 569 | if (s_d) { |
| 570 | return s_d->activeWindow(); |
| 571 | } |
| 572 | NETRootInfo info(QX11Info::connection(), NET::ActiveWindow, NET::Properties2(), QX11Info::appScreen()); |
| 573 | return info.activeWindow(); |
| 574 | } |
| 575 | |
| 576 | void KX11Extras::(WId win, long time) |
| 577 | { |
| 578 | CHECK_X11_VOID |
| 579 | NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen()); |
| 580 | if (time == 0) { |
| 581 | time = QX11Info::appUserTime(); |
| 582 | } |
| 583 | info.setActiveWindow(window: win, src: NET::FromApplication, timestamp: time, active_window: QGuiApplication::focusWindow() ? QGuiApplication::focusWindow()->winId() : 0); |
| 584 | } |
| 585 | |
| 586 | void KX11Extras::(WId win, long time) |
| 587 | { |
| 588 | CHECK_X11_VOID |
| 589 | NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen()); |
| 590 | if (time == 0) { |
| 591 | time = QX11Info::appTime(); |
| 592 | } |
| 593 | info.setActiveWindow(window: win, src: NET::FromTool, timestamp: time, active_window: 0); |
| 594 | } |
| 595 | |
| 596 | void KX11Extras::(QWindow *win, long time) |
| 597 | { |
| 598 | CHECK_X11_VOID |
| 599 | forceActiveWindow(win: win->winId(), time); |
| 600 | } |
| 601 | |
| 602 | bool KX11Extras::() |
| 603 | { |
| 604 | CHECK_X11 |
| 605 | KX11Extras::self()->init(what: INFO_BASIC); |
| 606 | if (KX11Extras::self()->s_d_func()->haveXfixes) { |
| 607 | return KX11Extras::self()->s_d_func()->compositingEnabled; |
| 608 | } else { |
| 609 | create_atoms(); |
| 610 | return XGetSelectionOwner(QX11Info::display(), net_wm_cm); |
| 611 | } |
| 612 | } |
| 613 | |
| 614 | int KX11Extras::() |
| 615 | { |
| 616 | CHECK_X11 |
| 617 | if (!QX11Info::connection()) { |
| 618 | return 1; |
| 619 | } |
| 620 | |
| 621 | if (mapViewport()) { |
| 622 | KX11Extras::self()->init(what: INFO_BASIC); |
| 623 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 624 | NETPoint p = s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true)); |
| 625 | return KX11Extras::self()->viewportToDesktop(pos: QPoint(p.x, p.y) / qApp->devicePixelRatio()); |
| 626 | } |
| 627 | |
| 628 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 629 | if (s_d) { |
| 630 | return s_d->currentDesktop(ignore_viewport: true); |
| 631 | } |
| 632 | NETRootInfo info(QX11Info::connection(), NET::CurrentDesktop, NET::Properties2(), QX11Info::appScreen()); |
| 633 | return info.currentDesktop(ignore_viewport: true); |
| 634 | } |
| 635 | |
| 636 | int KX11Extras::() |
| 637 | { |
| 638 | CHECK_X11 |
| 639 | if (!QX11Info::connection()) { |
| 640 | return 1; |
| 641 | } |
| 642 | |
| 643 | if (mapViewport()) { |
| 644 | KX11Extras::self()->init(what: INFO_BASIC); |
| 645 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 646 | NETSize s = s_d->desktopGeometry(); |
| 647 | return s.width / displayWidth() * s.height / displayHeight(); |
| 648 | } |
| 649 | |
| 650 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 651 | if (s_d) { |
| 652 | return s_d->numberOfDesktops(ignore_viewport: true); |
| 653 | } |
| 654 | NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops, NET::Properties2(), QX11Info::appScreen()); |
| 655 | return info.numberOfDesktops(ignore_viewport: true); |
| 656 | } |
| 657 | |
| 658 | void KX11Extras::(int desktop) |
| 659 | { |
| 660 | CHECK_X11_VOID |
| 661 | if (mapViewport()) { |
| 662 | KX11Extras::self()->init(what: INFO_BASIC); |
| 663 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 664 | NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen()); |
| 665 | QPoint pos = KX11Extras::self()->desktopToViewport(desktop, absolute: true); |
| 666 | NETPoint p; |
| 667 | p.x = pos.x(); |
| 668 | p.y = pos.y(); |
| 669 | info.setDesktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true), viewport: p); |
| 670 | return; |
| 671 | } |
| 672 | NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen()); |
| 673 | info.setCurrentDesktop(desktop, ignore_viewport: true); |
| 674 | } |
| 675 | |
| 676 | void KX11Extras::(WId win, bool b) |
| 677 | { |
| 678 | CHECK_X11_VOID |
| 679 | if (mapViewport()) { |
| 680 | if (b) { |
| 681 | setState(win, state: NET::Sticky); |
| 682 | } else { |
| 683 | clearState(win, state: NET::Sticky); |
| 684 | } |
| 685 | return; |
| 686 | } |
| 687 | NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMDesktop, NET::Properties2()); |
| 688 | if (b) { |
| 689 | info.setDesktop(desktop: NETWinInfo::OnAllDesktops, ignore_viewport: true); |
| 690 | } else if (info.desktop(ignore_viewport: true) == NETWinInfo::OnAllDesktops) { |
| 691 | NETRootInfo rinfo(QX11Info::connection(), NET::CurrentDesktop, NET::Properties2(), QX11Info::appScreen()); |
| 692 | info.setDesktop(desktop: rinfo.currentDesktop(ignore_viewport: true), ignore_viewport: true); |
| 693 | } |
| 694 | } |
| 695 | |
| 696 | void KX11Extras::(WId win, int desktop) |
| 697 | { |
| 698 | CHECK_X11_VOID |
| 699 | if (mapViewport()) { |
| 700 | if (desktop == NET::OnAllDesktops) { |
| 701 | return setOnAllDesktops(win, b: true); |
| 702 | } else { |
| 703 | clearState(win, state: NET::Sticky); |
| 704 | } |
| 705 | KX11Extras::self()->init(what: INFO_BASIC); |
| 706 | QPoint p = KX11Extras::self()->desktopToViewport(desktop, absolute: false); |
| 707 | Window dummy; |
| 708 | int x; |
| 709 | int y; |
| 710 | unsigned int w; |
| 711 | unsigned int h; |
| 712 | unsigned int b; |
| 713 | unsigned int dp; |
| 714 | XGetGeometry(QX11Info::display(), win, &dummy, &x, &y, &w, &h, &b, &dp); |
| 715 | // get global position |
| 716 | XTranslateCoordinates(QX11Info::display(), win, QX11Info::appRootWindow(), 0, 0, &x, &y, &dummy); |
| 717 | x += w / 2; // center |
| 718 | y += h / 2; |
| 719 | // transform to coordinates on the current "desktop" |
| 720 | x = x % displayWidth(); |
| 721 | y = y % displayHeight(); |
| 722 | if (x < 0) { |
| 723 | x = x + displayWidth(); |
| 724 | } |
| 725 | if (y < 0) { |
| 726 | y = y + displayHeight(); |
| 727 | } |
| 728 | x += p.x(); // move to given "desktop" |
| 729 | y += p.y(); |
| 730 | x -= w / 2; // from center back to topleft |
| 731 | y -= h / 2; |
| 732 | p = KX11Extras::self()->constrainViewportRelativePosition(pos: QPoint(x, y)); |
| 733 | int flags = (NET::FromTool << 12) | (0x03 << 8) | 10; // from tool(?), x/y, static gravity |
| 734 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 735 | s_d->moveResizeWindowRequest(window: win, flags, x: p.x(), y: p.y(), width: w, height: h); |
| 736 | return; |
| 737 | } |
| 738 | NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMDesktop, NET::Properties2()); |
| 739 | info.setDesktop(desktop, ignore_viewport: true); |
| 740 | } |
| 741 | |
| 742 | void KX11Extras::(WId win, const QStringList &activities) |
| 743 | { |
| 744 | CHECK_X11_VOID |
| 745 | NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::WM2Activities); |
| 746 | info.setActivities(activities.join(sep: QLatin1Char(',')).toLatin1().constData()); |
| 747 | } |
| 748 | |
| 749 | QPixmap KX11Extras::(WId win, int width, int height, bool scale) |
| 750 | { |
| 751 | CHECK_X11 |
| 752 | return icon(win, width, height, scale, flags: NETWM | WMHints | ClassHint | XApp); |
| 753 | } |
| 754 | |
| 755 | QPixmap iconFromNetWinInfo(int width, int height, bool scale, int flags, NETWinInfo *info) |
| 756 | { |
| 757 | QPixmap result; |
| 758 | if (!info) { |
| 759 | return result; |
| 760 | } |
| 761 | if (flags & KX11Extras::NETWM) { |
| 762 | NETIcon ni = info->icon(width, height); |
| 763 | if (ni.data && ni.size.width > 0 && ni.size.height > 0) { |
| 764 | QImage img((uchar *)ni.data, (int)ni.size.width, (int)ni.size.height, QImage::Format_ARGB32); |
| 765 | if (scale && width > 0 && height > 0 && img.size() != QSize(width, height) && !img.isNull()) { |
| 766 | img = img.scaled(w: width, h: height, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation); |
| 767 | } |
| 768 | if (!img.isNull()) { |
| 769 | result = QPixmap::fromImage(image: img); |
| 770 | } |
| 771 | return result; |
| 772 | } |
| 773 | } |
| 774 | |
| 775 | if (flags & KX11Extras::WMHints) { |
| 776 | xcb_pixmap_t p = info->icccmIconPixmap(); |
| 777 | xcb_pixmap_t p_mask = info->icccmIconPixmapMask(); |
| 778 | |
| 779 | if (p != XCB_PIXMAP_NONE) { |
| 780 | QPixmap pm = KXUtils::createPixmapFromHandle(c: info->xcbConnection(), pixmap: p, mask: p_mask); |
| 781 | if (scale && width > 0 && height > 0 && !pm.isNull() // |
| 782 | && (pm.width() != width || pm.height() != height)) { |
| 783 | result = QPixmap::fromImage(image: pm.toImage().scaled(w: width, h: height, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation)); |
| 784 | } else { |
| 785 | result = pm; |
| 786 | } |
| 787 | } |
| 788 | } |
| 789 | |
| 790 | // Since width can be any arbitrary size, but the icons cannot, |
| 791 | // take the nearest value for best results (ignoring 22 pixel |
| 792 | // icons as they don't exist for apps): |
| 793 | int iconWidth; |
| 794 | if (width < 24) { |
| 795 | iconWidth = 16; |
| 796 | } else if (width < 40) { |
| 797 | iconWidth = 32; |
| 798 | } else if (width < 56) { |
| 799 | iconWidth = 48; |
| 800 | } else if (width < 96) { |
| 801 | iconWidth = 64; |
| 802 | } else if (width < 192) { |
| 803 | iconWidth = 128; |
| 804 | } else { |
| 805 | iconWidth = 256; |
| 806 | } |
| 807 | |
| 808 | if (flags & KX11Extras::ClassHint) { |
| 809 | // Try to load the icon from the classhint if the app didn't specify |
| 810 | // its own: |
| 811 | if (result.isNull()) { |
| 812 | const QIcon icon = QIcon::fromTheme(name: QString::fromUtf8(utf8: info->windowClassClass()).toLower()); |
| 813 | const QPixmap pm = icon.isNull() ? QPixmap() : icon.pixmap(w: iconWidth, h: iconWidth); |
| 814 | if (scale && !pm.isNull()) { |
| 815 | result = QPixmap::fromImage(image: pm.toImage().scaled(w: width, h: height, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation)); |
| 816 | } else { |
| 817 | result = pm; |
| 818 | } |
| 819 | } |
| 820 | } |
| 821 | |
| 822 | if (flags & KX11Extras::XApp) { |
| 823 | // If the icon is still a null pixmap, load the icon for X applications |
| 824 | // as a last resort: |
| 825 | if (result.isNull()) { |
| 826 | const QIcon icon = QIcon::fromTheme(QStringLiteral("xorg" )); |
| 827 | const QPixmap pm = icon.isNull() ? QPixmap() : icon.pixmap(w: iconWidth, h: iconWidth); |
| 828 | if (scale && !pm.isNull()) { |
| 829 | result = QPixmap::fromImage(image: pm.toImage().scaled(w: width, h: height, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation)); |
| 830 | } else { |
| 831 | result = pm; |
| 832 | } |
| 833 | } |
| 834 | } |
| 835 | return result; |
| 836 | } |
| 837 | |
| 838 | QPixmap KX11Extras::(WId win, int width, int height, bool scale, int flags) |
| 839 | { |
| 840 | CHECK_X11 |
| 841 | NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMIcon, NET::WM2WindowClass | NET::WM2IconPixmap); |
| 842 | return iconFromNetWinInfo(width, height, scale, flags, info: &info); |
| 843 | } |
| 844 | |
| 845 | QPixmap KX11Extras::(WId win, int width, int height, bool scale, int flags, NETWinInfo *info) |
| 846 | { |
| 847 | // No CHECK_X11 here, kwin_wayland calls this to get the icon for XWayland windows |
| 848 | width *= qGuiApp->devicePixelRatio(); |
| 849 | height *= qGuiApp->devicePixelRatio(); |
| 850 | |
| 851 | if (info) { |
| 852 | return iconFromNetWinInfo(width, height, scale, flags, info); |
| 853 | } |
| 854 | CHECK_X11 |
| 855 | |
| 856 | NETWinInfo newInfo(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMIcon, NET::WM2WindowClass | NET::WM2IconPixmap); |
| 857 | |
| 858 | return iconFromNetWinInfo(width, height, scale, flags, info: &newInfo); |
| 859 | } |
| 860 | |
| 861 | // enum values for ICCCM 4.1.2.4 and 4.1.4, defined to not depend on xcb-icccm |
| 862 | enum { |
| 863 | _ICCCM_WM_STATE_WITHDRAWN = 0, |
| 864 | _ICCCM_WM_STATE_NORMAL = 1, |
| 865 | _ICCCM_WM_STATE_ICONIC = 3, |
| 866 | }; |
| 867 | |
| 868 | void KX11Extras::(WId win) |
| 869 | { |
| 870 | CHECK_X11_VOID |
| 871 | create_atoms(); |
| 872 | // as described in ICCCM 4.1.4 |
| 873 | KXcbEvent<xcb_client_message_event_t> ev; |
| 874 | ev.response_type = XCB_CLIENT_MESSAGE; |
| 875 | ev.window = win; |
| 876 | ev.type = _wm_change_state; |
| 877 | ev.format = 32; |
| 878 | ev.data.data32[0] = _ICCCM_WM_STATE_ICONIC; |
| 879 | ev.data.data32[1] = 0; |
| 880 | ev.data.data32[2] = 0; |
| 881 | ev.data.data32[3] = 0; |
| 882 | ev.data.data32[4] = 0; |
| 883 | xcb_send_event(c: QX11Info::connection(), |
| 884 | propagate: false, |
| 885 | destination: QX11Info::appRootWindow(), |
| 886 | event_mask: XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, |
| 887 | event: ev.buffer()); |
| 888 | } |
| 889 | |
| 890 | void KX11Extras::(WId win) |
| 891 | { |
| 892 | CHECK_X11_VOID |
| 893 | xcb_map_window(c: QX11Info::connection(), window: win); |
| 894 | } |
| 895 | |
| 896 | QRect KX11Extras::(int desktop) |
| 897 | { |
| 898 | CHECK_X11 |
| 899 | KX11Extras::self()->init(what: INFO_BASIC); |
| 900 | int desk = (desktop > 0 && desktop <= (int)KX11Extras::self()->s_d_func()->numberOfDesktops()) ? desktop : currentDesktop(); |
| 901 | if (desk <= 0) { |
| 902 | return displayGeometry() / qApp->devicePixelRatio(); |
| 903 | } |
| 904 | |
| 905 | NETRect r = KX11Extras::self()->s_d_func()->workArea(desktop: desk); |
| 906 | if (r.size.width <= 0 || r.size.height <= 0) { // not set |
| 907 | return displayGeometry() / qApp->devicePixelRatio(); |
| 908 | } |
| 909 | |
| 910 | return QRect(r.pos.x, r.pos.y, r.size.width, r.size.height) / qApp->devicePixelRatio(); |
| 911 | } |
| 912 | |
| 913 | QRect KX11Extras::(const QList<WId> &exclude, int desktop) |
| 914 | { |
| 915 | CHECK_X11 |
| 916 | KX11Extras::self()->init(what: INFO_WINDOWS); // invalidates s_d_func's return value |
| 917 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 918 | |
| 919 | QRect all = displayGeometry(); |
| 920 | QRect a = all; |
| 921 | |
| 922 | if (desktop == -1) { |
| 923 | desktop = s_d->currentDesktop(); |
| 924 | } |
| 925 | |
| 926 | QList<WId>::ConstIterator it1; |
| 927 | for (it1 = s_d->windows.constBegin(); it1 != s_d->windows.constEnd(); ++it1) { |
| 928 | if (exclude.contains(t: *it1)) { |
| 929 | continue; |
| 930 | } |
| 931 | |
| 932 | // Kicker (very) extensively calls this function, causing hundreds of roundtrips just |
| 933 | // to repeatedly find out struts of all windows. Therefore strut values for strut |
| 934 | // windows are cached here. |
| 935 | NETStrut strut; |
| 936 | auto it2 = s_d->strutWindows.begin(); |
| 937 | for (; it2 != s_d->strutWindows.end(); ++it2) { |
| 938 | if ((*it2).window == *it1) { |
| 939 | break; |
| 940 | } |
| 941 | } |
| 942 | |
| 943 | if (it2 != s_d->strutWindows.end()) { |
| 944 | if (!((*it2).desktop == desktop || (*it2).desktop == NETWinInfo::OnAllDesktops)) { |
| 945 | continue; |
| 946 | } |
| 947 | |
| 948 | strut = (*it2).strut; |
| 949 | } else if (s_d->possibleStrutWindows.contains(t: *it1)) { |
| 950 | NETWinInfo info(QX11Info::connection(), (*it1), QX11Info::appRootWindow(), NET::WMStrut | NET::WMDesktop, NET::Properties2()); |
| 951 | strut = info.strut(); |
| 952 | s_d->possibleStrutWindows.removeAll(t: *it1); |
| 953 | s_d->strutWindows.append(t: NETEventFilter::StrutData(*it1, info.strut(), info.desktop())); |
| 954 | |
| 955 | if (!(info.desktop() == desktop || info.desktop() == NETWinInfo::OnAllDesktops)) { |
| 956 | continue; |
| 957 | } |
| 958 | } else { |
| 959 | continue; // not a strut window |
| 960 | } |
| 961 | |
| 962 | QRect r = all; |
| 963 | if (strut.left > 0) { |
| 964 | r.setLeft(r.left() + (int)strut.left); |
| 965 | } |
| 966 | if (strut.top > 0) { |
| 967 | r.setTop(r.top() + (int)strut.top); |
| 968 | } |
| 969 | if (strut.right > 0) { |
| 970 | r.setRight(r.right() - (int)strut.right); |
| 971 | } |
| 972 | if (strut.bottom > 0) { |
| 973 | r.setBottom(r.bottom() - (int)strut.bottom); |
| 974 | } |
| 975 | |
| 976 | a = a.intersected(other: r); |
| 977 | } |
| 978 | return a / qApp->devicePixelRatio(); |
| 979 | } |
| 980 | |
| 981 | QString KX11Extras::(int desktop) |
| 982 | { |
| 983 | CHECK_X11 |
| 984 | KX11Extras::self()->init(what: INFO_BASIC); |
| 985 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 986 | |
| 987 | bool isDesktopSane = (desktop > 0 && desktop <= (int)s_d->numberOfDesktops()); |
| 988 | const char *name = s_d->desktopName(desktop: isDesktopSane ? desktop : currentDesktop()); |
| 989 | |
| 990 | if (name && name[0]) { |
| 991 | return QString::fromUtf8(utf8: name); |
| 992 | } |
| 993 | |
| 994 | return KWindowSystem::tr(s: "Desktop %1" ).arg(a: desktop); |
| 995 | } |
| 996 | |
| 997 | void KX11Extras::(int desktop, const QString &name) |
| 998 | { |
| 999 | CHECK_X11_VOID |
| 1000 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 1001 | |
| 1002 | if (desktop <= 0 || desktop > (int)numberOfDesktops()) { |
| 1003 | desktop = currentDesktop(); |
| 1004 | } |
| 1005 | |
| 1006 | if (s_d) { |
| 1007 | s_d->setDesktopName(desktop, desktopName: name.toUtf8().constData()); |
| 1008 | return; |
| 1009 | } |
| 1010 | |
| 1011 | NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::Properties2(), QX11Info::appScreen()); |
| 1012 | info.setDesktopName(desktop, desktopName: name.toUtf8().constData()); |
| 1013 | } |
| 1014 | |
| 1015 | QString KX11Extras::(WId win, unsigned long atom) |
| 1016 | { |
| 1017 | CHECK_X11 |
| 1018 | XTextProperty tp; |
| 1019 | char **text = nullptr; |
| 1020 | int count; |
| 1021 | QString result; |
| 1022 | if (XGetTextProperty(QX11Info::display(), win, &tp, atom) != 0 && tp.value != nullptr) { |
| 1023 | create_atoms(); |
| 1024 | |
| 1025 | if (tp.encoding == kwm_utf8_string) { |
| 1026 | result = QString::fromUtf8(utf8: (const char *)tp.value); |
| 1027 | } else if (XmbTextPropertyToTextList(display: QX11Info::display(), text_prop: &tp, list_return: &text, count_return: &count) == Success && text != nullptr && count > 0) { |
| 1028 | result = QString::fromLocal8Bit(ba: text[0]); |
| 1029 | } else if (tp.encoding == XA_STRING) { |
| 1030 | result = QString::fromLocal8Bit(ba: (const char *)tp.value); |
| 1031 | } |
| 1032 | if (text != nullptr) { |
| 1033 | XFreeStringList(text); |
| 1034 | } |
| 1035 | XFree(tp.value); |
| 1036 | } |
| 1037 | return result; |
| 1038 | } |
| 1039 | |
| 1040 | bool KX11Extras::() |
| 1041 | { |
| 1042 | CHECK_X11 |
| 1043 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 1044 | if (s_d) { |
| 1045 | return s_d->mapViewport(); |
| 1046 | } |
| 1047 | |
| 1048 | // Handle case of not having a QGuiApplication |
| 1049 | if (!QX11Info::connection()) { |
| 1050 | return false; |
| 1051 | } |
| 1052 | |
| 1053 | // avoid creating KWindowSystemPrivate |
| 1054 | NETRootInfo infos(QX11Info::connection(), NET::Supported, NET::Properties2(), QX11Info::appScreen()); |
| 1055 | if (!infos.isSupported(property: NET::DesktopViewport)) { |
| 1056 | return false; |
| 1057 | } |
| 1058 | NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::CurrentDesktop | NET::DesktopGeometry, NET::Properties2(), QX11Info::appScreen()); |
| 1059 | if (info.numberOfDesktops(ignore_viewport: true) <= 1 && (info.desktopGeometry().width > displayWidth() || info.desktopGeometry().height > displayHeight())) { |
| 1060 | return true; |
| 1061 | } |
| 1062 | return false; |
| 1063 | } |
| 1064 | |
| 1065 | int KX11Extras::(const QRect &rect) |
| 1066 | { |
| 1067 | CHECK_X11 |
| 1068 | const QRect r = rect / qApp->devicePixelRatio(); |
| 1069 | |
| 1070 | KX11Extras::self()->init(what: INFO_BASIC); |
| 1071 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 1072 | QPoint p = r.center(); |
| 1073 | // make absolute |
| 1074 | p = QPoint(p.x() + s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true)).x, p.y() + s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true)).y); |
| 1075 | NETSize s = s_d->desktopGeometry(); |
| 1076 | QSize vs(displayWidth(), displayHeight()); |
| 1077 | int xs = s.width / vs.width(); |
| 1078 | int x = p.x() < 0 ? 0 : p.x() >= s.width ? xs - 1 : p.x() / vs.width(); |
| 1079 | int ys = s.height / vs.height(); |
| 1080 | int y = p.y() < 0 ? 0 : p.y() >= s.height ? ys - 1 : p.y() / vs.height(); |
| 1081 | return y * xs + x + 1; |
| 1082 | } |
| 1083 | |
| 1084 | void KX11Extras::(WId win, |
| 1085 | qreal left_width, |
| 1086 | qreal left_start, |
| 1087 | qreal left_end, |
| 1088 | qreal right_width, |
| 1089 | qreal right_start, |
| 1090 | qreal right_end, |
| 1091 | qreal top_width, |
| 1092 | qreal top_start, |
| 1093 | qreal top_end, |
| 1094 | qreal bottom_width, |
| 1095 | qreal bottom_start, |
| 1096 | qreal bottom_end) |
| 1097 | { |
| 1098 | CHECK_X11_VOID |
| 1099 | const qreal dpr = qApp->devicePixelRatio(); |
| 1100 | |
| 1101 | NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); |
| 1102 | NETExtendedStrut strut; |
| 1103 | strut.left_width = std::lround(x: left_width * dpr); |
| 1104 | strut.right_width = std::lround(x: right_width * dpr); |
| 1105 | strut.top_width = std::lround(x: top_width * dpr); |
| 1106 | strut.bottom_width = std::lround(x: bottom_width * dpr); |
| 1107 | strut.left_start = std::lround(x: left_start * dpr); |
| 1108 | strut.left_end = std::lround(x: left_end * dpr); |
| 1109 | strut.right_start = std::lround(x: right_start * dpr); |
| 1110 | strut.right_end = std::lround(x: right_end * dpr); |
| 1111 | strut.top_start = std::lround(x: top_start * dpr); |
| 1112 | strut.top_end = std::lround(x: top_end * dpr); |
| 1113 | strut.bottom_start = std::lround(x: bottom_start * dpr); |
| 1114 | strut.bottom_end = std::lround(x: bottom_end * dpr); |
| 1115 | info.setExtendedStrut(strut); |
| 1116 | NETStrut oldstrut; |
| 1117 | oldstrut.left = std::lround(x: left_width * dpr); |
| 1118 | oldstrut.right = std::lround(x: right_width * dpr); |
| 1119 | oldstrut.top = std::lround(x: top_width * dpr); |
| 1120 | oldstrut.bottom = std::lround(x: bottom_width * dpr); |
| 1121 | info.setStrut(oldstrut); |
| 1122 | } |
| 1123 | |
| 1124 | void KX11Extras::(WId win, qreal left, qreal right, qreal top, qreal bottom) |
| 1125 | { |
| 1126 | CHECK_X11_VOID |
| 1127 | const qreal dpr = qApp->devicePixelRatio(); |
| 1128 | |
| 1129 | int w = displayWidth(); |
| 1130 | int h = displayHeight(); |
| 1131 | setExtendedStrut(win, |
| 1132 | left_width: std::lround(x: left * dpr), |
| 1133 | left_start: 0, |
| 1134 | left_end: std::lround(x: left * dpr) != 0 ? w : 0, |
| 1135 | right_width: std::lround(x: right * dpr), |
| 1136 | right_start: 0, |
| 1137 | right_end: std::lround(x: right * dpr) != 0 ? w : 0, |
| 1138 | top_width: std::lround(x: top * dpr), |
| 1139 | top_start: 0, |
| 1140 | top_end: std::lround(x: top * dpr) != 0 ? h : 0, |
| 1141 | bottom_width: std::lround(x: bottom * dpr), |
| 1142 | bottom_start: 0, |
| 1143 | bottom_end: std::lround(x: bottom * dpr) != 0 ? h : 0); |
| 1144 | } |
| 1145 | |
| 1146 | // optimalization - create private only when needed and only for what is needed |
| 1147 | void KX11Extras::(const QMetaMethod &signal) |
| 1148 | { |
| 1149 | CHECK_X11_VOID |
| 1150 | FilterInfo what = INFO_BASIC; |
| 1151 | if (signal == QMetaMethod::fromSignal(signal: &KX11Extras::workAreaChanged)) { |
| 1152 | what = INFO_WINDOWS; |
| 1153 | } else if (signal == QMetaMethod::fromSignal(signal: &KX11Extras::strutChanged)) { |
| 1154 | what = INFO_WINDOWS; |
| 1155 | } else if (signal == QMetaMethod::fromSignal(signal: &KX11Extras::windowChanged)) { |
| 1156 | what = INFO_WINDOWS; |
| 1157 | } |
| 1158 | |
| 1159 | init(what); |
| 1160 | NETEventFilter *const s_d = s_d_func(); |
| 1161 | if (!s_d->strutSignalConnected && signal == QMetaMethod::fromSignal(signal: &KX11Extras::strutChanged)) { |
| 1162 | s_d->strutSignalConnected = true; |
| 1163 | } |
| 1164 | QObject::connectNotify(signal); |
| 1165 | } |
| 1166 | |
| 1167 | void KX11Extras::(WId win, NET::WindowType windowType) |
| 1168 | { |
| 1169 | CHECK_X11_VOID |
| 1170 | NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); |
| 1171 | info.setWindowType(windowType); |
| 1172 | } |
| 1173 | |
| 1174 | void KX11Extras::(WId win, NET::States state) |
| 1175 | { |
| 1176 | CHECK_X11_VOID |
| 1177 | NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); |
| 1178 | info.setState(state, mask: state); |
| 1179 | } |
| 1180 | |
| 1181 | void KX11Extras::(WId win, NET::States state) |
| 1182 | { |
| 1183 | CHECK_X11_VOID |
| 1184 | NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); |
| 1185 | info.setState(state: NET::States(), mask: state); |
| 1186 | } |
| 1187 | |
| 1188 | int KX11Extras::(const QPoint &p) |
| 1189 | { |
| 1190 | CHECK_X11 |
| 1191 | KX11Extras::self()->init(what: INFO_BASIC); |
| 1192 | NETEventFilter *const s_d = KX11Extras::self()->s_d_func(); |
| 1193 | NETSize s = s_d->desktopGeometry(); |
| 1194 | QSize vs(displayWidth(), displayHeight()); |
| 1195 | int xs = s.width / vs.width(); |
| 1196 | int x = p.x() < 0 ? 0 : p.x() >= s.width ? xs - 1 : p.x() / vs.width(); |
| 1197 | int ys = s.height / vs.height(); |
| 1198 | int y = p.y() < 0 ? 0 : p.y() >= s.height ? ys - 1 : p.y() / vs.height(); |
| 1199 | return y * xs + x + 1; |
| 1200 | } |
| 1201 | |
| 1202 | QPoint KX11Extras::(const QPoint &pos) |
| 1203 | { |
| 1204 | CHECK_X11 |
| 1205 | init(what: INFO_BASIC); |
| 1206 | NETEventFilter *const s_d = s_d_func(); |
| 1207 | NETSize s = s_d->desktopGeometry(); |
| 1208 | NETPoint c = s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true)); |
| 1209 | int x = (pos.x() + c.x) % s.width; |
| 1210 | int y = (pos.y() + c.y) % s.height; |
| 1211 | if (x < 0) { |
| 1212 | x += s.width; |
| 1213 | } |
| 1214 | if (y < 0) { |
| 1215 | y += s.height; |
| 1216 | } |
| 1217 | return QPoint(x - c.x, y - c.y); |
| 1218 | } |
| 1219 | |
| 1220 | QPoint KX11Extras::(int desktop, bool absolute) |
| 1221 | { |
| 1222 | CHECK_X11 |
| 1223 | init(what: INFO_BASIC); |
| 1224 | NETEventFilter *const s_d = s_d_func(); |
| 1225 | NETSize s = s_d->desktopGeometry(); |
| 1226 | QSize vs(displayWidth(), displayHeight()); |
| 1227 | int xs = s.width / vs.width(); |
| 1228 | int ys = s.height / vs.height(); |
| 1229 | if (desktop <= 0 || desktop > xs * ys) { |
| 1230 | return QPoint(0, 0); |
| 1231 | } |
| 1232 | --desktop; |
| 1233 | QPoint ret(vs.width() * (desktop % xs), vs.height() * (desktop / xs)); |
| 1234 | if (!absolute) { |
| 1235 | ret = QPoint(ret.x() - s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true)).x, ret.y() - s_d->desktopViewport(desktop: s_d->currentDesktop(ignore_viewport: true)).y); |
| 1236 | if (ret.x() >= s.width) { |
| 1237 | ret.setX(ret.x() - s.width); |
| 1238 | } |
| 1239 | if (ret.x() < 0) { |
| 1240 | ret.setX(ret.x() + s.width); |
| 1241 | } |
| 1242 | if (ret.y() >= s.height) { |
| 1243 | ret.setY(ret.y() - s.height); |
| 1244 | } |
| 1245 | if (ret.y() < 0) { |
| 1246 | ret.setY(ret.y() + s.height); |
| 1247 | } |
| 1248 | } |
| 1249 | return ret; |
| 1250 | } |
| 1251 | |
| 1252 | bool KX11Extras::() |
| 1253 | { |
| 1254 | KX11Extras::self()->init(what: INFO_BASIC); |
| 1255 | return KX11Extras::self()->s_d_func()->showingDesktop(); |
| 1256 | } |
| 1257 | |
| 1258 | void KX11Extras::(bool showing) |
| 1259 | { |
| 1260 | NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::WM2ShowingDesktop, QX11Info::appScreen()); |
| 1261 | info.setShowingDesktop(showing); |
| 1262 | } |
| 1263 | |
| 1264 | #include "kx11extras.moc" |
| 1265 | #include "moc_kx11extras.cpp" |
| 1266 | |