File indexing completed on 2025-04-20 08:03:33
0001 /* 0002 SPDX-FileCopyrightText: 2016 Smith AR <audoban@openmailbox.org> 0003 SPDX-FileCopyrightText: 2016 Michail Vourlakos <mvourlakos@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "xwindowinterface.h" 0009 0010 // local 0011 #include <coretypes.h> 0012 #include "tasktools.h" 0013 #include "view/view.h" 0014 #include "view/helpers/screenedgeghostwindow.h" 0015 0016 // Qt 0017 #include <QDebug> 0018 #include <QTimer> 0019 #include <QtX11Extras/QX11Info> 0020 0021 // KDE 0022 #include <KDesktopFile> 0023 #include <KWindowSystem> 0024 #include <KWindowInfo> 0025 #include <KIconThemes/KIconLoader> 0026 0027 // X11 0028 #include <NETWM> 0029 #include <xcb/xcb.h> 0030 #include <xcb/shape.h> 0031 0032 namespace Latte { 0033 namespace WindowSystem { 0034 0035 XWindowInterface::XWindowInterface(QObject *parent) 0036 : AbstractWindowInterface(parent) 0037 { 0038 m_currentDesktop = QString(KWindowSystem::self()->currentDesktop()); 0039 0040 connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AbstractWindowInterface::activeWindowChanged); 0041 connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &AbstractWindowInterface::windowRemoved); 0042 0043 connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &XWindowInterface::windowAddedProxy); 0044 0045 connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, [&](int desktop) { 0046 m_currentDesktop = QString(desktop); 0047 emit currentDesktopChanged(); 0048 }); 0049 0050 connect(KWindowSystem::self() 0051 , static_cast<void (KWindowSystem::*)(WId, NET::Properties, NET::Properties2)> 0052 (&KWindowSystem::windowChanged) 0053 , this, &XWindowInterface::windowChangedProxy); 0054 0055 0056 for(auto wid : KWindowSystem::self()->windows()) { 0057 windowAddedProxy(wid); 0058 } 0059 } 0060 0061 XWindowInterface::~XWindowInterface() 0062 { 0063 } 0064 0065 void XWindowInterface::setViewExtraFlags(QObject *view,bool isPanelWindow, Latte::Types::Visibility mode) 0066 { 0067 WId winId = -1; 0068 0069 QQuickView *quickView = qobject_cast<QQuickView *>(view); 0070 0071 if (quickView) { 0072 winId = quickView->winId(); 0073 } 0074 0075 if (!quickView) { 0076 QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(view); 0077 0078 if (quickWindow) { 0079 winId = quickWindow->winId(); 0080 } 0081 } 0082 0083 NETWinInfo winfo(QX11Info::connection() 0084 , static_cast<xcb_window_t>(winId) 0085 , static_cast<xcb_window_t>(winId) 0086 , 0, 0); 0087 0088 winfo.setAllowedActions(NET::ActionChangeDesktop); 0089 0090 if (isPanelWindow) { 0091 KWindowSystem::setType(winId, NET::Dock); 0092 } else { 0093 KWindowSystem::setType(winId, NET::Normal); 0094 } 0095 0096 KWindowSystem::setState(winId, NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher); 0097 KWindowSystem::setOnAllDesktops(winId, true); 0098 0099 //! Layer to be applied 0100 if (mode == Latte::Types::WindowsCanCover || mode == Latte::Types::WindowsAlwaysCover) { 0101 setKeepBelow(winId, true); 0102 } else if (mode == Latte::Types::NormalWindow) { 0103 setKeepBelow(winId, false); 0104 setKeepAbove(winId, false); 0105 } else { 0106 setKeepAbove(winId, true); 0107 } 0108 } 0109 0110 void XWindowInterface::setViewStruts(QWindow &view, const QRect &rect 0111 , Plasma::Types::Location location) 0112 { 0113 NETExtendedStrut strut; 0114 0115 const auto screen = view.screen(); 0116 0117 const QRect currentScreen {screen->geometry()}; 0118 const QRect wholeScreen {{0, 0}, screen->virtualSize()}; 0119 0120 switch (location) { 0121 case Plasma::Types::TopEdge: { 0122 const int topOffset {screen->geometry().top()}; 0123 strut.top_width = rect.height() + topOffset; 0124 strut.top_start = rect.x(); 0125 strut.top_end = rect.x() + rect.width() - 1; 0126 break; 0127 } 0128 0129 case Plasma::Types::BottomEdge: { 0130 const int bottomOffset {wholeScreen.bottom() - currentScreen.bottom()}; 0131 strut.bottom_width = rect.height() + bottomOffset; 0132 strut.bottom_start = rect.x(); 0133 strut.bottom_end = rect.x() + rect.width() - 1; 0134 break; 0135 } 0136 0137 case Plasma::Types::LeftEdge: { 0138 const int leftOffset = {screen->geometry().left()}; 0139 strut.left_width = rect.width() + leftOffset; 0140 strut.left_start = rect.y(); 0141 strut.left_end = rect.y() + rect.height() - 1; 0142 break; 0143 } 0144 0145 case Plasma::Types::RightEdge: { 0146 const int rightOffset = {wholeScreen.right() - currentScreen.right()}; 0147 strut.right_width = rect.width() + rightOffset; 0148 strut.right_start = rect.y(); 0149 strut.right_end = rect.y() + rect.height() - 1; 0150 break; 0151 } 0152 0153 default: 0154 qWarning() << "wrong location:" << location; 0155 return; 0156 } 0157 0158 KWindowSystem::setExtendedStrut(view.winId(), 0159 strut.left_width, strut.left_start, strut.left_end, 0160 strut.right_width, strut.right_start, strut.right_end, 0161 strut.top_width, strut.top_start, strut.top_end, 0162 strut.bottom_width, strut.bottom_start, strut.bottom_end 0163 ); 0164 } 0165 0166 void XWindowInterface::switchToNextVirtualDesktop() 0167 { 0168 int desktops = KWindowSystem::numberOfDesktops(); 0169 0170 if (desktops <= 1) { 0171 return; 0172 } 0173 0174 int curPos = KWindowSystem::currentDesktop(); 0175 int nextPos = curPos + 1; 0176 0177 if (curPos == desktops) { 0178 if (isVirtualDesktopNavigationWrappingAround()) { 0179 nextPos = 1; 0180 } else { 0181 return; 0182 } 0183 } 0184 0185 KWindowSystem::setCurrentDesktop(nextPos); 0186 } 0187 0188 void XWindowInterface::switchToPreviousVirtualDesktop() 0189 { 0190 int desktops = KWindowSystem::numberOfDesktops(); 0191 if (desktops <= 1) { 0192 return; 0193 } 0194 0195 int curPos = KWindowSystem::currentDesktop(); 0196 int nextPos = curPos - 1; 0197 0198 if (curPos == 1) { 0199 if (isVirtualDesktopNavigationWrappingAround()) { 0200 nextPos = desktops; 0201 } else { 0202 return; 0203 } 0204 } 0205 0206 KWindowSystem::setCurrentDesktop(nextPos); 0207 } 0208 0209 void XWindowInterface::setWindowOnActivities(const WindowId &wid, const QStringList &activities) 0210 { 0211 KWindowSystem::setOnActivities(wid.toUInt(), activities); 0212 } 0213 0214 void XWindowInterface::removeViewStruts(QWindow &view) 0215 { 0216 KWindowSystem::setStrut(view.winId(), 0, 0, 0, 0); 0217 } 0218 0219 WindowId XWindowInterface::activeWindow() 0220 { 0221 return KWindowSystem::self()->activeWindow(); 0222 } 0223 0224 void XWindowInterface::skipTaskBar(const QDialog &dialog) 0225 { 0226 KWindowSystem::setState(dialog.winId(), NET::SkipTaskbar); 0227 } 0228 0229 void XWindowInterface::slideWindow(QWindow &view, AbstractWindowInterface::Slide location) 0230 { 0231 auto slideLocation = KWindowEffects::NoEdge; 0232 0233 switch (location) { 0234 case Slide::Top: 0235 slideLocation = KWindowEffects::TopEdge; 0236 break; 0237 0238 case Slide::Bottom: 0239 slideLocation = KWindowEffects::BottomEdge; 0240 break; 0241 0242 case Slide::Left: 0243 slideLocation = KWindowEffects::LeftEdge; 0244 break; 0245 0246 case Slide::Right: 0247 slideLocation = KWindowEffects::RightEdge; 0248 break; 0249 0250 default: 0251 break; 0252 } 0253 0254 KWindowEffects::slideWindow(view.winId(), slideLocation, -1); 0255 } 0256 0257 void XWindowInterface::enableBlurBehind(QWindow &view) 0258 { 0259 KWindowEffects::enableBlurBehind(view.winId()); 0260 } 0261 0262 void XWindowInterface::setActiveEdge(QWindow *view, bool active) 0263 { 0264 ViewPart::ScreenEdgeGhostWindow *window = qobject_cast<ViewPart::ScreenEdgeGhostWindow *>(view); 0265 0266 if (!window) { 0267 return; 0268 } 0269 0270 xcb_connection_t *c = QX11Info::connection(); 0271 0272 const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"); 0273 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); 0274 0275 QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); 0276 0277 if (!atom) { 0278 return; 0279 } 0280 0281 if (!active) { 0282 xcb_delete_property(c, window->winId(), atom->atom); 0283 window->hideWithMask(); 0284 return; 0285 } 0286 0287 window->showWithMask(); 0288 0289 uint32_t value = 0; 0290 0291 switch (window->location()) { 0292 case Plasma::Types::TopEdge: 0293 value = 0; 0294 break; 0295 0296 case Plasma::Types::RightEdge: 0297 value = 1; 0298 break; 0299 0300 case Plasma::Types::BottomEdge: 0301 value = 2; 0302 break; 0303 0304 case Plasma::Types::LeftEdge: 0305 value = 3; 0306 break; 0307 0308 case Plasma::Types::Floating: 0309 default: 0310 value = 4; 0311 break; 0312 } 0313 0314 int hideType = 0; 0315 0316 value |= hideType << 8; 0317 0318 xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value); 0319 } 0320 0321 QRect XWindowInterface::visibleGeometry(const WindowId &wid, const QRect &frameGeometry) const 0322 { 0323 NETWinInfo ni(QX11Info::connection(), wid.toUInt(), QX11Info::appRootWindow(), 0, NET::WM2GTKFrameExtents); 0324 NETStrut struts = ni.gtkFrameExtents(); 0325 QMargins margins(struts.left, struts.top, struts.right, struts.bottom); 0326 QRect visibleGeometry = frameGeometry; 0327 0328 if (!margins.isNull()) { 0329 visibleGeometry -= margins; 0330 } 0331 0332 return visibleGeometry; 0333 } 0334 0335 0336 0337 void XWindowInterface::setFrameExtents(QWindow *view, const QMargins &margins) 0338 { 0339 if (!view) { 0340 return; 0341 } 0342 0343 NETWinInfo ni(QX11Info::connection(), view->winId(), QX11Info::appRootWindow(), 0, NET::WM2GTKFrameExtents); 0344 0345 if (margins.isNull()) { 0346 //! delete property 0347 xcb_connection_t *c = QX11Info::connection(); 0348 const QByteArray atomName = QByteArrayLiteral("_GTK_FRAME_EXTENTS"); 0349 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, atomName.length(), atomName.constData()); 0350 QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); 0351 0352 if (!atom) { 0353 return; 0354 } 0355 0356 // qDebug() << " deleting gtk frame extents atom.."; 0357 0358 xcb_delete_property(c, view->winId(), atom->atom); 0359 } else { 0360 NETStrut struts; 0361 struts.left = margins.left(); 0362 struts.top = margins.top(); 0363 struts.right = margins.right(); 0364 struts.bottom = margins.bottom(); 0365 0366 ni.setGtkFrameExtents(struts); 0367 } 0368 0369 /*NETWinInfo ni2(QX11Info::connection(), view->winId(), QX11Info::appRootWindow(), 0, NET::WM2GTKFrameExtents); 0370 NETStrut applied = ni2.gtkFrameExtents(); 0371 QMargins amargins(applied.left, applied.top, applied.right, applied.bottom); 0372 qDebug() << " window gtk frame extents applied :: " << amargins;*/ 0373 0374 } 0375 0376 void XWindowInterface::checkShapeExtension() 0377 { 0378 if (!m_shapeExtensionChecked) { 0379 xcb_connection_t *c = QX11Info::connection(); 0380 xcb_prefetch_extension_data(c, &xcb_shape_id); 0381 const xcb_query_extension_reply_t *extension = xcb_get_extension_data(c, &xcb_shape_id); 0382 if (extension->present) { 0383 // query version 0384 auto cookie = xcb_shape_query_version(c); 0385 QScopedPointer<xcb_shape_query_version_reply_t, QScopedPointerPodDeleter> version(xcb_shape_query_version_reply(c, cookie, nullptr)); 0386 if (!version.isNull()) { 0387 m_shapeAvailable = (version->major_version * 0x10 + version->minor_version) >= 0x11; 0388 } 0389 } 0390 m_shapeExtensionChecked = true; 0391 } 0392 } 0393 0394 void XWindowInterface::setInputMask(QWindow *window, const QRect &rect) 0395 { 0396 if (!window || !window->isVisible()) { 0397 return; 0398 } 0399 0400 xcb_connection_t *c = QX11Info::connection(); 0401 0402 if (!m_shapeExtensionChecked) { 0403 checkShapeExtension(); 0404 } 0405 0406 if (!m_shapeAvailable) { 0407 return; 0408 } 0409 0410 if (!rect.isEmpty()) { 0411 xcb_rectangle_t xcbrect; 0412 xcbrect.x = qMax(SHRT_MIN, rect.x()); 0413 xcbrect.y = qMax(SHRT_MIN, rect.y()); 0414 xcbrect.width = qMin((int)USHRT_MAX, rect.width()); 0415 xcbrect.height = qMin((int)USHRT_MAX, rect.height()); 0416 0417 // set input shape, so that it doesn't accept any input events 0418 xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, 0419 XCB_CLIP_ORDERING_UNSORTED, window->winId(), 0, 0, 1, &xcbrect); 0420 } else { 0421 // delete the shape 0422 xcb_shape_mask(c, XCB_SHAPE_SO_INTERSECT, XCB_SHAPE_SK_INPUT, 0423 window->winId(), 0, 0, XCB_PIXMAP_NONE); 0424 } 0425 } 0426 0427 WindowInfoWrap XWindowInterface::requestInfoActive() 0428 { 0429 return requestInfo(KWindowSystem::activeWindow()); 0430 } 0431 0432 WindowInfoWrap XWindowInterface::requestInfo(WindowId wid) 0433 { 0434 const KWindowInfo winfo{wid.value<WId>(), NET::WMFrameExtents 0435 | NET::WMWindowType 0436 | NET::WMGeometry 0437 | NET::WMDesktop 0438 | NET::WMState 0439 | NET::WMName 0440 | NET::WMVisibleName, 0441 NET::WM2WindowClass 0442 | NET::WM2Activities 0443 | NET::WM2AllowedActions 0444 | NET::WM2TransientFor}; 0445 0446 WindowInfoWrap winfoWrap; 0447 0448 const auto winClass = QString(winfo.windowClassName()); 0449 0450 //!used to track Plasma DesktopView windows because during startup can not be identified properly 0451 bool plasmaBlockedWindow = (winClass == QLatin1String("plasmashell") && !isAcceptableWindow(wid)); 0452 0453 if (!winfo.valid() || plasmaBlockedWindow) { 0454 winfoWrap.setIsValid(false); 0455 } else if (isValidWindow(wid)) { 0456 winfoWrap.setIsValid(true); 0457 winfoWrap.setWid(wid); 0458 winfoWrap.setParentId(winfo.transientFor()); 0459 winfoWrap.setIsActive(KWindowSystem::activeWindow() == wid.value<WId>()); 0460 winfoWrap.setIsMinimized(winfo.hasState(NET::Hidden)); 0461 winfoWrap.setIsMaxVert(winfo.hasState(NET::MaxVert)); 0462 winfoWrap.setIsMaxHoriz(winfo.hasState(NET::MaxHoriz)); 0463 winfoWrap.setIsFullscreen(winfo.hasState(NET::FullScreen)); 0464 winfoWrap.setIsShaded(winfo.hasState(NET::Shaded)); 0465 winfoWrap.setIsOnAllDesktops(winfo.onAllDesktops()); 0466 winfoWrap.setIsOnAllActivities(winfo.activities().empty()); 0467 winfoWrap.setGeometry(visibleGeometry(wid, winfo.frameGeometry())); 0468 winfoWrap.setIsKeepAbove(winfo.hasState(NET::KeepAbove)); 0469 winfoWrap.setIsKeepBelow(winfo.hasState(NET::KeepBelow)); 0470 winfoWrap.setHasSkipPager(winfo.hasState(NET::SkipPager)); 0471 winfoWrap.setHasSkipSwitcher(winfo.hasState(NET::SkipSwitcher)); 0472 winfoWrap.setHasSkipTaskbar(winfo.hasState(NET::SkipTaskbar)); 0473 0474 //! BEGIN:Window Abilities 0475 winfoWrap.setIsClosable(winfo.actionSupported(NET::ActionClose)); 0476 winfoWrap.setIsFullScreenable(winfo.actionSupported(NET::ActionFullScreen)); 0477 winfoWrap.setIsMaximizable(winfo.actionSupported(NET::ActionMax)); 0478 winfoWrap.setIsMinimizable(winfo.actionSupported(NET::ActionMinimize)); 0479 winfoWrap.setIsMovable(winfo.actionSupported(NET::ActionMove)); 0480 winfoWrap.setIsResizable(winfo.actionSupported(NET::ActionResize)); 0481 winfoWrap.setIsShadeable(winfo.actionSupported(NET::ActionShade)); 0482 winfoWrap.setIsVirtualDesktopsChangeable(winfo.actionSupported(NET::ActionChangeDesktop)); 0483 //! END:Window Abilities 0484 0485 winfoWrap.setDisplay(winfo.visibleName()); 0486 winfoWrap.setDesktops({QString(winfo.desktop())}); 0487 winfoWrap.setActivities(winfo.activities()); 0488 } 0489 0490 if (plasmaBlockedWindow) { 0491 windowRemoved(wid); 0492 } 0493 0494 return winfoWrap; 0495 } 0496 0497 AppData XWindowInterface::appDataFor(WindowId wid) 0498 { 0499 return appDataFromUrl(windowUrl(wid)); 0500 } 0501 0502 QUrl XWindowInterface::windowUrl(WindowId wid) 0503 { 0504 const KWindowInfo info(wid.value<WId>(), 0, NET::WM2WindowClass | NET::WM2DesktopFileName); 0505 0506 QString desktopFile = QString::fromUtf8(info.desktopFileName()); 0507 0508 if (!desktopFile.isEmpty()) { 0509 KService::Ptr service = KService::serviceByStorageId(desktopFile); 0510 0511 if (service) { 0512 const QString &menuId = service->menuId(); 0513 0514 // applications: URLs are used to refer to applications by their KService::menuId 0515 // (i.e. .desktop file name) rather than the absolute path to a .desktop file. 0516 if (!menuId.isEmpty()) { 0517 return QUrl(QStringLiteral("applications:") + menuId); 0518 } 0519 0520 return QUrl::fromLocalFile(service->entryPath()); 0521 } 0522 0523 if (!desktopFile.endsWith(QLatin1String(".desktop"))) { 0524 desktopFile.append(QLatin1String(".desktop")); 0525 } 0526 0527 if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) { 0528 return QUrl::fromLocalFile(desktopFile); 0529 } 0530 } 0531 0532 return windowUrlFromMetadata(info.windowClassClass(), 0533 NETWinInfo(QX11Info::connection(), wid.value<WId>(), QX11Info::appRootWindow(), NET::WMPid, NET::Properties2()).pid(), 0534 rulesConfig, info.windowClassName()); 0535 } 0536 0537 bool XWindowInterface::windowCanBeDragged(WindowId wid) 0538 { 0539 WindowInfoWrap winfo = requestInfo(wid); 0540 return (winfo.isValid() 0541 && !winfo.isMinimized() 0542 && winfo.isMovable() 0543 && inCurrentDesktopActivity(winfo)); 0544 } 0545 0546 bool XWindowInterface::windowCanBeMaximized(WindowId wid) 0547 { 0548 WindowInfoWrap winfo = requestInfo(wid); 0549 return (winfo.isValid() 0550 && !winfo.isMinimized() 0551 && winfo.isMaximizable() 0552 && inCurrentDesktopActivity(winfo)); 0553 } 0554 0555 void XWindowInterface::requestActivate(WindowId wid) 0556 { 0557 KWindowSystem::activateWindow(wid.toInt()); 0558 } 0559 0560 QIcon XWindowInterface::iconFor(WindowId wid) 0561 { 0562 QIcon icon; 0563 0564 icon.addPixmap(KWindowSystem::icon(wid.value<WId>(), KIconLoader::SizeSmall, KIconLoader::SizeSmall, false)); 0565 icon.addPixmap(KWindowSystem::icon(wid.value<WId>(), KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium, false)); 0566 icon.addPixmap(KWindowSystem::icon(wid.value<WId>(), KIconLoader::SizeMedium, KIconLoader::SizeMedium, false)); 0567 icon.addPixmap(KWindowSystem::icon(wid.value<WId>(), KIconLoader::SizeLarge, KIconLoader::SizeLarge, false)); 0568 0569 return icon; 0570 } 0571 0572 WindowId XWindowInterface::winIdFor(QString appId, QRect geometry) 0573 { 0574 return activeWindow(); 0575 } 0576 0577 WindowId XWindowInterface::winIdFor(QString appId, QString title) 0578 { 0579 return activeWindow(); 0580 } 0581 0582 void XWindowInterface::requestClose(WindowId wid) 0583 { 0584 WindowInfoWrap wInfo = requestInfo(wid); 0585 0586 if (!wInfo.isValid()) { 0587 return; 0588 } 0589 0590 NETRootInfo ri(QX11Info::connection(), NET::CloseWindow); 0591 ri.closeWindowRequest(wInfo.wid().toUInt()); 0592 } 0593 0594 void XWindowInterface::requestMoveWindow(WindowId wid, QPoint from) 0595 { 0596 WindowInfoWrap wInfo = requestInfo(wid); 0597 0598 if (!wInfo.isValid() || !inCurrentDesktopActivity(wInfo)) { 0599 return; 0600 } 0601 0602 int borderX = wInfo.geometry().width() > 120 ? 60 : 10; 0603 int borderY{10}; 0604 0605 //! find min/max values for x,y based on active window geometry 0606 int minX = wInfo.geometry().x() + borderX; 0607 int maxX = wInfo.geometry().x() + wInfo.geometry().width() - borderX; 0608 int minY = wInfo.geometry().y() + borderY; 0609 int maxY = wInfo.geometry().y() + wInfo.geometry().height() - borderY; 0610 0611 //! set the point from which this window will be moved, 0612 //! make sure that it is in window boundaries 0613 int validX = qBound(minX, from.x(), maxX); 0614 int validY = qBound(minY, from.y(), maxY); 0615 0616 NETRootInfo ri(QX11Info::connection(), NET::WMMoveResize); 0617 ri.moveResizeRequest(wInfo.wid().toUInt(), validX, validY, NET::Move); 0618 } 0619 0620 void XWindowInterface::requestToggleIsOnAllDesktops(WindowId wid) 0621 { 0622 WindowInfoWrap wInfo = requestInfo(wid); 0623 0624 if (!wInfo.isValid()) { 0625 return; 0626 } 0627 0628 if (KWindowSystem::numberOfDesktops() <= 1) { 0629 return; 0630 } 0631 0632 if (wInfo.isOnAllDesktops()) { 0633 KWindowSystem::setOnDesktop(wid.toUInt(), KWindowSystem::currentDesktop()); 0634 KWindowSystem::forceActiveWindow(wid.toUInt()); 0635 } else { 0636 KWindowSystem::setOnAllDesktops(wid.toUInt(), true); 0637 } 0638 } 0639 0640 void XWindowInterface::requestToggleKeepAbove(WindowId wid) 0641 { 0642 WindowInfoWrap wInfo = requestInfo(wid); 0643 0644 if (!wInfo.isValid()) { 0645 return; 0646 } 0647 0648 NETWinInfo ni(QX11Info::connection(), wid.toUInt(), QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); 0649 0650 if (wInfo.isKeepAbove()) { 0651 ni.setState(NET::States(), NET::KeepAbove); 0652 } else { 0653 ni.setState(NET::KeepAbove, NET::KeepAbove); 0654 } 0655 } 0656 0657 void XWindowInterface::setKeepAbove(WindowId wid, bool active) 0658 { 0659 if (wid.toUInt() <= 0) { 0660 return; 0661 } 0662 0663 if (active) { 0664 KWindowSystem::setState(wid.toUInt(), NET::KeepAbove); 0665 KWindowSystem::clearState(wid.toUInt(), NET::KeepBelow); 0666 } else { 0667 KWindowSystem::clearState(wid.toUInt(), NET::KeepAbove); 0668 } 0669 } 0670 0671 void XWindowInterface::setKeepBelow(WindowId wid, bool active) 0672 { 0673 if (wid.toUInt() <= 0) { 0674 return; 0675 } 0676 0677 if (active) { 0678 KWindowSystem::setState(wid.toUInt(), NET::KeepBelow); 0679 KWindowSystem::clearState(wid.toUInt(), NET::KeepAbove); 0680 } else { 0681 KWindowSystem::clearState(wid.toUInt(), NET::KeepBelow); 0682 } 0683 } 0684 0685 0686 void XWindowInterface::requestToggleMinimized(WindowId wid) 0687 { 0688 WindowInfoWrap wInfo = requestInfo(wid); 0689 0690 if (!wInfo.isValid() || !inCurrentDesktopActivity(wInfo)) { 0691 return; 0692 } 0693 0694 if (wInfo.isMinimized()) { 0695 bool onCurrent = wInfo.isOnDesktop(m_currentDesktop); 0696 0697 KWindowSystem::unminimizeWindow(wid.toUInt()); 0698 0699 if (onCurrent) { 0700 KWindowSystem::forceActiveWindow(wid.toUInt()); 0701 } 0702 } else { 0703 KWindowSystem::minimizeWindow(wid.toUInt()); 0704 } 0705 } 0706 0707 void XWindowInterface::requestToggleMaximized(WindowId wid) 0708 { 0709 WindowInfoWrap wInfo = requestInfo(wid); 0710 0711 if (!windowCanBeMaximized(wid) || !inCurrentDesktopActivity(wInfo)) { 0712 return; 0713 } 0714 0715 bool restore = wInfo.isMaxHoriz() && wInfo.isMaxVert(); 0716 0717 if (wInfo.isMinimized()) { 0718 KWindowSystem::unminimizeWindow(wid.toUInt()); 0719 } 0720 0721 NETWinInfo ni(QX11Info::connection(), wid.toInt(), QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); 0722 0723 if (restore) { 0724 ni.setState(NET::States(), NET::Max); 0725 } else { 0726 ni.setState(NET::Max, NET::Max); 0727 } 0728 } 0729 0730 bool XWindowInterface::isValidWindow(WindowId wid) 0731 { 0732 if (windowsTracker()->isValidFor(wid)) { 0733 return true; 0734 } 0735 0736 return isAcceptableWindow(wid); 0737 } 0738 0739 bool XWindowInterface::isAcceptableWindow(WindowId wid) 0740 { 0741 const KWindowInfo info(wid.toUInt(), NET::WMGeometry | NET::WMState, NET::WM2WindowClass); 0742 0743 const auto winClass = QString(info.windowClassName()); 0744 0745 //! ignored windows do not trackd 0746 if (hasBlockedTracking(wid)) { 0747 return false; 0748 } 0749 0750 //! whitelisted/approved windows 0751 if (isWhitelistedWindow(wid)) { 0752 return true; 0753 } 0754 0755 //! Window Checks 0756 bool hasSkipTaskbar = info.hasState(NET::SkipTaskbar); 0757 bool hasSkipPager = info.hasState(NET::SkipPager); 0758 bool isSkipped = hasSkipTaskbar && hasSkipPager; 0759 0760 if (isSkipped 0761 && ((winClass == QLatin1String("yakuake") 0762 || (winClass == QLatin1String("krunner"))) )) { 0763 registerWhitelistedWindow(wid); 0764 } else if (winClass == QLatin1String("plasmashell")) { 0765 if (isSkipped && isSidepanel(info.geometry())) { 0766 registerWhitelistedWindow(wid); 0767 return true; 0768 } else if (isPlasmaPanel(info.geometry()) || isFullScreenWindow(info.geometry())) { 0769 registerPlasmaIgnoredWindow(wid); 0770 return false; 0771 } 0772 } else if ((winClass == QLatin1String("latte-dock")) 0773 || (winClass == QLatin1String("ksmserver"))) { 0774 if (isFullScreenWindow(info.geometry())) { 0775 registerIgnoredWindow(wid); 0776 return false; 0777 } 0778 } 0779 0780 return !isSkipped; 0781 } 0782 0783 void XWindowInterface::windowAddedProxy(WId wid) 0784 { 0785 if (!isAcceptableWindow(wid)) { 0786 return; 0787 } 0788 0789 emit windowAdded(wid); 0790 considerWindowChanged(wid); 0791 } 0792 0793 void XWindowInterface::windowChangedProxy(WId wid, NET::Properties prop1, NET::Properties2 prop2) 0794 { 0795 if (!isValidWindow(wid)) { 0796 return; 0797 } 0798 0799 //! accept only NET::Properties events, 0800 //! ignore when the user presses a key, or a window is sending X events etc. 0801 //! without needing to (e.g. Firefox, https://bugzilla.mozilla.org/show_bug.cgi?id=1389953) 0802 //! NET::WM2UserTime, NET::WM2IconPixmap etc.... 0803 if (prop1 == 0 && !(prop2 & (NET::WM2Activities | NET::WM2TransientFor))) { 0804 return; 0805 } 0806 0807 //! accept only the following NET:Properties changed signals 0808 //! NET::WMState, NET::WMGeometry, NET::ActiveWindow 0809 if ( !(prop1 & NET::WMState) 0810 && !(prop1 & NET::WMGeometry) 0811 && !(prop1 & NET::ActiveWindow) 0812 && !(prop1 & (NET::WMName | NET::WMVisibleName) 0813 && !(prop2 & NET::WM2TransientFor) 0814 && !(prop2 & NET::WM2Activities)) ) { 0815 return; 0816 } 0817 0818 considerWindowChanged(wid); 0819 } 0820 0821 } 0822 }