diff --git a/autotests/integration/xwayland_input_test.cpp b/autotests/integration/xwayland_input_test.cpp index 74951849d..0cecabe97 100644 --- a/autotests/integration/xwayland_input_test.cpp +++ b/autotests/integration/xwayland_input_test.cpp @@ -1,212 +1,315 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "x11client.h" #include "cursor.h" #include "deleted.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "xdgshellclient.h" #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_xwayland_input-0"); class XWaylandInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); - void testPointerEnterLeave(); + void testPointerEnterLeaveSsd(); + void testPointerEventLeaveCsd(); }; void XWaylandInputTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void XWaylandInputTest::init() { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); + xcb_warp_pointer(connection(), XCB_WINDOW_NONE, kwinApp()->x11RootWindow(), 0, 0, 0, 0, 640, 512); + xcb_flush(connection()); QVERIFY(waylandServer()->clients().isEmpty()); } - struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; class X11EventReaderHelper : public QObject { Q_OBJECT public: X11EventReaderHelper(xcb_connection_t *c); Q_SIGNALS: - void entered(); - void left(); + void entered(const QPoint &localPoint); + void left(const QPoint &localPoint); private: void processXcbEvents(); xcb_connection_t *m_connection; QSocketNotifier *m_notifier; }; X11EventReaderHelper::X11EventReaderHelper(xcb_connection_t *c) : QObject() , m_connection(c) , m_notifier(new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this)) { connect(m_notifier, &QSocketNotifier::activated, this, &X11EventReaderHelper::processXcbEvents); connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &X11EventReaderHelper::processXcbEvents); connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &X11EventReaderHelper::processXcbEvents); } void X11EventReaderHelper::processXcbEvents() { while (auto event = xcb_poll_for_event(m_connection)) { const uint8_t eventType = event->response_type & ~0x80; switch (eventType) { - case XCB_ENTER_NOTIFY: - emit entered(); - break; - case XCB_LEAVE_NOTIFY: - emit left(); - break; + case XCB_ENTER_NOTIFY: { + auto enterEvent = reinterpret_cast(event); + emit entered(QPoint(enterEvent->event_x, enterEvent->event_y)); + break; } + case XCB_LEAVE_NOTIFY: { + auto leaveEvent = reinterpret_cast(event); + emit left(QPoint(leaveEvent->event_x, leaveEvent->event_y)); + break; } } free(event); } xcb_flush(m_connection); } -void XWaylandInputTest::testPointerEnterLeave() +void XWaylandInputTest::testPointerEnterLeaveSsd() { - // this test simulates a pointer enter and pointer leave on an X11 window + // this test simulates a pointer enter and pointer leave on a server-side decorated X11 window // create the test window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); if (xcb_get_setup(c.data())->release_number < 11800000) { QSKIP("XWayland 1.18 required"); } X11EventReaderHelper eventReader(c.data()); QSignalSpy enteredSpy(&eventReader, &X11EventReaderHelper::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(&eventReader, &X11EventReaderHelper::left); QVERIFY(leftSpy.isValid()); // atom for the screenedge show hide functionality Xcb::Atom atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"), false, c.data()); xcb_window_t w = xcb_generate_id(c.data()); const QRect windowGeometry = QRect(0, 0, 100, 200); const uint32_t values[] = { XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW }; xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); NETWinInfo info(c.data(), w, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); info.setWindowType(NET::Normal); xcb_map_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); X11Client *client = windowCreatedSpy.last().first().value(); QVERIFY(client); QVERIFY(client->isDecorated()); QVERIFY(!client->hasStrut()); QVERIFY(!client->isHiddenInternal()); QVERIFY(!client->readyForPainting()); QMetaObject::invokeMethod(client, "setReadyForPainting"); QVERIFY(client->readyForPainting()); QVERIFY(!client->surface()); QSignalSpy surfaceChangedSpy(client, &Toplevel::surfaceChanged); QVERIFY(surfaceChangedSpy.isValid()); QVERIFY(surfaceChangedSpy.wait()); QVERIFY(client->surface()); // move pointer into the window, should trigger an enter QVERIFY(!client->frameGeometry().contains(Cursor::pos())); QVERIFY(enteredSpy.isEmpty()); Cursor::setPos(client->frameGeometry().center()); QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), client->surface()); QVERIFY(waylandServer()->seat()->focusedPointer()); QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.last().first(), QPoint(49, 81)); // move out of window Cursor::setPos(client->frameGeometry().bottomRight() + QPoint(10, 10)); QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.last().first(), QPoint(49, 81)); // destroy window again QSignalSpy windowClosedSpy(client, &X11Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); QVERIFY(windowClosedSpy.wait()); } +void XWaylandInputTest::testPointerEventLeaveCsd() +{ + // this test simulates a pointer enter and pointer leave on a client-side decorated X11 window + + QScopedPointer c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.data())); + + if (xcb_get_setup(c.data())->release_number < 11800000) { + QSKIP("XWayland 1.18 required"); + } + if (!Xcb::Extensions::self()->isShapeAvailable()) { + QSKIP("SHAPE extension is required"); + } + + X11EventReaderHelper eventReader(c.data()); + QSignalSpy enteredSpy(&eventReader, &X11EventReaderHelper::entered); + QVERIFY(enteredSpy.isValid()); + QSignalSpy leftSpy(&eventReader, &X11EventReaderHelper::left); + QVERIFY(leftSpy.isValid()); + + // Extents of the client-side drop-shadow. + NETStrut clientFrameExtent; + clientFrameExtent.left = 10; + clientFrameExtent.right = 10; + clientFrameExtent.top = 5; + clientFrameExtent.bottom = 20; + + // Need to set the bounding shape in order to create a window without decoration. + xcb_rectangle_t boundingRect; + boundingRect.x = 0; + boundingRect.y = 0; + boundingRect.width = 100 + clientFrameExtent.left + clientFrameExtent.right; + boundingRect.height = 200 + clientFrameExtent.top + clientFrameExtent.bottom; + + xcb_window_t window = xcb_generate_id(c.data()); + const uint32_t values[] = { + XCB_EVENT_MASK_ENTER_WINDOW | + XCB_EVENT_MASK_LEAVE_WINDOW + }; + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, window, rootWindow(), + boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height, + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, boundingRect.x, boundingRect.y); + xcb_icccm_size_hints_set_size(&hints, 1, boundingRect.width, boundingRect.height); + xcb_icccm_set_wm_normal_hints(c.data(), window, &hints); + xcb_shape_rectangles(c.data(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, + XCB_CLIP_ORDERING_UNSORTED, window, 0, 0, 1, &boundingRect); + NETWinInfo info(c.data(), window, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Normal); + info.setGtkFrameExtents(clientFrameExtent); + xcb_map_window(c.data(), window); + xcb_flush(c.data()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); + QVERIFY(windowCreatedSpy.isValid()); + QVERIFY(windowCreatedSpy.wait()); + X11Client *client = windowCreatedSpy.last().first().value(); + QVERIFY(client); + QVERIFY(!client->isDecorated()); + QVERIFY(client->isClientSideDecorated()); + QCOMPARE(client->bufferGeometry(), QRect(0, 0, 120, 225)); + QCOMPARE(client->frameGeometry(), QRect(10, 5, 100, 200)); + + QMetaObject::invokeMethod(client, "setReadyForPainting"); + QVERIFY(client->readyForPainting()); + QVERIFY(!client->surface()); + QSignalSpy surfaceChangedSpy(client, &Toplevel::surfaceChanged); + QVERIFY(surfaceChangedSpy.isValid()); + QVERIFY(surfaceChangedSpy.wait()); + QVERIFY(client->surface()); + + // Move pointer into the window, should trigger an enter. + QVERIFY(!client->frameGeometry().contains(Cursor::pos())); + QVERIFY(enteredSpy.isEmpty()); + Cursor::setPos(client->frameGeometry().center()); + QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), client->surface()); + QVERIFY(waylandServer()->seat()->focusedPointer()); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.last().first(), QPoint(59, 104)); + + // Move out of the window, should trigger a leave. + QVERIFY(leftSpy.isEmpty()); + Cursor::setPos(client->frameGeometry().bottomRight() + QPoint(100, 100)); + QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.last().first(), QPoint(59, 104)); + + // Destroy the window. + QSignalSpy windowClosedSpy(client, &X11Client::windowClosed); + QVERIFY(windowClosedSpy.isValid()); + xcb_unmap_window(c.data(), window); + xcb_destroy_window(c.data(), window); + xcb_flush(c.data()); + QVERIFY(windowClosedSpy.wait()); +} + } WAYLANDTEST_MAIN(KWin::XWaylandInputTest) #include "xwayland_input_test.moc" diff --git a/effects/presentwindows/presentwindows.cpp b/effects/presentwindows/presentwindows.cpp index f6fbdb052..16bc75766 100644 --- a/effects/presentwindows/presentwindows.cpp +++ b/effects/presentwindows/presentwindows.cpp @@ -1,1962 +1,1966 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2008 Lucas Murray This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "presentwindows.h" //KConfigSkeleton #include "presentwindowsconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { PresentWindowsEffect::PresentWindowsEffect() : m_proxy(this) , m_activated(false) , m_ignoreMinimized(false) , m_decalOpacity(0.0) , m_hasKeyboardGrab(false) , m_mode(ModeCurrentDesktop) , m_managerWindow(nullptr) , m_needInitialSelection(false) , m_highlightedWindow(nullptr) , m_filterFrame(nullptr) , m_closeView(nullptr) , m_exposeAction(new QAction(this)) , m_exposeAllAction(new QAction(this)) , m_exposeClassAction(new QAction(this)) { initConfig(); auto announceSupportProperties = [this] { m_atomDesktop = effects->announceSupportProperty("_KDE_PRESENT_WINDOWS_DESKTOP", this); m_atomWindows = effects->announceSupportProperty("_KDE_PRESENT_WINDOWS_GROUP", this); }; announceSupportProperties(); connect(effects, &EffectsHandler::xcbConnectionChanged, this, announceSupportProperties); QAction* exposeAction = m_exposeAction; exposeAction->setObjectName(QStringLiteral("Expose")); exposeAction->setText(i18n("Toggle Present Windows (Current desktop)")); KGlobalAccel::self()->setDefaultShortcut(exposeAction, QList() << Qt::CTRL + Qt::Key_F9); KGlobalAccel::self()->setShortcut(exposeAction, QList() << Qt::CTRL + Qt::Key_F9); shortcut = KGlobalAccel::self()->shortcut(exposeAction); effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F9, exposeAction); connect(exposeAction, &QAction::triggered, this, &PresentWindowsEffect::toggleActive); QAction* exposeAllAction = m_exposeAllAction; exposeAllAction->setObjectName(QStringLiteral("ExposeAll")); exposeAllAction->setText(i18n("Toggle Present Windows (All desktops)")); KGlobalAccel::self()->setDefaultShortcut(exposeAllAction, QList() << Qt::CTRL + Qt::Key_F10 << Qt::Key_LaunchC); KGlobalAccel::self()->setShortcut(exposeAllAction, QList() << Qt::CTRL + Qt::Key_F10 << Qt::Key_LaunchC); shortcutAll = KGlobalAccel::self()->shortcut(exposeAllAction); effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F10, exposeAllAction); effects->registerTouchpadSwipeShortcut(SwipeDirection::Down, exposeAllAction); connect(exposeAllAction, &QAction::triggered, this, &PresentWindowsEffect::toggleActiveAllDesktops); QAction* exposeClassAction = m_exposeClassAction; exposeClassAction->setObjectName(QStringLiteral("ExposeClass")); exposeClassAction->setText(i18n("Toggle Present Windows (Window class)")); KGlobalAccel::self()->setDefaultShortcut(exposeClassAction, QList() << Qt::CTRL + Qt::Key_F7); KGlobalAccel::self()->setShortcut(exposeClassAction, QList() << Qt::CTRL + Qt::Key_F7); effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F7, exposeClassAction); connect(exposeClassAction, &QAction::triggered, this, &PresentWindowsEffect::toggleActiveClass); shortcutClass = KGlobalAccel::self()->shortcut(exposeClassAction); connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &PresentWindowsEffect::globalShortcutChanged); reconfigure(ReconfigureAll); connect(effects, &EffectsHandler::windowAdded, this, &PresentWindowsEffect::slotWindowAdded); connect(effects, &EffectsHandler::windowClosed, this, &PresentWindowsEffect::slotWindowClosed); connect(effects, &EffectsHandler::windowDeleted, this, &PresentWindowsEffect::slotWindowDeleted); connect(effects, &EffectsHandler::windowFrameGeometryChanged, this, &PresentWindowsEffect::slotWindowFrameGeometryChanged); connect(effects, &EffectsHandler::propertyNotify, this, &PresentWindowsEffect::slotPropertyNotify); connect(effects, &EffectsHandler::numberScreensChanged, this, [this] { if (isActive()) reCreateGrids(); } ); connect(effects, &EffectsHandler::screenAboutToLock, this, [this]() { setActive(false); }); } PresentWindowsEffect::~PresentWindowsEffect() { delete m_filterFrame; delete m_closeView; } void PresentWindowsEffect::reconfigure(ReconfigureFlags) { PresentWindowsConfig::self()->read(); foreach (ElectricBorder border, m_borderActivate) { effects->unreserveElectricBorder(border, this); } foreach (ElectricBorder border, m_borderActivateAll) { effects->unreserveElectricBorder(border, this); } m_borderActivate.clear(); m_borderActivateAll.clear(); foreach (int i, PresentWindowsConfig::borderActivate()) { m_borderActivate.append(ElectricBorder(i)); effects->reserveElectricBorder(ElectricBorder(i), this); } foreach (int i, PresentWindowsConfig::borderActivateAll()) { m_borderActivateAll.append(ElectricBorder(i)); effects->reserveElectricBorder(ElectricBorder(i), this); } foreach (int i, PresentWindowsConfig::borderActivateClass()) { m_borderActivateClass.append(ElectricBorder(i)); effects->reserveElectricBorder(ElectricBorder(i), this); } m_layoutMode = PresentWindowsConfig::layoutMode(); m_showCaptions = PresentWindowsConfig::drawWindowCaptions(); m_showIcons = PresentWindowsConfig::drawWindowIcons(); m_doNotCloseWindows = !PresentWindowsConfig::allowClosingWindows(); if (m_doNotCloseWindows) { delete m_closeView; m_closeView = nullptr; } m_ignoreMinimized = PresentWindowsConfig::ignoreMinimized(); m_accuracy = PresentWindowsConfig::accuracy() * 20; m_fillGaps = PresentWindowsConfig::fillGaps(); m_fadeDuration = double(animationTime(150)); m_showPanel = PresentWindowsConfig::showPanel(); m_leftButtonWindow = (WindowMouseAction)PresentWindowsConfig::leftButtonWindow(); m_middleButtonWindow = (WindowMouseAction)PresentWindowsConfig::middleButtonWindow(); m_rightButtonWindow = (WindowMouseAction)PresentWindowsConfig::rightButtonWindow(); m_leftButtonDesktop = (DesktopMouseAction)PresentWindowsConfig::leftButtonDesktop(); m_middleButtonDesktop = (DesktopMouseAction)PresentWindowsConfig::middleButtonDesktop(); m_rightButtonDesktop = (DesktopMouseAction)PresentWindowsConfig::rightButtonDesktop(); // touch screen edges const QVector relevantBorders{ElectricLeft, ElectricTop, ElectricRight, ElectricBottom}; for (auto e : relevantBorders) { effects->unregisterTouchBorder(e, m_exposeAction); effects->unregisterTouchBorder(e, m_exposeAllAction); effects->unregisterTouchBorder(e, m_exposeClassAction); } auto touchEdge = [&relevantBorders] (const QList touchBorders, QAction *action) { for (int i : touchBorders) { if (!relevantBorders.contains(ElectricBorder(i))) { continue; } effects->registerTouchBorder(ElectricBorder(i), action); } }; touchEdge(PresentWindowsConfig::touchBorderActivate(), m_exposeAction); touchEdge(PresentWindowsConfig::touchBorderActivateAll(), m_exposeAllAction); touchEdge(PresentWindowsConfig::touchBorderActivateClass(), m_exposeClassAction); } void* PresentWindowsEffect::proxy() { return &m_proxy; } void PresentWindowsEffect::toggleActiveClass() { if (!m_activated) { if (!effects->activeWindow()) return; m_mode = ModeWindowClass; m_class = effects->activeWindow()->windowClass(); } setActive(!m_activated); } //----------------------------------------------------------------------------- // Screen painting void PresentWindowsEffect::prePaintScreen(ScreenPrePaintData &data, int time) { m_motionManager.calculate(time); // We need to mark the screen as having been transformed otherwise there will be no repainting if (m_activated || m_motionManager.managingWindows()) data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; if (m_activated) m_decalOpacity = qMin(1.0, m_decalOpacity + time / m_fadeDuration); else m_decalOpacity = qMax(0.0, m_decalOpacity - time / m_fadeDuration); effects->prePaintScreen(data, time); } void PresentWindowsEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data) { effects->paintScreen(mask, region, data); // Display the filter box if (!m_windowFilter.isEmpty()) m_filterFrame->render(region); if (m_closeView) effects->renderEffectQuickView(m_closeView); } void PresentWindowsEffect::postPaintScreen() { if (m_motionManager.areWindowsMoving()) effects->addRepaintFull(); else if (!m_activated && m_motionManager.managingWindows() && !(m_closeView && m_closeView->isVisible())) { // We have finished moving them back, stop processing m_motionManager.unmanageAll(); DataHash::iterator i = m_windowData.begin(); while (i != m_windowData.end()) { delete i.value().textFrame; delete i.value().iconFrame; ++i; } m_windowData.clear(); foreach (EffectWindow * w, effects->stackingOrder()) { w->setData(WindowForceBlurRole, QVariant()); w->setData(WindowForceBackgroundContrastRole, QVariant()); } effects->setActiveFullScreenEffect(nullptr); effects->addRepaintFull(); } else if (m_activated && m_needInitialSelection) { m_needInitialSelection = false; QMouseEvent me(QEvent::MouseMove, cursorPos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier); windowInputMouseEvent(&me); } // Update windows that are changing brightness or opacity DataHash::const_iterator i; for (i = m_windowData.constBegin(); i != m_windowData.constEnd(); ++i) { if (i.value().opacity > 0.0 && i.value().opacity < 1.0) i.key()->addRepaintFull(); if (i.key()->isDesktop() && !m_motionManager.isManaging(i.key())) { if (i.value().highlight != 0.3) i.key()->addRepaintFull(); } else if (i.value().highlight > 0.0 && i.value().highlight < 1.0) i.key()->addRepaintFull(); } effects->postPaintScreen(); } //----------------------------------------------------------------------------- // Window painting void PresentWindowsEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, int time) { // TODO: We should also check to see if any windows are fading just in case fading takes longer // than moving the windows when the effect is deactivated. if (m_activated || m_motionManager.areWindowsMoving() || m_closeView) { DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end()) { effects->prePaintWindow(w, data, time); return; } w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE); // Display always w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); // Calculate window's opacity // TODO: Minimized windows or windows not on the current desktop are only 75% visible? if (winData->visible) { if (winData->deleted) winData->opacity = qMax(0.0, winData->opacity - time / m_fadeDuration); else winData->opacity = qMin(/*(w->isMinimized() || !w->isOnCurrentDesktop()) ? 0.75 :*/ 1.0, winData->opacity + time / m_fadeDuration); } else winData->opacity = qMax(0.0, winData->opacity - time / m_fadeDuration); if (winData->opacity <= 0.0) { // don't disable painting for panels if show panel is set if (!(m_showPanel && w->isDock())) w->disablePainting(EffectWindow::PAINT_DISABLED); } else if (winData->opacity != 1.0) data.setTranslucent(); const bool isInMotion = m_motionManager.isManaging(w); // Calculate window's brightness if (w == m_highlightedWindow || !m_activated) winData->highlight = qMin(1.0, winData->highlight + time / m_fadeDuration); else if (!isInMotion && w->isDesktop()) winData->highlight = 0.3; else winData->highlight = qMax(0.0, winData->highlight - time / m_fadeDuration); // Closed windows if (winData->deleted) { data.setTranslucent(); if (winData->opacity <= 0.0 && winData->referenced) { // it's possible that another effect has referenced the window // we have to keep the window in the list to prevent flickering winData->referenced = false; w->unrefWindow(); } else w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DELETE); } // desktop windows on other desktops (Plasma activity per desktop) should not be painted if (w->isDesktop() && !w->isOnCurrentDesktop()) w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); if (isInMotion) data.setTransformed(); // We will be moving this window } effects->prePaintWindow(w, data, time); } void PresentWindowsEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { if (m_activated || m_motionManager.areWindowsMoving()) { DataHash::const_iterator winData = m_windowData.constFind(w); if (winData == m_windowData.constEnd() || (w->isDock() && m_showPanel)) { // in case the panel should be shown just display it without any changes effects->paintWindow(w, mask, region, data); return; } mask |= PAINT_WINDOW_LANCZOS; // Apply opacity and brightness data.multiplyOpacity(winData->opacity); data.multiplyBrightness(interpolate(0.40, 1.0, winData->highlight)); if (m_motionManager.isManaging(w)) { if (w->isDesktop()) { effects->paintWindow(w, mask, region, data); } m_motionManager.apply(w, data); QRect rect = m_motionManager.transformedGeometry(w).toRect(); if (m_activated && winData->highlight > 0.0) { // scale the window (interpolated by the highlight level) to at least 105% or to cover 1/16 of the screen size - yet keep it in screen bounds QRect area = effects->clientArea(FullScreenArea, w); QSizeF effSize(w->width()*data.xScale(), w->height()*data.yScale()); const float xr = area.width()/effSize.width(); const float yr = area.height()/effSize.height(); float tScale = 0.0; if (xr < yr) { tScale = qMax(xr/4.0, yr/32.0); } else { tScale = qMax(xr/32.0, yr/4.0); } if (tScale < 1.05) { tScale = 1.05; } if (effSize.width()*tScale > area.width()) tScale = area.width() / effSize.width(); if (effSize.height()*tScale > area.height()) tScale = area.height() / effSize.height(); const qreal scale = interpolate(1.0, tScale, winData->highlight); if (scale > 1.0) { if (scale < tScale) // don't use lanczos during transition mask &= ~PAINT_WINDOW_LANCZOS; const float df = (tScale-1.0f)*0.5f; int tx = qRound(rect.width()*df); int ty = qRound(rect.height()*df); QRect tRect(rect.adjusted(-tx, -ty, tx, ty)); tx = qMax(tRect.x(), area.x()) + qMin(0, area.right()-tRect.right()); ty = qMax(tRect.y(), area.y()) + qMin(0, area.bottom()-tRect.bottom()); tx = qRound((tx-rect.x())*winData->highlight); ty = qRound((ty-rect.y())*winData->highlight); rect.translate(tx,ty); rect.setWidth(rect.width()*scale); rect.setHeight(rect.height()*scale); data *= QVector2D(scale, scale); data += QPoint(tx, ty); } } if (m_motionManager.areWindowsMoving()) { mask &= ~PAINT_WINDOW_LANCZOS; } effects->paintWindow(w, mask, region, data); if (m_showIcons) { QPoint point(rect.x() + rect.width() * 0.95, rect.y() + rect.height() * 0.95); winData->iconFrame->setPosition(point); if (effects->compositingType() == KWin::OpenGL2Compositing && data.shader) { const float a = 0.9 * data.opacity() * m_decalOpacity * 0.75; data.shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } winData->iconFrame->render(region, 0.9 * data.opacity() * m_decalOpacity, 0.75); } if (m_showCaptions) { QPoint point(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2); winData->textFrame->setPosition(point); if (effects->compositingType() == KWin::OpenGL2Compositing && data.shader) { const float a = 0.9 * data.opacity() * m_decalOpacity * 0.75; data.shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } winData->textFrame->render(region, 0.9 * data.opacity() * m_decalOpacity, 0.75); } } else { effects->paintWindow(w, mask, region, data); } } else effects->paintWindow(w, mask, region, data); } //----------------------------------------------------------------------------- // User interaction void PresentWindowsEffect::slotWindowAdded(EffectWindow *w) { if (!m_activated) return; WindowData *winData = &m_windowData[w]; winData->visible = isVisibleWindow(w); winData->opacity = 0.0; winData->highlight = 0.0; winData->textFrame = effects->effectFrame(EffectFrameUnstyled, false); QFont font; font.setBold(true); font.setPointSize(12); winData->textFrame->setFont(font); winData->iconFrame = effects->effectFrame(EffectFrameUnstyled, false); winData->iconFrame->setAlignment(Qt::AlignRight | Qt::AlignBottom); winData->iconFrame->setIcon(w->icon()); winData->iconFrame->setIconSize(QSize(32, 32)); if (isSelectableWindow(w)) { m_motionManager.manage(w); rearrangeWindows(); } } void PresentWindowsEffect::slotWindowClosed(EffectWindow *w) { if (m_managerWindow == w) m_managerWindow = nullptr; DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end()) return; winData->deleted = true; if (!winData->referenced) { winData->referenced = true; w->refWindow(); } if (m_highlightedWindow == w) setHighlightedWindow(findFirstWindow()); rearrangeWindows(); foreach (EffectWindow *w, m_motionManager.managedWindows()) { winData = m_windowData.find(w); if (winData != m_windowData.end() && !winData->deleted) return; // found one that is not deleted? then we go on } setActive(false); //else no need to keep this open } void PresentWindowsEffect::slotWindowDeleted(EffectWindow *w) { DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end()) return; delete winData->textFrame; delete winData->iconFrame; m_windowData.erase(winData); m_motionManager.unmanage(w); } void PresentWindowsEffect::slotWindowFrameGeometryChanged(EffectWindow* w, const QRect& old) { Q_UNUSED(old) if (!m_activated) return; if (!m_windowData.contains(w)) return; rearrangeWindows(); } bool PresentWindowsEffect::borderActivated(ElectricBorder border) { int mode = 0; if (m_borderActivate.contains(border)) mode |= 1; else if (m_borderActivateAll.contains(border)) mode |= 2; else if (m_borderActivateClass.contains(border)) mode |= 4; if (!mode) return false; if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return true; if (mode & 1) toggleActive(); else if (mode & 2) toggleActiveAllDesktops(); else if (mode & 4) toggleActiveClass(); return true; } void PresentWindowsEffect::windowInputMouseEvent(QEvent *e) { QMouseEvent* me = dynamic_cast< QMouseEvent* >(e); if (!me) { return; } + me->setAccepted(false); if (m_closeView) { const bool contains = m_closeView->geometry().contains(me->pos()); if (!m_closeView->isVisible() && contains) { updateCloseWindow(); } m_closeView->forwardMouseEvent(e); } + if (e->isAccepted()) { + return; + } inputEventUpdate(me->pos(), me->type(), me->button()); } void PresentWindowsEffect::inputEventUpdate(const QPoint &pos, QEvent::Type type, Qt::MouseButton button) { // Which window are we hovering over? Always trigger as we don't always get move events before clicking // We cannot use m_motionManager.windowAtPoint() as the window might not be visible EffectWindowList windows = m_motionManager.managedWindows(); bool hovering = false; EffectWindow *highlightCandidate = nullptr; for (int i = 0; i < windows.size(); ++i) { DataHash::const_iterator winData = m_windowData.constFind(windows.at(i)); if (winData == m_windowData.constEnd()) continue; if (m_motionManager.transformedGeometry(windows.at(i)).contains(pos) && winData->visible && !winData->deleted) { hovering = true; if (windows.at(i) && m_highlightedWindow != windows.at(i)) highlightCandidate = windows.at(i); break; } } if (!hovering) setHighlightedWindow(nullptr); if (m_highlightedWindow && m_motionManager.transformedGeometry(m_highlightedWindow).contains(pos)) updateCloseWindow(); else if (m_closeView) m_closeView->hide(); if (type == QEvent::MouseButtonRelease) { if (highlightCandidate) setHighlightedWindow(highlightCandidate); if (button == Qt::LeftButton) { if (hovering) { // mouse is hovering above a window - use MouseActionsWindow mouseActionWindow(m_leftButtonWindow); } else { // mouse is hovering above desktop - use MouseActionsDesktop mouseActionDesktop(m_leftButtonDesktop); } } if (button == Qt::MidButton) { if (hovering) { // mouse is hovering above a window - use MouseActionsWindow mouseActionWindow(m_middleButtonWindow); } else { // mouse is hovering above desktop - use MouseActionsDesktop mouseActionDesktop(m_middleButtonDesktop); } } if (button == Qt::RightButton) { if (hovering) { // mouse is hovering above a window - use MouseActionsWindow mouseActionWindow(m_rightButtonWindow); } else { // mouse is hovering above desktop - use MouseActionsDesktop mouseActionDesktop(m_rightButtonDesktop); } } } else if (highlightCandidate && !m_motionManager.areWindowsMoving()) setHighlightedWindow(highlightCandidate); } bool PresentWindowsEffect::touchDown(qint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(time) if (!m_activated) { return false; } // only if we don't track a touch id yet if (!m_touch.active) { m_touch.active = true; m_touch.id = id; inputEventUpdate(pos.toPoint()); } return true; } bool PresentWindowsEffect::touchMotion(qint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) if (!m_activated) { return false; } if (m_touch.active && m_touch.id == id) { // only update for the touch id we track inputEventUpdate(pos.toPoint()); } return true; } bool PresentWindowsEffect::touchUp(qint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) if (!m_activated) { return false; } if (m_touch.active && m_touch.id == id) { m_touch.active = false; m_touch.id = 0; if (m_highlightedWindow) { mouseActionWindow(m_leftButtonWindow); } } return true; } void PresentWindowsEffect::mouseActionWindow(WindowMouseAction& action) { switch(action) { case WindowActivateAction: if (m_highlightedWindow) effects->activateWindow(m_highlightedWindow); setActive(false); break; case WindowExitAction: setActive(false); break; case WindowToCurrentDesktopAction: if (m_highlightedWindow) effects->windowToDesktop(m_highlightedWindow, effects->currentDesktop()); break; case WindowToAllDesktopsAction: if (m_highlightedWindow) { if (m_highlightedWindow->isOnAllDesktops()) effects->windowToDesktop(m_highlightedWindow, effects->currentDesktop()); else effects->windowToDesktop(m_highlightedWindow, NET::OnAllDesktops); } break; case WindowMinimizeAction: if (m_highlightedWindow) { if (m_highlightedWindow->isMinimized()) m_highlightedWindow->unminimize(); else m_highlightedWindow->minimize(); } break; case WindowCloseAction: if (m_highlightedWindow) { m_highlightedWindow->closeWindow(); } break; default: break; } } void PresentWindowsEffect::mouseActionDesktop(DesktopMouseAction& action) { switch(action) { case DesktopActivateAction: if (m_highlightedWindow) effects->activateWindow(m_highlightedWindow); setActive(false); break; case DesktopExitAction: setActive(false); break; case DesktopShowDesktopAction: effects->setShowingDesktop(true); setActive(false); default: break; } } void PresentWindowsEffect::grabbedKeyboardEvent(QKeyEvent *e) { if (e->type() == QEvent::KeyPress) { // check for global shortcuts // HACK: keyboard grab disables the global shortcuts so we have to check for global shortcut (bug 156155) if (m_mode == ModeCurrentDesktop && shortcut.contains(e->key() + e->modifiers())) { toggleActive(); return; } if (m_mode == ModeAllDesktops && shortcutAll.contains(e->key() + e->modifiers())) { toggleActiveAllDesktops(); return; } if (m_mode == ModeWindowClass && shortcutClass.contains(e->key() + e->modifiers())) { toggleActiveClass(); return; } switch(e->key()) { // Wrap only if not auto-repeating case Qt::Key_Left: setHighlightedWindow(relativeWindow(m_highlightedWindow, -1, 0, !e->isAutoRepeat())); break; case Qt::Key_Right: setHighlightedWindow(relativeWindow(m_highlightedWindow, 1, 0, !e->isAutoRepeat())); break; case Qt::Key_Up: setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, -1, !e->isAutoRepeat())); break; case Qt::Key_Down: setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, 1, !e->isAutoRepeat())); break; case Qt::Key_Home: setHighlightedWindow(relativeWindow(m_highlightedWindow, -1000, 0, false)); break; case Qt::Key_End: setHighlightedWindow(relativeWindow(m_highlightedWindow, 1000, 0, false)); break; case Qt::Key_PageUp: setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, -1000, false)); break; case Qt::Key_PageDown: setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, 1000, false)); break; case Qt::Key_Backspace: if (!m_windowFilter.isEmpty()) { m_windowFilter.remove(m_windowFilter.length() - 1, 1); updateFilterFrame(); rearrangeWindows(); } return; case Qt::Key_Escape: setActive(false); return; case Qt::Key_Return: case Qt::Key_Enter: if (m_highlightedWindow) effects->activateWindow(m_highlightedWindow); setActive(false); return; case Qt::Key_Tab: return; // Nothing at the moment case Qt::Key_Delete: if (!m_windowFilter.isEmpty()) { m_windowFilter.clear(); updateFilterFrame(); rearrangeWindows(); } break; case 0: return; // HACK: Workaround for Qt bug on unbound keys (#178547) default: if (!e->text().isEmpty()) { m_windowFilter.append(e->text()); updateFilterFrame(); rearrangeWindows(); return; } break; } } } //----------------------------------------------------------------------------- // Atom handling void PresentWindowsEffect::slotPropertyNotify(EffectWindow* w, long a) { if (m_atomDesktop == XCB_ATOM_NONE && m_atomWindows == XCB_ATOM_NONE) { return; } if (!w || (a != m_atomDesktop && a != m_atomWindows)) return; // Not our atom if (a == m_atomDesktop) { QByteArray byteData = w->readProperty(m_atomDesktop, m_atomDesktop, 32); if (byteData.length() < 1) { // Property was removed, end present windows setActive(false); return; } auto* data = reinterpret_cast(byteData.data()); if (!data[0]) { // Purposely ending present windows by issuing a NULL target setActive(false); return; } // present windows is active so don't do anything if (m_activated) return; int desktop = data[0]; if (desktop > effects->numberOfDesktops()) return; if (desktop == -1) toggleActiveAllDesktops(); else { m_mode = ModeSelectedDesktop; m_desktop = desktop; m_managerWindow = w; setActive(true); } } else if (a == m_atomWindows) { QByteArray byteData = w->readProperty(m_atomWindows, m_atomWindows, 32); if (byteData.length() < 1) { // Property was removed, end present windows setActive(false); return; } auto* data = reinterpret_cast(byteData.data()); if (!data[0]) { // Purposely ending present windows by issuing a NULL target setActive(false); return; } // present windows is active so don't do anything if (m_activated) return; // for security clear selected windows m_selectedWindows.clear(); int length = byteData.length() / sizeof(data[0]); for (int i = 0; i < length; i++) { EffectWindow* foundWin = effects->findWindow(data[i]); if (!foundWin) { qCDebug(KWINEFFECTS) << "Invalid window targetted for present windows. Requested:" << data[i]; continue; } m_selectedWindows.append(foundWin); } m_mode = ModeWindowGroup; m_managerWindow = w; setActive(true); } } //----------------------------------------------------------------------------- // Window rearranging void PresentWindowsEffect::rearrangeWindows() { if (!m_activated) return; effects->addRepaintFull(); // Trigger the first repaint if (m_closeView) m_closeView->hide(); // Work out which windows are on which screens EffectWindowList windowlist; QList windowlists; for (int i = 0; i < effects->numScreens(); i++) windowlists.append(EffectWindowList()); if (m_windowFilter.isEmpty()) { windowlist = m_motionManager.managedWindows(); foreach (EffectWindow * w, m_motionManager.managedWindows()) { DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end() || winData->deleted) continue; // don't include closed windows windowlists[w->screen()].append(w); winData->visible = true; } } else { // Can we move this filtering somewhere else? foreach (EffectWindow * w, m_motionManager.managedWindows()) { DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end() || winData->deleted) continue; // don't include closed windows if (w->caption().contains(m_windowFilter, Qt::CaseInsensitive) || w->windowClass().contains(m_windowFilter, Qt::CaseInsensitive) || w->windowRole().contains(m_windowFilter, Qt::CaseInsensitive)) { windowlist.append(w); windowlists[w->screen()].append(w); winData->visible = true; } else winData->visible = false; } } if (windowlist.isEmpty()) { setHighlightedWindow(nullptr); return; } // We filtered out the highlighted window if (m_highlightedWindow) { DataHash::iterator winData = m_windowData.find(m_highlightedWindow); if (winData != m_windowData.end() && !winData->visible) setHighlightedWindow(findFirstWindow()); } else setHighlightedWindow(findFirstWindow()); int screens = effects->numScreens(); for (int screen = 0; screen < screens; screen++) { EffectWindowList windows; windows = windowlists[screen]; // Don't rearrange if the grid is the same size as what it was before to prevent // windows moving to a better spot if one was filtered out. if (m_layoutMode == LayoutRegularGrid && m_gridSizes[screen].columns && m_gridSizes[screen].rows && windows.size() < m_gridSizes[screen].columns * m_gridSizes[screen].rows && windows.size() > (m_gridSizes[screen].columns - 1) * m_gridSizes[screen].rows && windows.size() > m_gridSizes[screen].columns *(m_gridSizes[screen].rows - 1)) continue; // No point continuing if there is no windows to process if (!windows.count()) continue; calculateWindowTransformations(windows, screen, m_motionManager); } // Resize text frames if required QFontMetrics* metrics = nullptr; // All fonts are the same foreach (EffectWindow * w, m_motionManager.managedWindows()) { DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end()) continue; if (!metrics) metrics = new QFontMetrics(winData->textFrame->font()); QRect geom = m_motionManager.targetGeometry(w).toRect(); QString string = metrics->elidedText(w->caption(), Qt::ElideRight, geom.width() * 0.9); if (string != winData->textFrame->text()) winData->textFrame->setText(string); } delete metrics; } void PresentWindowsEffect::calculateWindowTransformations(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager, bool external) { if (m_layoutMode == LayoutRegularGrid) calculateWindowTransformationsClosest(windowlist, screen, motionManager); else if (m_layoutMode == LayoutFlexibleGrid) calculateWindowTransformationsKompose(windowlist, screen, motionManager); else calculateWindowTransformationsNatural(windowlist, screen, motionManager); // If called externally we don't need to remember this data if (external) m_windowData.clear(); } static inline int distance(QPoint &pos1, QPoint &pos2) { const int xdiff = pos1.x() - pos2.x(); const int ydiff = pos1.y() - pos2.y(); return int(sqrt(float(xdiff*xdiff + ydiff*ydiff))); } void PresentWindowsEffect::calculateWindowTransformationsClosest(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager) { // This layout mode requires at least one window visible if (windowlist.count() == 0) return; QRect area = effects->clientArea(ScreenArea, screen, effects->currentDesktop()); if (m_showPanel) // reserve space for the panel area = effects->clientArea(MaximizeArea, screen, effects->currentDesktop()); int columns = int(ceil(sqrt(double(windowlist.count())))); int rows = int(ceil(windowlist.count() / double(columns))); // Remember the size for later // If we are using this layout externally we don't need to remember m_gridSizes. if (m_gridSizes.size() != 0) { m_gridSizes[screen].columns = columns; m_gridSizes[screen].rows = rows; } // Assign slots int slotWidth = area.width() / columns; int slotHeight = area.height() / rows; QVector takenSlots; takenSlots.resize(rows*columns); takenSlots.fill(0); // precalculate all slot centers QVector slotCenters; slotCenters.resize(rows*columns); for (int x = 0; x < columns; ++x) for (int y = 0; y < rows; ++y) { slotCenters[x + y*columns] = QPoint(area.x() + slotWidth * x + slotWidth / 2, area.y() + slotHeight * y + slotHeight / 2); } // Assign each window to the closest available slot EffectWindowList tmpList = windowlist; // use a QLinkedList copy instead? QPoint otherPos; while (!tmpList.isEmpty()) { EffectWindow *w = tmpList.first(); int slotCandidate = -1, slotCandidateDistance = INT_MAX; QPoint pos = w->geometry().center(); for (int i = 0; i < columns*rows; ++i) { // all slots const int dist = distance(pos, slotCenters[i]); if (dist < slotCandidateDistance) { // window is interested in this slot EffectWindow *occupier = takenSlots[i]; Q_ASSERT(occupier != w); if (!occupier || dist < distance((otherPos = occupier->geometry().center()), slotCenters[i])) { // either nobody lives here, or we're better - takeover the slot if it's our best slotCandidate = i; slotCandidateDistance = dist; } } } Q_ASSERT(slotCandidate != -1); if (takenSlots[slotCandidate]) tmpList << takenSlots[slotCandidate]; // occupier needs a new home now :p tmpList.removeAll(w); takenSlots[slotCandidate] = w; // ...and we rumble in =) } for (int slot = 0; slot < columns*rows; ++slot) { EffectWindow *w = takenSlots[slot]; if (!w) // some slots might be empty continue; // Work out where the slot is QRect target( area.x() + (slot % columns) * slotWidth, area.y() + (slot / columns) * slotHeight, slotWidth, slotHeight); target.adjust(10, 10, -10, -10); // Borders double scale; if (target.width() / double(w->width()) < target.height() / double(w->height())) { // Center vertically scale = target.width() / double(w->width()); target.moveTop(target.top() + (target.height() - int(w->height() * scale)) / 2); target.setHeight(int(w->height() * scale)); } else { // Center horizontally scale = target.height() / double(w->height()); target.moveLeft(target.left() + (target.width() - int(w->width() * scale)) / 2); target.setWidth(int(w->width() * scale)); } // Don't scale the windows too much if (scale > 2.0 || (scale > 1.0 && (w->width() > 300 || w->height() > 300))) { scale = (w->width() > 300 || w->height() > 300) ? 1.0 : 2.0; target = QRect( target.center().x() - int(w->width() * scale) / 2, target.center().y() - int(w->height() * scale) / 2, scale * w->width(), scale * w->height()); } motionManager.moveWindow(w, target); } } void PresentWindowsEffect::calculateWindowTransformationsKompose(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager) { // This layout mode requires at least one window visible if (windowlist.count() == 0) return; QRect availRect = effects->clientArea(ScreenArea, screen, effects->currentDesktop()); if (m_showPanel) // reserve space for the panel availRect = effects->clientArea(MaximizeArea, screen, effects->currentDesktop()); std::sort(windowlist.begin(), windowlist.end()); // The location of the windows should not depend on the stacking order // Following code is taken from Kompose 0.5.4, src/komposelayout.cpp int spacing = 10; int rows, columns; double parentRatio = availRect.width() / (double)availRect.height(); // Use more columns than rows when parent's width > parent's height if (parentRatio > 1) { columns = (int)ceil(sqrt((double)windowlist.count())); rows = (int)ceil((double)windowlist.count() / (double)columns); } else { rows = (int)ceil(sqrt((double)windowlist.count())); columns = (int)ceil((double)windowlist.count() / (double)rows); } //qCDebug(KWINEFFECTS) << "Using " << rows << " rows & " << columns << " columns for " << windowlist.count() << " clients"; // Calculate width & height int w = (availRect.width() - (columns + 1) * spacing) / columns; int h = (availRect.height() - (rows + 1) * spacing) / rows; EffectWindowList::iterator it(windowlist.begin()); QList geometryRects; QList maxRowHeights; // Process rows for (int i = 0; i < rows; ++i) { int xOffsetFromLastCol = 0; int maxHeightInRow = 0; // Process columns for (int j = 0; j < columns; ++j) { EffectWindow* window; // Check for end of List if (it == windowlist.end()) break; window = *it; // Calculate width and height of widget double ratio = aspectRatio(window); int widgetw = 100; int widgeth = 100; int usableW = w; int usableH = h; // use width of two boxes if there is no right neighbour if (window == windowlist.last() && j != columns - 1) { usableW = 2 * w; } ++it; // We need access to the neighbour in the following // expand if right neighbour has ratio < 1 if (j != columns - 1 && it != windowlist.end() && aspectRatio(*it) < 1) { int addW = w - widthForHeight(*it, h); if (addW > 0) { usableW = w + addW; } } if (ratio == -1) { widgetw = w; widgeth = h; } else { double widthByHeight = widthForHeight(window, usableH); double heightByWidth = heightForWidth(window, usableW); if ((ratio >= 1.0 && heightByWidth <= usableH) || (ratio < 1.0 && widthByHeight > usableW)) { widgetw = usableW; widgeth = (int)heightByWidth; } else if ((ratio < 1.0 && widthByHeight <= usableW) || (ratio >= 1.0 && heightByWidth > usableH)) { widgeth = usableH; widgetw = (int)widthByHeight; } // Don't upscale large-ish windows if (widgetw > window->width() && (window->width() > 300 || window->height() > 300)) { widgetw = window->width(); widgeth = window->height(); } } // Set the Widget's size int alignmentXoffset = 0; int alignmentYoffset = 0; if (i == 0 && h > widgeth) alignmentYoffset = h - widgeth; if (j == 0 && w > widgetw) alignmentXoffset = w - widgetw; QRect geom(availRect.x() + j *(w + spacing) + spacing + alignmentXoffset + xOffsetFromLastCol, availRect.y() + i *(h + spacing) + spacing + alignmentYoffset, widgetw, widgeth); geometryRects.append(geom); // Set the x offset for the next column if (alignmentXoffset == 0) xOffsetFromLastCol += widgetw - w; if (maxHeightInRow < widgeth) maxHeightInRow = widgeth; } maxRowHeights.append(maxHeightInRow); } int topOffset = 0; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { int pos = i * columns + j; if (pos >= windowlist.count()) break; EffectWindow* window = windowlist[pos]; QRect target = geometryRects[pos]; target.setY(target.y() + topOffset); // @Marrtin: any idea what this is good for? // DataHash::iterator winData = m_windowData.find(window); // if (winData != m_windowData.end()) // winData->slot = pos; motionManager.moveWindow(window, target); //qCDebug(KWINEFFECTS) << "Window '" << window->caption() << "' gets moved to (" << // mWindowData[window].area.left() << "; " << mWindowData[window].area.right() << // "), scale: " << mWindowData[window].scale << endl; } if (maxRowHeights[i] - h > 0) topOffset += maxRowHeights[i] - h; } } void PresentWindowsEffect::calculateWindowTransformationsNatural(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager) { // If windows do not overlap they scale into nothingness, fix by resetting. To reproduce // just have a single window on a Xinerama screen or have two windows that do not touch. // TODO: Work out why this happens, is most likely a bug in the manager. foreach (EffectWindow * w, windowlist) if (motionManager.transformedGeometry(w) == w->geometry()) motionManager.reset(w); if (windowlist.count() == 1) { // Just move the window to its original location to save time if (effects->clientArea(FullScreenArea, windowlist[0]).contains(windowlist[0]->geometry())) { motionManager.moveWindow(windowlist[0], windowlist[0]->geometry()); return; } } // As we are using pseudo-random movement (See "slot") we need to make sure the list // is always sorted the same way no matter which window is currently active. std::sort(windowlist.begin(), windowlist.end()); QRect area = effects->clientArea(ScreenArea, screen, effects->currentDesktop()); if (m_showPanel) // reserve space for the panel area = effects->clientArea(MaximizeArea, screen, effects->currentDesktop()); QRect bounds = area; int direction = 0; QHash targets; QHash directions; foreach (EffectWindow * w, windowlist) { bounds = bounds.united(w->geometry()); targets[w] = w->geometry(); // Reuse the unused "slot" as a preferred direction attribute. This is used when the window // is on the edge of the screen to try to use as much screen real estate as possible. directions[w] = direction; direction++; if (direction == 4) direction = 0; } // Iterate over all windows, if two overlap push them apart _slightly_ as we try to // brute-force the most optimal positions over many iterations. bool overlap; do { overlap = false; foreach (EffectWindow * w, windowlist) { QRect *target_w = &targets[w]; foreach (EffectWindow * e, windowlist) { if (w == e) continue; QRect *target_e = &targets[e]; if (target_w->adjusted(-5, -5, 5, 5).intersects(target_e->adjusted(-5, -5, 5, 5))) { overlap = true; // Determine pushing direction QPoint diff(target_e->center() - target_w->center()); // Prevent dividing by zero and non-movement if (diff.x() == 0 && diff.y() == 0) diff.setX(1); // Try to keep screen aspect ratio //if (bounds.height() / bounds.width() > area.height() / area.width()) // diff.setY(diff.y() / 2); //else // diff.setX(diff.x() / 2); // Approximate a vector of between 10px and 20px in magnitude in the same direction diff *= m_accuracy / double(diff.manhattanLength()); // Move both windows apart target_w->translate(-diff); target_e->translate(diff); // Try to keep the bounding rect the same aspect as the screen so that more // screen real estate is utilised. We do this by splitting the screen into nine // equal sections, if the window center is in any of the corner sections pull the // window towards the outer corner. If it is in any of the other edge sections // alternate between each corner on that edge. We don't want to determine it // randomly as it will not produce consistant locations when using the filter. // Only move one window so we don't cause large amounts of unnecessary zooming // in some situations. We need to do this even when expanding later just in case // all windows are the same size. // (We are using an old bounding rect for this, hopefully it doesn't matter) int xSection = (target_w->x() - bounds.x()) / (bounds.width() / 3); int ySection = (target_w->y() - bounds.y()) / (bounds.height() / 3); diff = QPoint(0, 0); if (xSection != 1 || ySection != 1) { // Remove this if you want the center to pull as well if (xSection == 1) xSection = (directions[w] / 2 ? 2 : 0); if (ySection == 1) ySection = (directions[w] % 2 ? 2 : 0); } if (xSection == 0 && ySection == 0) diff = QPoint(bounds.topLeft() - target_w->center()); if (xSection == 2 && ySection == 0) diff = QPoint(bounds.topRight() - target_w->center()); if (xSection == 2 && ySection == 2) diff = QPoint(bounds.bottomRight() - target_w->center()); if (xSection == 0 && ySection == 2) diff = QPoint(bounds.bottomLeft() - target_w->center()); if (diff.x() != 0 || diff.y() != 0) { diff *= m_accuracy / double(diff.manhattanLength()); target_w->translate(diff); } // Update bounding rect bounds = bounds.united(*target_w); bounds = bounds.united(*target_e); } } } } while (overlap); // Work out scaling by getting the most top-left and most bottom-right window coords. // The 20's and 10's are so that the windows don't touch the edge of the screen. double scale; if (bounds == area) scale = 1.0; // Don't add borders to the screen else if (area.width() / double(bounds.width()) < area.height() / double(bounds.height())) scale = (area.width() - 20) / double(bounds.width()); else scale = (area.height() - 20) / double(bounds.height()); // Make bounding rect fill the screen size for later steps bounds = QRect( bounds.x() - (area.width() - 20 - bounds.width() * scale) / 2 - 10 / scale, bounds.y() - (area.height() - 20 - bounds.height() * scale) / 2 - 10 / scale, area.width() / scale, area.height() / scale ); // Move all windows back onto the screen and set their scale QHash::iterator target = targets.begin(); while (target != targets.end()) { target->setRect((target->x() - bounds.x()) * scale + area.x(), (target->y() - bounds.y()) * scale + area.y(), target->width() * scale, target->height() * scale ); ++target; } // Try to fill the gaps by enlarging windows if they have the space if (m_fillGaps) { // Don't expand onto or over the border QRegion borderRegion(area.adjusted(-200, -200, 200, 200)); borderRegion ^= area.adjusted(10 / scale, 10 / scale, -10 / scale, -10 / scale); bool moved; do { moved = false; foreach (EffectWindow * w, windowlist) { QRect oldRect; QRect *target = &targets[w]; // This may cause some slight distortion if the windows are enlarged a large amount int widthDiff = m_accuracy; int heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height(); int xDiff = widthDiff / 2; // Also move a bit in the direction of the enlarge, allows the int yDiff = heightDiff / 2; // center windows to be enlarged if there is gaps on the side. // heightDiff (and yDiff) will be re-computed after each successful enlargement attempt // so that the error introduced in the window's aspect ratio is minimized // Attempt enlarging to the top-right oldRect = *target; target->setRect(target->x() + xDiff, target->y() - yDiff - heightDiff, target->width() + widthDiff, target->height() + heightDiff ); if (isOverlappingAny(w, targets, borderRegion)) *target = oldRect; else { moved = true; heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height(); yDiff = heightDiff / 2; } // Attempt enlarging to the bottom-right oldRect = *target; target->setRect( target->x() + xDiff, target->y() + yDiff, target->width() + widthDiff, target->height() + heightDiff ); if (isOverlappingAny(w, targets, borderRegion)) *target = oldRect; else { moved = true; heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height(); yDiff = heightDiff / 2; } // Attempt enlarging to the bottom-left oldRect = *target; target->setRect( target->x() - xDiff - widthDiff, target->y() + yDiff, target->width() + widthDiff, target->height() + heightDiff ); if (isOverlappingAny(w, targets, borderRegion)) *target = oldRect; else { moved = true; heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height(); yDiff = heightDiff / 2; } // Attempt enlarging to the top-left oldRect = *target; target->setRect( target->x() - xDiff - widthDiff, target->y() - yDiff - heightDiff, target->width() + widthDiff, target->height() + heightDiff ); if (isOverlappingAny(w, targets, borderRegion)) *target = oldRect; else moved = true; } } while (moved); // The expanding code above can actually enlarge windows over 1.0/2.0 scale, we don't like this // We can't add this to the loop above as it would cause a never-ending loop so we have to make // do with the less-than-optimal space usage with using this method. foreach (EffectWindow * w, windowlist) { QRect *target = &targets[w]; double scale = target->width() / double(w->width()); if (scale > 2.0 || (scale > 1.0 && (w->width() > 300 || w->height() > 300))) { scale = (w->width() > 300 || w->height() > 300) ? 1.0 : 2.0; target->setRect( target->center().x() - int(w->width() * scale) / 2, target->center().y() - int(w->height() * scale) / 2, w->width() * scale, w->height() * scale); } } } // Notify the motion manager of the targets foreach (EffectWindow * w, windowlist) motionManager.moveWindow(w, targets.value(w)); } bool PresentWindowsEffect::isOverlappingAny(EffectWindow *w, const QHash &targets, const QRegion &border) { QHash::const_iterator winTarget = targets.find(w); if (winTarget == targets.constEnd()) return false; if (border.intersects(*winTarget)) return true; // Is there a better way to do this? QHash::const_iterator target; for (target = targets.constBegin(); target != targets.constEnd(); ++target) { if (target == winTarget) continue; if (winTarget->adjusted(-5, -5, 5, 5).intersects(target->adjusted(-5, -5, 5, 5))) return true; } return false; } //----------------------------------------------------------------------------- // Activation void PresentWindowsEffect::setActive(bool active) { if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return; if (m_activated == active) return; m_activated = active; if (m_activated) { effects->setShowingDesktop(false); m_needInitialSelection = true; m_closeButtonCorner = (Qt::Corner)effects->kwinOption(KWin::CloseButtonCorner).toInt(); m_decalOpacity = 0.0; m_highlightedWindow = nullptr; m_windowFilter.clear(); if (!(m_doNotCloseWindows || m_closeView)) { m_closeView = new CloseWindowView(); connect(m_closeView, &EffectQuickView::repaintNeeded, this, []() { effects->addRepaintFull(); }); connect(m_closeView, &CloseWindowView::requestClose, this, &PresentWindowsEffect::closeWindow); } // Add every single window to m_windowData (Just calling [w] creates it) foreach (EffectWindow * w, effects->stackingOrder()) { DataHash::iterator winData; if ((winData = m_windowData.find(w)) != m_windowData.end()) { winData->visible = isVisibleWindow(w); continue; // Happens if we reactivate before the ending animation finishes } winData = m_windowData.insert(w, WindowData()); winData->visible = isVisibleWindow(w); winData->deleted = false; winData->referenced = false; winData->opacity = 0.0; if (w->isOnCurrentDesktop() && !w->isMinimized()) winData->opacity = 1.0; winData->highlight = 1.0; winData->textFrame = effects->effectFrame(EffectFrameUnstyled, false); QFont font; font.setBold(true); font.setPointSize(12); winData->textFrame->setFont(font); winData->iconFrame = effects->effectFrame(EffectFrameUnstyled, false); winData->iconFrame->setAlignment(Qt::AlignRight | Qt::AlignBottom); winData->iconFrame->setIcon(w->icon()); winData->iconFrame->setIconSize(QSize(32, 32)); } // Filter out special windows such as panels and taskbars foreach (EffectWindow * w, effects->stackingOrder()) { if (isSelectableWindow(w)) { m_motionManager.manage(w); } } if (m_motionManager.managedWindows().isEmpty() || ((m_motionManager.managedWindows().count() == 1) && m_motionManager.managedWindows().first()->isOnCurrentDesktop() && (m_ignoreMinimized || !m_motionManager.managedWindows().first()->isMinimized()))) { // No point triggering if there is nothing to do m_activated = false; DataHash::iterator i = m_windowData.begin(); while (i != m_windowData.end()) { delete i.value().textFrame; delete i.value().iconFrame; ++i; } m_windowData.clear(); m_motionManager.unmanageAll(); return; } // Create temporary input window to catch mouse events effects->startMouseInterception(this, Qt::PointingHandCursor); m_hasKeyboardGrab = effects->grabKeyboard(this); effects->setActiveFullScreenEffect(this); reCreateGrids(); rearrangeWindows(); setHighlightedWindow(effects->activeWindow()); foreach (EffectWindow * w, effects->stackingOrder()) { w->setData(WindowForceBlurRole, QVariant(true)); w->setData(WindowForceBackgroundContrastRole, QVariant(true)); } } else { m_needInitialSelection = false; if (m_highlightedWindow) effects->setElevatedWindow(m_highlightedWindow, false); // Fade in/out all windows EffectWindow *activeWindow = effects->activeWindow(); int desktop = effects->currentDesktop(); if (activeWindow && !activeWindow->isOnAllDesktops()) desktop = activeWindow->desktop(); foreach (EffectWindow * w, effects->stackingOrder()) { DataHash::iterator winData = m_windowData.find(w); if (winData != m_windowData.end()) winData->visible = (w->isOnDesktop(desktop) || w->isOnAllDesktops()) && !w->isMinimized(); } if (m_closeView) m_closeView->hide(); // Move all windows back to their original position foreach (EffectWindow * w, m_motionManager.managedWindows()) m_motionManager.moveWindow(w, w->geometry()); if (m_filterFrame) { m_filterFrame->free(); } m_windowFilter.clear(); m_selectedWindows.clear(); effects->stopMouseInterception(this); if (m_hasKeyboardGrab) effects->ungrabKeyboard(); m_hasKeyboardGrab = false; // destroy atom on manager window if (m_managerWindow) { if (m_mode == ModeSelectedDesktop && m_atomDesktop != XCB_ATOM_NONE) m_managerWindow->deleteProperty(m_atomDesktop); else if (m_mode == ModeWindowGroup && m_atomWindows != XCB_ATOM_NONE) m_managerWindow->deleteProperty(m_atomWindows); m_managerWindow = nullptr; } } effects->addRepaintFull(); // Trigger the first repaint } //----------------------------------------------------------------------------- // Filter box void PresentWindowsEffect::updateFilterFrame() { QRect area = effects->clientArea(ScreenArea, effects->activeScreen(), effects->currentDesktop()); if (!m_filterFrame) { m_filterFrame = effects->effectFrame(EffectFrameStyled, false); QFont font; font.setPointSize(font.pointSize() * 2); font.setBold(true); m_filterFrame->setFont(font); } m_filterFrame->setPosition(QPoint(area.x() + area.width() / 2, area.y() + area.height() / 2)); m_filterFrame->setText(i18n("Filter:\n%1", m_windowFilter)); } //----------------------------------------------------------------------------- // Helper functions bool PresentWindowsEffect::isSelectableWindow(EffectWindow *w) { if (!w->isOnCurrentActivity()) return false; if (w->isSpecialWindow() || w->isUtility()) return false; if (w->isDeleted()) return false; if (!w->acceptsFocus()) return false; if (w->isSkipSwitcher()) return false; if (m_ignoreMinimized && w->isMinimized()) return false; switch(m_mode) { default: case ModeAllDesktops: return true; case ModeCurrentDesktop: return w->isOnCurrentDesktop(); case ModeSelectedDesktop: return w->isOnDesktop(m_desktop); case ModeWindowGroup: return m_selectedWindows.contains(w); case ModeWindowClass: return m_class == w->windowClass(); } } bool PresentWindowsEffect::isVisibleWindow(EffectWindow *w) { if (w->isDesktop()) return true; return isSelectableWindow(w); } void PresentWindowsEffect::setHighlightedWindow(EffectWindow *w) { if (w == m_highlightedWindow || (w != nullptr && !m_motionManager.isManaging(w))) return; if (m_closeView) m_closeView->hide(); if (m_highlightedWindow) { effects->setElevatedWindow(m_highlightedWindow, false); m_highlightedWindow->addRepaintFull(); // Trigger the first repaint } m_highlightedWindow = w; if (m_highlightedWindow) { effects->setElevatedWindow(m_highlightedWindow, true); m_highlightedWindow->addRepaintFull(); // Trigger the first repaint } updateCloseWindow(); } void PresentWindowsEffect::updateCloseWindow() { if (!m_closeView || m_doNotCloseWindows) return; if (!m_activated || !m_highlightedWindow || m_highlightedWindow->isDesktop()) { m_closeView->hide(); return; } if (m_closeView->isVisible()) return; const QRectF rect(m_motionManager.targetGeometry(m_highlightedWindow)); if (2*m_closeView->geometry().width() > rect.width() && 2*m_closeView->geometry().height() > rect.height()) { // not for tiny windows (eg. with many windows) - they might become unselectable m_closeView->hide(); return; } QRect cvr(QPoint(0,0), m_closeView->size()); switch (m_closeButtonCorner) { case Qt::TopLeftCorner: default: cvr.moveTopLeft(rect.topLeft().toPoint()); break; case Qt::TopRightCorner: cvr.moveTopRight(rect.topRight().toPoint()); break; case Qt::BottomLeftCorner: cvr.moveBottomLeft(rect.bottomLeft().toPoint()); break; case Qt::BottomRightCorner: cvr.moveBottomRight(rect.bottomRight().toPoint()); break; } m_closeView->setGeometry(cvr); if (rect.contains(effects->cursorPos())) { m_closeView->show(); m_closeView->disarm(); } else m_closeView->hide(); } void PresentWindowsEffect::closeWindow() { if (m_highlightedWindow) m_highlightedWindow->closeWindow(); } EffectWindow* PresentWindowsEffect::relativeWindow(EffectWindow *w, int xdiff, int ydiff, bool wrap) const { if (!w) return m_motionManager.managedWindows().first(); // TODO: Is it possible to select hidden windows? EffectWindow* next; QRect area = effects->clientArea(FullArea, 0, effects->currentDesktop()); QRect detectRect; // Detect across the width of the desktop if (xdiff != 0) { if (xdiff > 0) { // Detect right for (int i = 0; i < xdiff; i++) { QRectF wArea = m_motionManager.transformedGeometry(w); detectRect = QRect(0, wArea.y(), area.width(), wArea.height()); next = nullptr; foreach (EffectWindow * e, m_motionManager.managedWindows()) { DataHash::const_iterator winData = m_windowData.find(e); if (winData == m_windowData.end() || !winData->visible) continue; QRectF eArea = m_motionManager.transformedGeometry(e); if (eArea.intersects(detectRect) && eArea.x() > wArea.x()) { if (next == nullptr) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.x() < nArea.x()) next = e; } } } if (next == nullptr) { if (wrap) // We are at the right-most window, now get the left-most one to wrap return relativeWindow(w, -1000, 0, false); break; // No more windows to the right } w = next; } return w; } else { // Detect left for (int i = 0; i < -xdiff; i++) { QRectF wArea = m_motionManager.transformedGeometry(w); detectRect = QRect(0, wArea.y(), area.width(), wArea.height()); next = nullptr; foreach (EffectWindow * e, m_motionManager.managedWindows()) { DataHash::const_iterator winData = m_windowData.find(e); if (winData == m_windowData.end() || !winData->visible) continue; QRectF eArea = m_motionManager.transformedGeometry(e); if (eArea.intersects(detectRect) && eArea.x() + eArea.width() < wArea.x() + wArea.width()) { if (next == nullptr) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.x() + eArea.width() > nArea.x() + nArea.width()) next = e; } } } if (next == nullptr) { if (wrap) // We are at the left-most window, now get the right-most one to wrap return relativeWindow(w, 1000, 0, false); break; // No more windows to the left } w = next; } return w; } } // Detect across the height of the desktop if (ydiff != 0) { if (ydiff > 0) { // Detect down for (int i = 0; i < ydiff; i++) { QRectF wArea = m_motionManager.transformedGeometry(w); detectRect = QRect(wArea.x(), 0, wArea.width(), area.height()); next = nullptr; foreach (EffectWindow * e, m_motionManager.managedWindows()) { DataHash::const_iterator winData = m_windowData.find(e); if (winData == m_windowData.end() || !winData->visible) continue; QRectF eArea = m_motionManager.transformedGeometry(e); if (eArea.intersects(detectRect) && eArea.y() > wArea.y()) { if (next == nullptr) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.y() < nArea.y()) next = e; } } } if (next == nullptr) { if (wrap) // We are at the bottom-most window, now get the top-most one to wrap return relativeWindow(w, 0, -1000, false); break; // No more windows to the bottom } w = next; } return w; } else { // Detect up for (int i = 0; i < -ydiff; i++) { QRectF wArea = m_motionManager.transformedGeometry(w); detectRect = QRect(wArea.x(), 0, wArea.width(), area.height()); next = nullptr; foreach (EffectWindow * e, m_motionManager.managedWindows()) { DataHash::const_iterator winData = m_windowData.find(e); if (winData == m_windowData.end() || !winData->visible) continue; QRectF eArea = m_motionManager.transformedGeometry(e); if (eArea.intersects(detectRect) && eArea.y() + eArea.height() < wArea.y() + wArea.height()) { if (next == nullptr) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.y() + eArea.height() > nArea.y() + nArea.height()) next = e; } } } if (next == nullptr) { if (wrap) // We are at the top-most window, now get the bottom-most one to wrap return relativeWindow(w, 0, 1000, false); break; // No more windows to the top } w = next; } return w; } } abort(); // Should never get here } EffectWindow* PresentWindowsEffect::findFirstWindow() const { EffectWindow *topLeft = nullptr; QRectF topLeftGeometry; foreach (EffectWindow * w, m_motionManager.managedWindows()) { DataHash::const_iterator winData = m_windowData.find(w); if (winData == m_windowData.end()) continue; QRectF geometry = m_motionManager.transformedGeometry(w); if (winData->visible == false) continue; // Not visible if (winData->deleted) continue; // Window has been closed if (topLeft == nullptr) { topLeft = w; topLeftGeometry = geometry; } else if (geometry.x() < topLeftGeometry.x() || geometry.y() < topLeftGeometry.y()) { topLeft = w; topLeftGeometry = geometry; } } return topLeft; } void PresentWindowsEffect::globalShortcutChanged(QAction *action, const QKeySequence& seq) { if (action->objectName() == QStringLiteral("Expose")) { shortcut.clear(); shortcut.append(seq); } else if (action->objectName() == QStringLiteral("ExposeAll")) { shortcutAll.clear(); shortcutAll.append(seq); } else if (action->objectName() == QStringLiteral("ExposeClass")) { shortcutClass.clear(); shortcutClass.append(seq); } } bool PresentWindowsEffect::isActive() const { return (m_activated || m_motionManager.managingWindows()) && !effects->isScreenLocked(); } void PresentWindowsEffect::reCreateGrids() { m_gridSizes.clear(); for (int i = 0; i < effects->numScreens(); ++i) { m_gridSizes.append(GridSize()); } rearrangeWindows(); } CloseWindowView::CloseWindowView(QObject *parent) : EffectQuickScene(parent) { setSource(QUrl(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/presentwindows/main.qml")))); if (QQuickItem *item = rootItem()) { connect(item, SIGNAL(clicked()), this, SLOT(clicked())); setGeometry(QRect(QPoint(), QSize(item->implicitWidth(), item->implicitHeight()))); } m_armTimer.restart(); } void CloseWindowView::clicked() { // 50ms until the window is elevated (seen!) and 300ms more to be "realized" by the user. if (m_armTimer.hasExpired(350)) { emit requestClose(); } } void CloseWindowView::disarm() { m_armTimer.restart(); } } // namespace diff --git a/x11client.cpp b/x11client.cpp index f6f04ecec..853209606 100644 --- a/x11client.cpp +++ b/x11client.cpp @@ -1,5032 +1,5048 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "x11client.h" // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client_machine.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "effects.h" #include "focuschain.h" #include "geometrytip.h" #include "group.h" #include "netinfo.h" #include "screens.h" #include "shadow.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "workspace.h" #include "screenedge.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include // KDE #include #include #include #include // Qt #include #include #include #include #include #include #include // xcb #include // system #include // c++ #include // Put all externs before the namespace statement to allow the linker // to resolve them properly namespace KWin { const long ClientWinMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | // need this, too! XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // window types that are supported as normal windows (i.e. KWin actually manages them) const NET::WindowTypes SUPPORTED_MANAGED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask /*| NET::OverrideMask*/ | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask | NET::NotificationMask | NET::OnScreenDisplayMask | NET::CriticalNotificationMask; // Creating a client: // - only by calling Workspace::createClient() // - it creates a new client and calls manage() for it // // Destroying a client: // - destroyClient() - only when the window itself has been destroyed // - releaseWindow() - the window is kept, only the client itself is destroyed /** * \class Client x11client.h * \brief The Client class encapsulates a window decoration frame. */ /** * This ctor is "dumb" - it only initializes data. All the real initialization * is done in manage(). */ X11Client::X11Client() : AbstractClient() , m_client() , m_wrapper() , m_frame() , m_activityUpdatesBlocked(false) , m_blockedActivityUpdatesRequireTransients(false) , m_moveResizeGrabWindow() , move_resize_has_keyboard_grab(false) , m_managed(false) , m_transientForId(XCB_WINDOW_NONE) , m_originalTransientForId(XCB_WINDOW_NONE) , shade_below(nullptr) , m_motif(atoms->motif_wm_hints) , blocks_compositing(false) , shadeHoverTimer(nullptr) , m_colormap(XCB_COLORMAP_NONE) , in_group(nullptr) , ping_timer(nullptr) , m_killHelperPID(0) , m_pingTimestamp(XCB_TIME_CURRENT_TIME) , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet , allowed_actions() , shade_geometry_change(false) , sm_stacking_order(-1) , activitiesDefined(false) , sessionActivityOverride(false) , needsXWindowMove(false) , m_decoInputExtent() , m_focusOutTimer(nullptr) { // TODO: Do all as initialization m_syncRequest.counter = m_syncRequest.alarm = XCB_NONE; m_syncRequest.timeout = m_syncRequest.failsafeTimeout = nullptr; m_syncRequest.lastTimestamp = xTime(); m_syncRequest.isPending = false; // Set the initial mapping state mapping_state = Withdrawn; info = nullptr; shade_mode = ShadeNone; deleting = false; m_fullscreenMode = FullScreenNone; hidden = false; noborder = false; app_noborder = false; ignore_focus_stealing = false; check_active_modal = false; max_mode = MaximizeRestore; //Client to workspace connections require that each //client constructed be connected to the workspace wrapper m_frameGeometry = QRect(0, 0, 100, 100); // So that decorations don't start with size being (0,0) connect(clientMachine(), &ClientMachine::localhostChanged, this, &X11Client::updateCaption); connect(options, &Options::condensedTitleChanged, this, &X11Client::updateCaption); connect(this, &X11Client::moveResizeCursorChanged, this, [this] (CursorShape cursor) { xcb_cursor_t nativeCursor = Cursor::x11Cursor(cursor); m_frame.defineCursor(nativeCursor); if (m_decoInputExtent.isValid()) m_decoInputExtent.defineCursor(nativeCursor); if (isMoveResize()) { // changing window attributes doesn't change cursor if there's pointer grab active xcb_change_active_pointer_grab(connection(), nativeCursor, xTime(), XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW); } }); // SELI TODO: Initialize xsizehints?? } /** * "Dumb" destructor. */ X11Client::~X11Client() { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } if (m_syncRequest.alarm != XCB_NONE) { xcb_sync_destroy_alarm(connection(), m_syncRequest.alarm); } Q_ASSERT(!isMoveResize()); Q_ASSERT(m_client == XCB_WINDOW_NONE); Q_ASSERT(m_wrapper == XCB_WINDOW_NONE); Q_ASSERT(m_frame == XCB_WINDOW_NONE); Q_ASSERT(!check_active_modal); for (auto it = m_connections.constBegin(); it != m_connections.constEnd(); ++it) { disconnect(*it); } } // Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly void X11Client::deleteClient(X11Client *c) { delete c; } /** * Releases the window. The client has done its job and the window is still existing. */ void X11Client::releaseWindow(bool on_shutdown) { Q_ASSERT(!deleting); deleting = true; #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox->isDisplayed() && tabBox->currentClient() == this) { tabBox->nextPrev(true); } #endif destroyWindowManagementInterface(); Deleted* del = nullptr; if (!on_shutdown) { del = Deleted::create(this); } if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); // Grab X during the release to make removing of properties, setting to withdrawn state // and repareting to root an atomic operation (https://lists.kde.org/?l=kde-devel&m=116448102901184&w=2) grabXServer(); exportMappingState(XCB_ICCCM_WM_STATE_WITHDRAWN); setModal(false); // Otherwise its mainwindow wouldn't get focus hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags) if (!on_shutdown) workspace()->clientHidden(this); m_frame.unmap(); // Destroying decoration would cause ugly visual effect destroyDecoration(); cleanGrouping(); if (!on_shutdown) { workspace()->removeClient(this); // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7) info->setDesktop(0); info->setState(NET::States(), info->state()); // Reset all state flags } xcb_connection_t *c = connection(); m_client.deleteProperty(atoms->kde_net_wm_user_creation_time); m_client.deleteProperty(atoms->net_frame_extents); m_client.deleteProperty(atoms->kde_net_wm_frame_strut); m_client.reparent(rootWindow(), m_bufferGeometry.x(), m_bufferGeometry.y()); xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client); m_client.selectInput(XCB_EVENT_MASK_NO_EVENT); if (on_shutdown) // Map the window, so it can be found after another WM is started m_client.map(); // TODO: Preserve minimized, shaded etc. state? else // Make sure it's not mapped if the app unmapped it (#65279). The app // may do map+unmap before we initially map the window by calling rawShow() from manage(). m_client.unmap(); m_client.reset(); m_wrapper.reset(); m_frame.reset(); unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry if (!on_shutdown) { disownDataPassedToDeleted(); del->unrefWindow(); } deleteClient(this); ungrabXServer(); } /** * Like releaseWindow(), but this one is called when the window has been already destroyed * (E.g. The application closed it) */ void X11Client::destroyClient() { Q_ASSERT(!deleting); deleting = true; #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) { tabBox->nextPrev(true); } #endif destroyWindowManagementInterface(); Deleted* del = Deleted::create(this); if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(ReleaseReason::Destroyed); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); setModal(false); hidden = true; // So that it's not considered visible anymore workspace()->clientHidden(this); destroyDecoration(); cleanGrouping(); workspace()->removeClient(this); m_client.reset(); // invalidate m_wrapper.reset(); m_frame.reset(); unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry disownDataPassedToDeleted(); del->unrefWindow(); deleteClient(this); } /** * Manages the clients. This means handling the very first maprequest: * reparenting, initial geometry, initial state, placement, etc. * Returns false if KWin is not going to manage this window. */ bool X11Client::manage(xcb_window_t w, bool isMapped) { StackingUpdatesBlocker stacking_blocker(workspace()); Xcb::WindowAttributes attr(w); Xcb::WindowGeometry windowGeometry(w); if (attr.isNull() || windowGeometry.isNull()) { return false; } // From this place on, manage() must not return false blockGeometryUpdates(); setPendingGeometryUpdate(PendingGeometryForced); // Force update when finishing with geometry changes embedClient(w, attr->visual, attr->colormap, windowGeometry->depth); m_visual = attr->visual; bit_depth = windowGeometry->depth; // SELI TODO: Order all these things in some sane manner const NET::Properties properties = NET::WMDesktop | NET::WMState | NET::WMWindowType | NET::WMStrut | NET::WMName | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMIconName; const NET::Properties2 properties2 = NET::WM2BlockCompositing | NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2UserTime | NET::WM2StartupId | NET::WM2ExtendedStrut | NET::WM2Opacity | NET::WM2FullscreenMonitors | NET::WM2FrameOverlap | NET::WM2GroupLeader | NET::WM2Urgency | NET::WM2Input | NET::WM2Protocols | NET::WM2InitialMappingState | NET::WM2IconPixmap | NET::WM2OpaqueRegion | NET::WM2DesktopFileName | NET::WM2GTKFrameExtents; auto wmClientLeaderCookie = fetchWmClientLeader(); auto skipCloseAnimationCookie = fetchSkipCloseAnimation(); auto showOnScreenEdgeCookie = fetchShowOnScreenEdge(); auto colorSchemeCookie = fetchColorScheme(); auto firstInTabBoxCookie = fetchFirstInTabBox(); auto transientCookie = fetchTransient(); auto activitiesCookie = fetchActivities(); auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName(); auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath(); m_geometryHints.init(window()); m_motif.init(window()); info = new WinInfo(this, m_client, rootWindow(), properties, properties2); if (isDesktop() && bit_depth == 32) { // force desktop windows to be opaque. It's a desktop after all, there is no window below bit_depth = 24; } // If it's already mapped, ignore hint bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic); m_colormap = attr->colormap; getResourceClass(); readWmClientLeader(wmClientLeaderCookie); getWmClientMachine(); getSyncCounter(); // First only read the caption text, so that setupWindowRules() can use it for matching, // and only then really set the caption using setCaption(), which checks for duplicates etc. // and also relies on rules already existing cap_normal = readName(); setupWindowRules(false); setCaption(cap_normal, true); connect(this, &X11Client::windowClassChanged, this, &X11Client::evaluateWindowRules); if (Xcb::Extensions::self()->isShapeAvailable()) xcb_shape_select_input(connection(), window(), true); detectShape(window()); detectNoBorder(); fetchIconicName(); setClientFrameExtents(info->gtkFrameExtents()); // Needs to be done before readTransient() because of reading the group checkGroup(); updateUrgency(); updateAllowedActions(); // Group affects isMinimizable() setModal((info->state() & NET::Modal) != 0); // Needs to be valid before handling groups readTransientProperty(transientCookie); setDesktopFileName(rules()->checkDesktopFile(QByteArray(info->desktopFileName()), true).toUtf8()); getIcons(); connect(this, &X11Client::desktopFileNameChanged, this, &X11Client::getIcons); m_geometryHints.read(); getMotifHints(); getWmOpaqueRegion(); readSkipCloseAnimation(skipCloseAnimationCookie); // TODO: Try to obey all state information from info->state() setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0); setSkipPager((info->state() & NET::SkipPager) != 0); setSkipSwitcher((info->state() & NET::SkipSwitcher) != 0); readFirstInTabBox(firstInTabBoxCookie); setupCompositing(); KStartupInfoId asn_id; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); // Make sure that the input window is created before we update the stacking order updateInputWindow(); workspace()->updateClientLayer(this); SessionInfo* session = workspace()->takeSessionInfo(this); if (session) { init_minimize = session->minimized; noborder = session->noBorder; } setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true)); init_minimize = rules()->checkMinimize(init_minimize, !isMapped); noborder = rules()->checkNoBorder(noborder, !isMapped); readActivities(activitiesCookie); // Initial desktop placement int desk = 0; if (session) { desk = session->desktop; if (session->onAllDesktops) desk = NET::OnAllDesktops; setOnActivities(session->activities); } else { // If this window is transient, ensure that it is opened on the // same window as its parent. this is necessary when an application // starts up on a different desktop than is currently displayed if (isTransient()) { auto mainclients = mainClients(); bool on_current = false; bool on_all = false; AbstractClient* maincl = nullptr; // This is slightly duplicated from Placement::placeOnMainWindow() for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) { if (mainclients.count() > 1 && // A group-transient (*it)->isSpecialWindow() && // Don't consider toolbars etc when placing !(info->state() & NET::Modal)) // except when it's modal (blocks specials as well) continue; maincl = *it; if ((*it)->isOnCurrentDesktop()) on_current = true; if ((*it)->isOnAllDesktops()) on_all = true; } if (on_all) desk = NET::OnAllDesktops; else if (on_current) desk = VirtualDesktopManager::self()->current(); else if (maincl != nullptr) desk = maincl->desktop(); if (maincl) setOnActivities(maincl->activities()); } else { // a transient shall appear on its leader and not drag that around if (info->desktop()) desk = info->desktop(); // Window had the initial desktop property, force it if (desktop() == 0 && asn_valid && asn_data.desktop() != 0) desk = asn_data.desktop(); } #ifdef KWIN_BUILD_ACTIVITIES if (Activities::self() && !isMapped && !noborder && isNormalWindow() && !activitiesDefined) { //a new, regular window, when we're not recovering from a crash, //and it hasn't got an activity. let's try giving it the current one. //TODO: decide whether to keep this before the 4.6 release //TODO: if we are keeping it (at least as an option), replace noborder checking //with a public API for setting windows to be on all activities. //something like KWindowSystem::setOnAllActivities or //KActivityConsumer::setOnAllActivities setOnActivity(Activities::self()->current(), true); } #endif } if (desk == 0) // Assume window wants to be visible on the current desktop desk = isDesktop() ? static_cast(NET::OnAllDesktops) : VirtualDesktopManager::self()->current(); desk = rules()->checkDesktop(desk, !isMapped); if (desk != NET::OnAllDesktops) // Do range check desk = qBound(1, desk, static_cast(VirtualDesktopManager::self()->count())); setDesktop(desk); info->setDesktop(desk); workspace()->updateOnAllDesktopsOfTransients(this); // SELI TODO //onAllDesktopsChange(); // Decoration doesn't exist here yet QString activitiesList; activitiesList = rules()->checkActivity(activitiesList, !isMapped); if (!activitiesList.isEmpty()) setOnActivities(activitiesList.split(QStringLiteral(","))); QRect geom(windowGeometry.rect()); bool placementDone = false; if (session) geom = session->geometry; QRect area; bool partial_keep_in_area = isMapped || session; if (isMapped || session) { area = workspace()->clientArea(FullArea, geom.center(), desktop()); checkOffscreenPosition(&geom, area); } else { int screen = asn_data.xinerama() == -1 ? screens()->current() : asn_data.xinerama(); screen = rules()->checkScreen(screen, !isMapped); area = workspace()->clientArea(PlacementArea, screens()->geometry(screen).center(), desktop()); } if (isDesktop()) // KWin doesn't manage desktop windows placementDone = true; bool usePosition = false; if (isMapped || session || placementDone) placementDone = true; // Use geometry else if (isTransient() && !isUtility() && !isDialog() && !isSplash()) usePosition = true; else if (isTransient() && !hasNETSupport()) usePosition = true; else if (isDialog() && hasNETSupport()) { // If the dialog is actually non-NETWM transient window, don't try to apply placement to it, // it breaks with too many things (xmms, display) if (mainClients().count() >= 1) { #if 1 // #78082 - Ok, it seems there are after all some cases when an application has a good // reason to specify a position for its dialog. Too bad other WMs have never bothered // with placement for dialogs, so apps always specify positions for their dialogs, // including such silly positions like always centered on the screen or under mouse. // Using ignoring requested position in window-specific settings helps, and now // there's also _NET_WM_FULL_PLACEMENT. usePosition = true; #else ; // Force using placement policy #endif } else usePosition = true; } else if (isSplash()) ; // Force using placement policy else usePosition = true; if (!rules()->checkIgnoreGeometry(!usePosition, true)) { if (m_geometryHints.hasPosition()) { placementDone = true; // Disobey xinerama placement option for now (#70943) area = workspace()->clientArea(PlacementArea, geom.center(), desktop()); } } if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) placementDone = false; // Weird, do not trust. if (placementDone) { QPoint position = geom.topLeft(); // Session contains the position of the frame geometry before gravitating. if (!session) { position = clientPosToFramePos(position); } move(position); } // Create client group if the window will have a decoration bool dontKeepInArea = false; readColorScheme(colorSchemeCookie); readApplicationMenuServiceName(applicationMenuServiceNameCookie); readApplicationMenuObjectPath(applicationMenuObjectPathCookie); updateDecoration(false); // Also gravitates // TODO: Is CentralGravity right here, when resizing is done after gravitating? plainResize(rules()->checkSize(sizeForClientSize(geom.size()), !isMapped)); QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped); if (forced_pos != invalidPoint) { move(forced_pos); placementDone = true; // Don't keep inside workarea if the window has specially configured position partial_keep_in_area = true; area = workspace()->clientArea(FullArea, geom.center(), desktop()); } if (!placementDone) { // Placement needs to be after setting size Placement::self()->place(this, area); // The client may have been moved to another screen, update placement area. area = workspace()->clientArea(PlacementArea, this); dontKeepInArea = true; placementDone = true; } // bugs #285967, #286146, #183694 // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully) // Maximization for oversized windows must happen NOW. // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained // to the combo of all screen MINUS all struts on the edges // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well. if (!session) { // has a better handling of this setGeometryRestore(frameGeometry()); // Remember restore geometry if (isMaximizable() && (width() >= area.width() || height() >= area.height())) { // Window is too large for the screen, maximize in the // directions necessary const QSize ss = workspace()->clientArea(ScreenArea, area.center(), desktop()).size(); const QRect fsa = workspace()->clientArea(FullArea, geom.center(), desktop()); const QSize cs = clientSize(); int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0); if (width() >= area.width()) pseudo_max |= MaximizeHorizontal; if (height() >= area.height()) pseudo_max |= MaximizeVertical; // heuristics: // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen) // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an // attempt for maximization, but just constrain the size (the window simply wants to be bigger) // NOTICE // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller // than the workspace") but gtk / gimp seems to store it's size including the decoration, // thus a former maximized window wil become non-maximized bool keepInFsArea = false; if (width() < fsa.width() && (cs.width() > ss.width()+1)) { pseudo_max &= ~MaximizeHorizontal; keepInFsArea = true; } if (height() < fsa.height() && (cs.height() > ss.height()+1)) { pseudo_max &= ~MaximizeVertical; keepInFsArea = true; } if (pseudo_max != MaximizeRestore) { maximize((MaximizeMode)pseudo_max); // from now on, care about maxmode, since the maximization call will override mode for fix aspects dontKeepInArea |= (max_mode == MaximizeFull); QRect savedGeometry; // Use placement when unmaximizing ... if (!(max_mode & MaximizeVertical)) { savedGeometry.setY(y()); // ...but only for horizontal direction savedGeometry.setHeight(height()); } if (!(max_mode & MaximizeHorizontal)) { savedGeometry.setX(x()); // ...but only for vertical direction savedGeometry.setWidth(width()); } setGeometryRestore(savedGeometry); } if (keepInFsArea) keepInArea(fsa, partial_keep_in_area); } } if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea) keepInArea(area, partial_keep_in_area); updateShape(); // CT: Extra check for stupid jdk 1.3.1. But should make sense in general // if client has initial state set to Iconic and is transient with a parent // window that is not Iconic, set init_state to Normal if (init_minimize && isTransient()) { auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) init_minimize = false; // SELI TODO: Even e.g. for NET::Utility? } // If a dialog is shown for minimized window, minimize it too if (!init_minimize && isTransient() && mainClients().count() > 0 && workspace()->sessionManager()->state() != SessionState::Saving) { bool visible_parent = false; // Use allMainClients(), to include also main clients of group transients // that have been optimized out in X11Client::checkGroupTransients() auto mainclients = allMainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) visible_parent = true; if (!visible_parent) { init_minimize = true; demandAttention(); } } if (init_minimize) minimize(true); // No animation // Other settings from the previous session if (session) { // Session restored windows are not considered to be new windows WRT rules, // I.e. obey only forcing rules setKeepAbove(session->keepAbove); setKeepBelow(session->keepBelow); setOriginalSkipTaskbar(session->skipTaskbar); setSkipPager(session->skipPager); setSkipSwitcher(session->skipSwitcher); setShade(session->shaded ? ShadeNormal : ShadeNone); setOpacity(session->opacity); setGeometryRestore(session->restore); if (session->maximized != MaximizeRestore) { maximize(MaximizeMode(session->maximized)); } if (session->fullscreen != FullScreenNone) { setFullScreen(true, false); geom_fs_restore = session->fsrestore; } QRect checkedGeometryRestore = geometryRestore(); checkOffscreenPosition(&checkedGeometryRestore, area); checkOffscreenPosition(&geom_fs_restore, area); setGeometryRestore(checkedGeometryRestore); } else { // Window may want to be maximized // done after checking that the window isn't larger than the workarea, so that // the restore geometry from the checks above takes precedence, and window // isn't restored larger than the workarea MaximizeMode maxmode = static_cast( ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0)); MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped); // Either hints were set to maximize, or is forced to maximize, // or is forced to non-maximize and hints were set to maximize if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore) maximize(forced_maxmode); // Read other initial states setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped)); setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped)); setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped)); setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped)); setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped)); setSkipSwitcher(rules()->checkSkipSwitcher(info->state() & NET::SkipSwitcher, !isMapped)); if (info->state() & NET::DemandsAttention) demandAttention(); if (info->state() & NET::Modal) setModal(true); setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false); } updateAllowedActions(true); // Set initial user time directly m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : nullptr, asn_valid ? &asn_data : nullptr, session); group()->updateUserTime(m_userTime); // And do what X11Client::updateUserTime() does // This should avoid flicker, because real restacking is done // only after manage() finishes because of blocking, but the window is shown sooner m_frame.lower(); if (session && session->stackingOrder != -1) { sm_stacking_order = session->stackingOrder; workspace()->restoreSessionStackingOrder(this); } if (compositing()) // Sending ConfigureNotify is done when setting mapping state below, // Getting the first sync response means window is ready for compositing sendSyncRequest(); else ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393 if (isShown(true)) { bool allow; if (session) allow = session->active && (!workspace()->wasUserInteraction() || workspace()->activeClient() == nullptr || workspace()->activeClient()->isDesktop()); else allow = workspace()->allowClientActivation(this, userTime(), false); const bool isSessionSaving = workspace()->sessionManager()->state() == SessionState::Saving; // If session saving, force showing new windows (i.e. "save file?" dialogs etc.) // also force if activation is allowed if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || isSessionSaving )) VirtualDesktopManager::self()->setCurrent( desktop()); // If the window is on an inactive activity during session saving, temporarily force it to show. if( !isMapped && !session && isSessionSaving && !isOnCurrentActivity()) { setSessionActivityOverride( true ); foreach( AbstractClient* c, mainClients()) { if (X11Client *mc = dynamic_cast(c)) { mc->setSessionActivityOverride(true); } } } if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0)) workspace()->restackClientUnderActive(this); updateVisibility(); if (!isMapped) { if (allow && isOnCurrentDesktop()) { if (!isSpecialWindow()) if (options->focusPolicyIsReasonable() && wantsTabFocus()) workspace()->requestFocus(this); } else if (!session && !isSpecialWindow()) demandAttention(); } } else updateVisibility(); Q_ASSERT(mapping_state != Withdrawn); m_managed = true; blockGeometryUpdates(false); if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) { // No known user time, set something old m_userTime = xTime() - 1000000; if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) // Let's be paranoid m_userTime = xTime() - 1000000 + 10; } //sendSyntheticConfigureNotify(); // Done when setting mapping state delete session; discardTemporaryRules(); applyWindowRules(); // Just in case RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules updateWindowRules(Rules::All); // Was blocked while !isManaged() setBlockingCompositing(info->isBlockingCompositing()); readShowOnScreenEdge(showOnScreenEdgeCookie); // Forward all opacity values to the frame in case there'll be other CM running. connect(Compositor::self(), &Compositor::compositingToggled, this, [this](bool active) { if (active) { return; } if (opacity() == 1.0) { return; } NETWinInfo info(connection(), frameId(), rootWindow(), NET::Properties(), NET::Properties2()); info.setOpacity(static_cast(opacity() * 0xffffffff)); } ); // TODO: there's a small problem here - isManaged() depends on the mapping state, // but this client is not yet in Workspace's client list at this point, will // be only done in addClient() emit clientManaging(this); return true; } // Called only from manage() void X11Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth) { Q_ASSERT(m_client == XCB_WINDOW_NONE); Q_ASSERT(frameId() == XCB_WINDOW_NONE); Q_ASSERT(m_wrapper == XCB_WINDOW_NONE); m_client.reset(w, false); const uint32_t zero_value = 0; xcb_connection_t *conn = connection(); // We don't want the window to be destroyed when we quit xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client); m_client.selectInput(zero_value); m_client.unmap(); m_client.setBorderWidth(zero_value); // Note: These values must match the order in the xcb_cw_t enum const uint32_t cw_values[] = { 0, // back_pixmap 0, // border_pixel colormap, // colormap Cursor::x11Cursor(Qt::ArrowCursor) }; const uint32_t cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP | XCB_CW_CURSOR; const uint32_t common_event_mask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; const uint32_t frame_event_mask = common_event_mask | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_VISIBILITY_CHANGE; const uint32_t wrapper_event_mask = common_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; const uint32_t client_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_COLOR_MAP_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE; // Create the frame window xcb_window_t frame = xcb_generate_id(conn); xcb_create_window(conn, depth, frame, rootWindow(), 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); m_frame.reset(frame); setWindowHandles(m_client); // Create the wrapper window xcb_window_t wrapperId = xcb_generate_id(conn); xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); m_wrapper.reset(wrapperId); m_client.reparent(m_wrapper); // We could specify the event masks when we create the windows, but the original // Xlib code didn't. Let's preserve that behavior here for now so we don't end up // receiving any unexpected events from the wrapper creation or the reparenting. m_frame.selectInput(frame_event_mask); m_wrapper.selectInput(wrapper_event_mask); m_client.selectInput(client_event_mask); updateMouseGrab(); } void X11Client::updateInputWindow() { if (!Xcb::Extensions::self()->isShapeInputAvailable()) return; QRegion region; if (!noBorder() && isDecorated()) { const QMargins &r = decoration()->resizeOnlyBorders(); const int left = r.left(); const int top = r.top(); const int right = r.right(); const int bottom = r.bottom(); if (left != 0 || top != 0 || right != 0 || bottom != 0) { region = QRegion(-left, -top, decoration()->size().width() + left + right, decoration()->size().height() + top + bottom); region = region.subtracted(decoration()->rect()); } } if (region.isEmpty()) { m_decoInputExtent.reset(); return; } QRect bounds = region.boundingRect(); input_offset = bounds.topLeft(); // Move the bounding rect to screen coordinates bounds.translate(frameGeometry().topLeft()); // Move the region to input window coordinates region.translate(-input_offset); if (!m_decoInputExtent.isValid()) { const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; const uint32_t values[] = {true, XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION }; m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values); if (mapping_state == Mapped) m_decoInputExtent.map(); } else { m_decoInputExtent.setGeometry(bounds); } const QVector rects = Xcb::regionToRects(region); xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, m_decoInputExtent, 0, 0, rects.count(), rects.constData()); } void X11Client::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = frameGeometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); updateShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); updateInputWindow(); blockGeometryUpdates(false); updateFrameExtents(); } void X11Client::createDecoration(const QRect& oldgeom) { KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); if (decoration) { QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::updateShadow); connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &X11Client::updateInputWindow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { updateFrameExtents(); GeometryUpdatesBlocker blocker(this); // TODO: this is obviously idempotent // calculateGravitation(true) would have to operate on the old border sizes // move(calculateGravitation(true)); // move(calculateGravitation(false)); QRect oldgeom = frameGeometry(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, &X11Client::updateInputWindow); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, &X11Client::updateInputWindow); } setDecoration(decoration); move(calculateGravitation(false)); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (Compositor::compositing()) { discardWindowPixmap(); } emit geometryShapeChanged(this, oldgeom); } void X11Client::destroyDecoration() { QRect oldgeom = frameGeometry(); if (isDecorated()) { QPoint grav = calculateGravitation(true); AbstractClient::destroyDecoration(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); move(grav); if (compositing()) discardWindowPixmap(); if (!deleting) { emit geometryShapeChanged(this, oldgeom); } } m_decoInputExtent.reset(); } void X11Client::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const { if (!isDecorated()) { return; } QRect r = decoration()->rect(); NETStrut strut = info->frameOverlap(); // Ignore the overlap strut when compositing is disabled if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) { top = QRect(r.x(), r.y(), r.width(), r.height() / 3); left = QRect(r.x(), r.y() + top.height(), width() / 2, r.height() / 3); right = QRect(r.x() + left.width(), r.y() + top.height(), r.width() - left.width(), left.height()); bottom = QRect(r.x(), r.y() + top.height() + left.height(), r.width(), r.height() - left.height() - top.height()); return; } top = QRect(r.x(), r.y(), r.width(), borderTop() + strut.top); bottom = QRect(r.x(), r.y() + r.height() - borderBottom() - strut.bottom, r.width(), borderBottom() + strut.bottom); left = QRect(r.x(), r.y() + top.height(), borderLeft() + strut.left, r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - borderRight() - strut.right, r.y() + top.height(), borderRight() + strut.right, r.height() - top.height() - bottom.height()); } QRect X11Client::transparentRect() const { if (isShade()) return QRect(); NETStrut strut = info->frameOverlap(); // Ignore the strut when compositing is disabled or the decoration doesn't support it if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) return QRect(); const QRect r = QRect(clientPos(), clientSize()) .adjusted(strut.left, strut.top, -strut.right, -strut.bottom); if (r.isValid()) return r; return QRect(); } void X11Client::detectNoBorder() { if (shape()) { noborder = true; app_noborder = true; return; } switch(windowType()) { case NET::Desktop : case NET::Dock : case NET::TopMenu : case NET::Splash : case NET::Notification : case NET::OnScreenDisplay : case NET::CriticalNotification : noborder = true; app_noborder = true; break; case NET::Unknown : case NET::Normal : case NET::Toolbar : case NET::Menu : case NET::Dialog : case NET::Utility : noborder = false; break; default: abort(); } // NET::Override is some strange beast without clear definition, usually // just meaning "noborder", so let's treat it only as such flag, and ignore it as // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it) if (info->windowType(NET::OverrideMask) == NET::Override) { noborder = true; app_noborder = true; } } void X11Client::updateFrameExtents() { NETStrut strut; strut.left = borderLeft(); strut.right = borderRight(); strut.top = borderTop(); strut.bottom = borderBottom(); info->setFrameExtents(strut); } void X11Client::setClientFrameExtents(const NETStrut &strut) { const QMargins clientFrameExtents(strut.left, strut.top, strut.right, strut.bottom); if (m_clientFrameExtents == clientFrameExtents) { return; } const bool wasClientSideDecorated = isClientSideDecorated(); m_clientFrameExtents = clientFrameExtents; // We should resize the client when its custom frame extents are changed so // the logical bounds remain the same. This however means that we will send // several configure requests to the application upon restoring it from the // maximized or fullscreen state. Notice that a client-side decorated client // cannot be shaded, therefore it's okay not to use the adjusted size here. setFrameGeometry(frameGeometry()); if (wasClientSideDecorated != isClientSideDecorated()) { emit clientSideDecoratedChanged(); } // This will invalidate the window quads cache. emit geometryShapeChanged(this, frameGeometry()); } /** * Resizes the decoration, and makes sure the decoration widget gets resize event * even if the size hasn't changed. This is needed to make sure the decoration * re-layouts (e.g. when maximization state changes, * the decoration may alter some borders, but the actual size * of the decoration stays the same). */ void X11Client::resizeDecoration() { triggerDecorationRepaint(); updateInputWindow(); } bool X11Client::userNoBorder() const { return noborder; } bool X11Client::isFullScreenable() const { if (!rules()->checkFullScreen(true)) { return false; } if (rules()->checkStrictGeometry(true)) { // check geometry constraints (rule to obey is set) const QRect fsarea = workspace()->clientArea(FullScreenArea, this); if (sizeForClientSize(fsarea.size(), SizeModeAny, true) != fsarea.size()) { return false; // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements } } // don't check size constrains - some apps request fullscreen despite requesting fixed size return !isSpecialWindow(); // also better disallow only weird types to go fullscreen } bool X11Client::noBorder() const { return userNoBorder() || isFullScreen(); } bool X11Client::userCanSetNoBorder() const { // Client-side decorations and server-side decorations are mutually exclusive. if (isClientSideDecorated()) { return false; } return !isFullScreen() && !isShade(); } void X11Client::setNoBorder(bool set) { if (!userCanSetNoBorder()) return; set = rules()->checkNoBorder(set); if (noborder == set) return; noborder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void X11Client::checkNoBorder() { setNoBorder(app_noborder); } bool X11Client::wantsShadowToBeRendered() const { return !isFullScreen() && maximizeMode() != MaximizeFull; } void X11Client::updateShape() { if (shape()) { // Workaround for #19644 - Shaped windows shouldn't have decoration if (!app_noborder) { // Only when shape is detected for the first time, still let the user to override app_noborder = true; noborder = rules()->checkNoBorder(true); updateDecoration(true); } if (noBorder()) { xcb_shape_combine(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, frameId(), clientPos().x(), clientPos().y(), window()); } } else if (app_noborder) { xcb_shape_mask(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE); detectNoBorder(); app_noborder = noborder; noborder = rules()->checkNoBorder(noborder || m_motif.noBorder()); updateDecoration(true); } // Decoration mask (i.e. 'else' here) setting is done in setMask() // when the decoration calls it or when the decoration is created/destroyed updateInputShape(); if (compositing()) { addRepaintFull(); addWorkspaceRepaint(visibleRect()); // In case shape change removes part of this window } emit geometryShapeChanged(this, frameGeometry()); } static Xcb::Window shape_helper_window(XCB_WINDOW_NONE); void X11Client::cleanupX11() { shape_helper_window.reset(); } void X11Client::updateInputShape() { if (hiddenPreview()) // Sets it to none, don't change return; if (Xcb::Extensions::self()->isShapeInputAvailable()) { // There appears to be no way to find out if a window has input // shape set or not, so always propagate the input shape // (it's the same like the bounding shape by default). // Also, build the shape using a helper window, not directly // in the frame window, because the sequence set-shape-to-frame, // remove-shape-of-client, add-input-shape-of-client has the problem // that after the second step there's a hole in the input shape // until the real shape of the client is added and that can make // the window lose focus (which is a problem with mouse focus policies) // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better if (!shape_helper_window.isValid()) shape_helper_window.create(QRect(0, 0, 1, 1)); shape_helper_window.resize(m_bufferGeometry.size()); xcb_connection_t *c = connection(); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, 0, 0, frameId()); xcb_shape_combine(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, frameId(), 0, 0, shape_helper_window); } } void X11Client::hideClient(bool hide) { if (hidden == hide) return; hidden = hide; updateVisibility(); } bool X11Client::setupCompositing() { if (!Toplevel::setupCompositing()){ return false; } updateVisibility(); // for internalKeep() return true; } void X11Client::finishCompositing(ReleaseReason releaseReason) { Toplevel::finishCompositing(releaseReason); updateVisibility(); // for safety in case KWin is just resizing the window resetHaveResizeEffect(); } /** * Returns whether the window is minimizable or not */ bool X11Client::isMinimizable() const { if (isSpecialWindow() && !isTransient()) return false; if (!rules()->checkMinimize(true)) return false; if (isTransient()) { // #66868 - Let other xmms windows be minimized when the mainwindow is minimized bool shown_mainwindow = false; auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) shown_mainwindow = true; if (!shown_mainwindow) return true; } #if 0 // This is here because kicker's taskbar doesn't provide separate entries // for windows with an explicitly given parent // TODO: perhaps this should be redone // Disabled for now, since at least modal dialogs should be minimizable // (resulting in the mainwindow being minimized too). if (transientFor() != NULL) return false; #endif if (!wantsTabFocus()) // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ? return false; return true; } void X11Client::doMinimize() { if (isShade()) { // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(isMinimized() ? NET::States() : NET::Shaded, NET::Shaded); } updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients(this); } QRect X11Client::iconGeometry() const { NETRect r = info->iconGeometry(); QRect geom(r.pos.x, r.pos.y, r.size.width, r.size.height); if (geom.isValid()) return geom; else { // Check all mainwindows of this window (recursively) foreach (AbstractClient * amainwin, mainClients()) { X11Client *mainwin = dynamic_cast(amainwin); if (!mainwin) { continue; } geom = mainwin->iconGeometry(); if (geom.isValid()) return geom; } // No mainwindow (or their parents) with icon geometry was found return AbstractClient::iconGeometry(); } } bool X11Client::isShadeable() const { return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone)); } void X11Client::setShade(ShadeMode mode) { if (mode == ShadeHover && isMove()) return; // causes geometry breaks and is probably nasty if (isSpecialWindow() || noBorder()) mode = ShadeNone; mode = rules()->checkShade(mode); if (shade_mode == mode) return; bool was_shade = isShade(); ShadeMode was_shade_mode = shade_mode; shade_mode = mode; // Decorations may turn off some borders when shaded // this has to happen _before_ the tab alignment since it will restrict the minimum geometry #if 0 if (decoration) decoration->borders(border_left, border_right, border_top, border_bottom); #endif if (was_shade == isShade()) { // Decoration may want to update after e.g. hover-shade changes emit shadeChanged(); return; // No real change in shaded state } Q_ASSERT(isDecorated()); // noborder windows can't be shaded GeometryUpdatesBlocker blocker(this); // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere if (isShade()) { // shade_mode == ShadeNormal addWorkspaceRepaint(visibleRect()); // Shade shade_geometry_change = true; QSize s(sizeForClientSize(QSize(clientSize()))); s.setHeight(borderTop() + borderBottom()); m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_wrapper.unmap(); m_client.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(XCB_ICCCM_WM_STATE_ICONIC); plainResize(s); shade_geometry_change = false; if (was_shade_mode == ShadeHover) { if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1) workspace()->restack(this, shade_below, true); if (isActive()) workspace()->activateNextClient(this); } else if (isActive()) { workspace()->focusToNull(); } } else { shade_geometry_change = true; if (decoratedClient()) decoratedClient()->signalShadeChange(); QSize s(sizeForClientSize(clientSize())); shade_geometry_change = false; plainResize(s); setGeometryRestore(frameGeometry()); if ((shade_mode == ShadeHover || shade_mode == ShadeActivated) && rules()->checkAcceptFocus(info->input())) setActive(true); if (shade_mode == ShadeHover) { QList order = workspace()->stackingOrder(); // invalidate, since "this" could be the topmost toplevel and shade_below dangeling shade_below = nullptr; // this is likely related to the index parameter?! for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) { shade_below = qobject_cast(order.at(idx)); if (shade_below) { break; } } if (shade_below && shade_below->isNormalWindow()) workspace()->raiseClient(this); else shade_below = nullptr; } m_wrapper.map(); m_client.map(); exportMappingState(XCB_ICCCM_WM_STATE_NORMAL); if (isActive()) workspace()->requestFocus(this); } info->setState(isShade() ? NET::Shaded : NET::States(), NET::Shaded); info->setState(isShown(false) ? NET::States() : NET::Hidden, NET::Hidden); discardWindowPixmap(); updateVisibility(); updateAllowedActions(); updateWindowRules(Rules::Shade); emit shadeChanged(); } void X11Client::shadeHover() { setShade(ShadeHover); cancelShadeHoverTimer(); } void X11Client::shadeUnhover() { setShade(ShadeNormal); cancelShadeHoverTimer(); } void X11Client::cancelShadeHoverTimer() { delete shadeHoverTimer; shadeHoverTimer = nullptr; } void X11Client::toggleShade() { // If the mode is ShadeHover or ShadeActive, cancel shade too setShade(shade_mode == ShadeNone ? ShadeNormal : ShadeNone); } void X11Client::updateVisibility() { if (deleting) return; if (hidden) { info->setState(NET::Hidden, NET::Hidden); setSkipTaskbar(true); // Also hide from taskbar if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden' if (isMinimized()) { info->setState(NET::Hidden, NET::Hidden); if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } info->setState(NET::States(), NET::Hidden); if (!isOnCurrentDesktop()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } if (!isOnCurrentActivity()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } internalShow(); } /** * Sets the client window's mapping state. Possible values are * WithdrawnState, IconicState, NormalState. */ void X11Client::exportMappingState(int s) { Q_ASSERT(m_client != XCB_WINDOW_NONE); Q_ASSERT(!deleting || s == XCB_ICCCM_WM_STATE_WITHDRAWN); if (s == XCB_ICCCM_WM_STATE_WITHDRAWN) { m_client.deleteProperty(atoms->wm_state); return; } Q_ASSERT(s == XCB_ICCCM_WM_STATE_NORMAL || s == XCB_ICCCM_WM_STATE_ICONIC); int32_t data[2]; data[0] = s; data[1] = XCB_NONE; m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data); } void X11Client::internalShow() { if (mapping_state == Mapped) return; MappingState old = mapping_state; mapping_state = Mapped; if (old == Unmapped || old == Withdrawn) map(); if (old == Kept) { m_decoInputExtent.map(); updateHiddenPreview(); } emit windowShown(this); } void X11Client::internalHide() { if (mapping_state == Unmapped) return; MappingState old = mapping_state; mapping_state = Unmapped; if (old == Mapped || old == Kept) unmap(); if (old == Kept) updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); emit windowHidden(this); } void X11Client::internalKeep() { Q_ASSERT(compositing()); if (mapping_state == Kept) return; MappingState old = mapping_state; mapping_state = Kept; if (old == Unmapped || old == Withdrawn) map(); m_decoInputExtent.unmap(); if (isActive()) workspace()->focusToNull(); // get rid of input focus, bug #317484 updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } /** * Maps (shows) the client. Note that it is mapping state of the frame, * not necessarily the client window itself (i.e. a shaded window is here * considered mapped, even though it is in IconicState). */ void X11Client::map() { // XComposite invalidates backing pixmaps on unmap (minimize, different // virtual desktop, etc.). We kept the last known good pixmap around // for use in effects, but now we want to have access to the new pixmap if (compositing()) discardWindowPixmap(); m_frame.map(); if (!isShade()) { m_wrapper.map(); m_client.map(); m_decoInputExtent.map(); exportMappingState(XCB_ICCCM_WM_STATE_NORMAL); } else exportMappingState(XCB_ICCCM_WM_STATE_ICONIC); addLayerRepaint(visibleRect()); } /** * Unmaps the client. Again, this is about the frame. */ void X11Client::unmap() { // Here it may look like a race condition, as some other client might try to unmap // the window between these two XSelectInput() calls. However, they're supposed to // use XWithdrawWindow(), which also sends a synthetic event to the root window, // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify // will be missed is also very minimal, so I don't think it's needed to grab the server // here. m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_frame.unmap(); m_wrapper.unmap(); m_client.unmap(); m_decoInputExtent.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(XCB_ICCCM_WM_STATE_ICONIC); } /** * XComposite doesn't keep window pixmaps of unmapped windows, which means * there wouldn't be any previews of windows that are minimized or on another * virtual desktop. Therefore rawHide() actually keeps such windows mapped. * However special care needs to be taken so that such windows don't interfere. * Therefore they're put very low in the stacking order and they have input shape * set to none, which hopefully is enough. If there's no input shape available, * then it's hoped that there will be some other desktop above it *shrug*. * Using normal shape would be better, but that'd affect other things, e.g. painting * of the actual preview. */ void X11Client::updateHiddenPreview() { if (hiddenPreview()) { workspace()->forceRestacking(); if (Xcb::Extensions::self()->isShapeInputAvailable()) { xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, nullptr); } } else { workspace()->forceRestacking(); updateInputShape(); } } void X11Client::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1, uint32_t data2, uint32_t data3, xcb_timestamp_t timestamp) { xcb_client_message_event_t ev; memset(&ev, 0, sizeof(ev)); ev.response_type = XCB_CLIENT_MESSAGE; ev.window = w; ev.type = a; ev.format = 32; ev.data.data32[0] = protocol; ev.data.data32[1] = timestamp; ev.data.data32[2] = data1; ev.data.data32[3] = data2; ev.data.data32[4] = data3; uint32_t eventMask = 0; if (w == rootWindow()) { eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic! } xcb_send_event(connection(), false, w, eventMask, reinterpret_cast(&ev)); xcb_flush(connection()); } /** * Returns whether the window may be closed (have a close button) */ bool X11Client::isCloseable() const { return rules()->checkCloseable(m_motif.close() && !isSpecialWindow()); } /** * Closes the window by either sending a delete_window message or using XKill. */ void X11Client::closeWindow() { if (!isCloseable()) return; // Update user time, because the window may create a confirming dialog. updateUserTime(); if (info->supportsProtocol(NET::DeleteWindowProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window); pingWindow(); } else // Client will not react on wm_delete_window. We have not choice // but destroy his connection to the XServer. killWindow(); } /** * Kills the window via XKill */ void X11Client::killWindow() { qCDebug(KWIN_CORE) << "X11Client::killWindow():" << caption(); killProcess(false); m_client.kill(); // Always kill this client at the server destroyClient(); } /** * Send a ping to the window using _NET_WM_PING if possible if it * doesn't respond within a reasonable time, it will be killed. */ void X11Client::pingWindow() { if (!info->supportsProtocol(NET::PingProtocol)) return; // Can't ping :( if (options->killPingTimeout() == 0) return; // Turned off if (ping_timer != nullptr) return; // Pinging already ping_timer = new QTimer(this); connect(ping_timer, &QTimer::timeout, this, [this]() { if (unresponsive()) { qCDebug(KWIN_CORE) << "Final ping timeout, asking to kill:" << caption(); ping_timer->deleteLater(); ping_timer = nullptr; killProcess(true, m_pingTimestamp); return; } qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); setUnresponsive(true); ping_timer->start(); } ); ping_timer->setSingleShot(true); // we'll run the timer twice, at first we'll desaturate the window // and the second time we'll show the "do you want to kill" prompt ping_timer->start(options->killPingTimeout() / 2); m_pingTimestamp = xTime(); workspace()->sendPingToWindow(window(), m_pingTimestamp); } void X11Client::gotPing(xcb_timestamp_t timestamp) { // Just plain compare is not good enough because of 64bit and truncating and whatnot if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0) return; delete ping_timer; ping_timer = nullptr; setUnresponsive(false); if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } } void X11Client::killProcess(bool ask, xcb_timestamp_t timestamp) { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) // means the process is alive return; Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME); pid_t pid = info->pid(); if (pid <= 0 || clientMachine()->hostName().isEmpty()) // Needed properties missing return; qCDebug(KWIN_CORE) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")"; if (!ask) { if (!clientMachine()->isLocal()) { QStringList lst; lst << QString::fromUtf8(clientMachine()->hostName()) << QStringLiteral("kill") << QString::number(pid); QProcess::startDetached(QStringLiteral("xon"), lst); } else ::kill(pid, SIGTERM); } else { QString hostname = clientMachine()->isLocal() ? QStringLiteral("localhost") : QString::fromUtf8(clientMachine()->hostName()); // execute helper from build dir or the system installed one const QFileInfo buildDirBinary{QDir{QCoreApplication::applicationDirPath()}, QStringLiteral("kwin_killer_helper")}; QProcess::startDetached(buildDirBinary.exists() ? buildDirBinary.absoluteFilePath() : QStringLiteral(KWIN_KILLER_BIN), QStringList() << QStringLiteral("--pid") << QString::number(unsigned(pid)) << QStringLiteral("--hostname") << hostname << QStringLiteral("--windowname") << captionNormal() << QStringLiteral("--applicationname") << QString::fromUtf8(resourceClass()) << QStringLiteral("--wid") << QString::number(window()) << QStringLiteral("--timestamp") << QString::number(timestamp), QString(), &m_killHelperPID); } } void X11Client::doSetKeepAbove() { info->setState(keepAbove() ? NET::KeepAbove : NET::States(), NET::KeepAbove); } void X11Client::doSetKeepBelow() { info->setState(keepBelow() ? NET::KeepBelow : NET::States(), NET::KeepBelow); } void X11Client::doSetSkipTaskbar() { info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(), NET::SkipTaskbar); } void X11Client::doSetSkipPager() { info->setState(skipPager() ? NET::SkipPager : NET::States(), NET::SkipPager); } void X11Client::doSetSkipSwitcher() { info->setState(skipSwitcher() ? NET::SkipSwitcher : NET::States(), NET::SkipSwitcher); } void X11Client::doSetDesktop() { updateVisibility(); } void X11Client::doSetDemandsAttention() { info->setState(isDemandingAttention() ? NET::DemandsAttention : NET::States(), NET::DemandsAttention); } /** * Sets whether the client is on @p activity. * If you remove it from its last activity, then it's on all activities. * * Note: If it was on all activities and you try to remove it from one, nothing will happen; * I don't think that's an important enough use case to handle here. */ void X11Client::setOnActivity(const QString &activity, bool enable) { #ifdef KWIN_BUILD_ACTIVITIES if (! Activities::self()) { return; } QStringList newActivitiesList = activities(); if (newActivitiesList.contains(activity) == enable) //nothing to do return; if (enable) { QStringList allActivities = Activities::self()->all(); if (!allActivities.contains(activity)) //bogus ID return; newActivitiesList.append(activity); } else newActivitiesList.removeOne(activity); setOnActivities(newActivitiesList); #else Q_UNUSED(activity) Q_UNUSED(enable) #endif } /** * set exactly which activities this client is on */ void X11Client::setOnActivities(QStringList newActivitiesList) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } QString joinedActivitiesList = newActivitiesList.join(QStringLiteral(",")); joinedActivitiesList = rules()->checkActivity(joinedActivitiesList, false); newActivitiesList = joinedActivitiesList.split(u',', QString::SkipEmptyParts); QStringList allActivities = Activities::self()->all(); auto it = newActivitiesList.begin(); while (it != newActivitiesList.end()) { if (! allActivities.contains(*it)) { it = newActivitiesList.erase(it); } else { it++; } } if (// If we got the request to be on all activities explicitly newActivitiesList.isEmpty() || joinedActivitiesList == Activities::nullUuid() || // If we got a list of activities that covers all activities (newActivitiesList.count() > 1 && newActivitiesList.count() == allActivities.count())) { activityList.clear(); const QByteArray nullUuid = Activities::nullUuid().toUtf8(); m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, nullUuid.length(), nullUuid.constData()); } else { QByteArray joined = joinedActivitiesList.toLatin1(); activityList = newActivitiesList; m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData()); } updateActivities(false); #else Q_UNUSED(newActivitiesList) #endif } void X11Client::blockActivityUpdates(bool b) { if (b) { ++m_activityUpdatesBlocked; } else { Q_ASSERT(m_activityUpdatesBlocked); --m_activityUpdatesBlocked; if (!m_activityUpdatesBlocked) updateActivities(m_blockedActivityUpdatesRequireTransients); } } /** * update after activities changed */ void X11Client::updateActivities(bool includeTransients) { if (m_activityUpdatesBlocked) { m_blockedActivityUpdatesRequireTransients |= includeTransients; return; } emit activitiesChanged(this); m_blockedActivityUpdatesRequireTransients = false; // reset FocusChain::self()->update(this, FocusChain::MakeFirst); updateVisibility(); updateWindowRules(Rules::Activity); } /** * Returns the list of activities the client window is on. * if it's on all activities, the list will be empty. * Don't use this, use isOnActivity() and friends (from class Toplevel) */ QStringList X11Client::activities() const { if (sessionActivityOverride) { return QStringList(); } return activityList; } /** * if @p on is true, sets on all activities. * if it's false, sets it to only be on the current activity */ void X11Client::setOnAllActivities(bool on) { #ifdef KWIN_BUILD_ACTIVITIES if (on == isOnAllActivities()) return; if (on) { setOnActivities(QStringList()); } else { setOnActivity(Activities::self()->current(), true); } #else Q_UNUSED(on) #endif } /** * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS */ void X11Client::takeFocus() { if (rules()->checkAcceptFocus(info->input())) m_client.focus(); else demandAttention(false); // window cannot take input, at least withdraw urgency if (info->supportsProtocol(NET::TakeFocusProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus, 0, 0, 0, XCB_CURRENT_TIME); } workspace()->setShouldGetFocus(this); bool breakShowingDesktop = !keepAbove(); if (breakShowingDesktop) { foreach (const X11Client *c, group()->members()) { if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } /** * Returns whether the window provides context help or not. If it does, * you should show a help menu item or a help button like '?' and call * contextHelp() if this is invoked. * * \sa contextHelp() */ bool X11Client::providesContextHelp() const { return info->supportsProtocol(NET::ContextHelpProtocol); } /** * Invokes context help on the window. Only works if the window * actually provides context help. * * \sa providesContextHelp() */ void X11Client::showContextHelp() { if (info->supportsProtocol(NET::ContextHelpProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); } } /** * Fetches the window's caption (WM_NAME property). It will be * stored in the client's caption(). */ void X11Client::fetchName() { setCaption(readName()); } static inline QString readNameProperty(xcb_window_t w, xcb_atom_t atom) { const auto cookie = xcb_icccm_get_text_property_unchecked(connection(), w, atom); xcb_icccm_get_text_property_reply_t reply; if (xcb_icccm_get_wm_name_reply(connection(), cookie, &reply, nullptr)) { QString retVal; if (reply.encoding == atoms->utf8_string) { retVal = QString::fromUtf8(QByteArray(reply.name, reply.name_len)); } else if (reply.encoding == XCB_ATOM_STRING) { retVal = QString::fromLocal8Bit(QByteArray(reply.name, reply.name_len)); } xcb_icccm_get_text_property_reply_wipe(&reply); return retVal.simplified(); } return QString(); } QString X11Client::readName() const { if (info->name() && info->name()[0] != '\0') return QString::fromUtf8(info->name()).simplified(); else { return readNameProperty(window(), XCB_ATOM_WM_NAME); } } // The list is taken from https://www.unicode.org/reports/tr9/ (#154840) static const QChar LRM(0x200E); void X11Client::setCaption(const QString& _s, bool force) { QString s(_s); for (int i = 0; i < s.length(); ) { if (!s[i].isPrint()) { if (QChar(s[i]).isHighSurrogate() && i + 1 < s.length() && QChar(s[i + 1]).isLowSurrogate()) { const uint uc = QChar::surrogateToUcs4(s[i], s[i + 1]); if (!QChar::isPrint(uc)) { s.remove(i, 2); } else { i += 2; } continue; } s.remove(i, 1); continue; } ++i; } const bool changed = (s != cap_normal); if (!force && !changed) { return; } cap_normal = s; if (!force && !changed) { emit captionChanged(); return; } bool reset_name = force; bool was_suffix = (!cap_suffix.isEmpty()); cap_suffix.clear(); QString machine_suffix; if (!options->condensedTitle()) { // machine doesn't qualify for "clean" if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal()) machine_suffix = QLatin1String(" <@") + QString::fromUtf8(clientMachine()->hostName()) + QLatin1Char('>') + LRM; } QString shortcut_suffix = shortcutCaptionSuffix(); cap_suffix = machine_suffix + shortcut_suffix; if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { int i = 2; do { cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM; i++; } while (findClientWithSameCaption()); info->setVisibleName(caption().toUtf8().constData()); reset_name = false; } if ((was_suffix && cap_suffix.isEmpty()) || reset_name) { // If it was new window, it may have old value still set, if the window is reused info->setVisibleName(""); info->setVisibleIconName(""); } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8().constData()); emit captionChanged(); } void X11Client::updateCaption() { setCaption(cap_normal, true); } void X11Client::fetchIconicName() { QString s; if (info->iconName() && info->iconName()[0] != '\0') s = QString::fromUtf8(info->iconName()); else s = readNameProperty(window(), XCB_ATOM_WM_ICON_NAME); if (s != cap_iconic) { bool was_set = !cap_iconic.isEmpty(); cap_iconic = s; if (!cap_suffix.isEmpty()) { if (!cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(s + cap_suffix).toUtf8().constData()); else if (was_set) info->setVisibleIconName(""); } } } void X11Client::setClientShown(bool shown) { if (deleting) return; // Don't change shown status if this client is being deleted if (shown != hidden) return; // nothing to change hidden = !shown; if (shown) { map(); takeFocus(); autoRaise(); FocusChain::self()->update(this, FocusChain::MakeFirst); } else { unmap(); // Don't move tabs to the end of the list when another tab get's activated FocusChain::self()->update(this, FocusChain::MakeLast); addWorkspaceRepaint(visibleRect()); } } void X11Client::getMotifHints() { const bool wasClosable = m_motif.close(); const bool wasNoBorder = m_motif.noBorder(); if (m_managed) // only on property change, initial read is prefetched m_motif.fetch(); m_motif.read(); if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) { // If we just got a hint telling us to hide decorations, we do so. if (m_motif.noBorder()) noborder = rules()->checkNoBorder(true); // If the Motif hint is now telling us to show decorations, we only do so if the app didn't // instruct us to hide decorations in some other way, though. else if (!app_noborder) noborder = rules()->checkNoBorder(false); } // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too // mmaximize; - Ignore, bogus - Maximizing is basically just resizing const bool closabilityChanged = wasClosable != m_motif.close(); if (isManaged()) updateDecoration(true); // Check if noborder state has changed if (closabilityChanged) { emit closeableChanged(isCloseable()); } } void X11Client::getIcons() { // First read icons from the window itself const QString themedIconName = iconFromDesktopFile(); if (!themedIconName.isEmpty()) { setIcon(QIcon::fromTheme(themedIconName)); return; } QIcon icon; auto readIcon = [this, &icon](int size, bool scale = true) { const QPixmap pix = KWindowSystem::icon(window(), size, size, scale, KWindowSystem::NETWM | KWindowSystem::WMHints, info); if (!pix.isNull()) { icon.addPixmap(pix); } }; readIcon(16); readIcon(32); readIcon(48, false); readIcon(64, false); readIcon(128, false); if (icon.isNull()) { // Then try window group icon = group()->icon(); } if (icon.isNull() && isTransient()) { // Then mainclients auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd() && icon.isNull(); ++it) { if (!(*it)->icon().isNull()) { icon = (*it)->icon(); break; } } } if (icon.isNull()) { // And if nothing else, load icon from classhint or xapp icon icon.addPixmap(KWindowSystem::icon(window(), 32, 32, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 16, 16, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 64, 64, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 128, 128, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); } setIcon(icon); } void X11Client::getSyncCounter() { // TODO: make sync working on XWayland static const bool isX11 = kwinApp()->operationMode() == Application::OperationModeX11; if (!Xcb::Extensions::self()->isSyncAvailable() || !isX11) return; Xcb::Property syncProp(false, window(), atoms->net_wm_sync_request_counter, XCB_ATOM_CARDINAL, 0, 1); const xcb_sync_counter_t counter = syncProp.value(XCB_NONE); if (counter != XCB_NONE) { m_syncRequest.counter = counter; m_syncRequest.value.hi = 0; m_syncRequest.value.lo = 0; auto *c = connection(); xcb_sync_set_counter(c, m_syncRequest.counter, m_syncRequest.value); if (m_syncRequest.alarm == XCB_NONE) { const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS; const uint32_t values[] = { m_syncRequest.counter, XCB_SYNC_VALUETYPE_RELATIVE, XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION, 1 }; m_syncRequest.alarm = xcb_generate_id(c); auto cookie = xcb_sync_create_alarm_checked(c, m_syncRequest.alarm, mask, values); ScopedCPointer error(xcb_request_check(c, cookie)); if (!error.isNull()) { m_syncRequest.alarm = XCB_NONE; } else { xcb_sync_change_alarm_value_list_t value; memset(&value, 0, sizeof(value)); value.value.hi = 0; value.value.lo = 1; value.delta.hi = 0; value.delta.lo = 1; xcb_sync_change_alarm_aux(c, m_syncRequest.alarm, XCB_SYNC_CA_DELTA | XCB_SYNC_CA_VALUE, &value); } } } } /** * Send the client a _NET_SYNC_REQUEST */ void X11Client::sendSyncRequest() { if (m_syncRequest.counter == XCB_NONE || m_syncRequest.isPending) { return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ... } if (!m_syncRequest.failsafeTimeout) { m_syncRequest.failsafeTimeout = new QTimer(this); connect(m_syncRequest.failsafeTimeout, &QTimer::timeout, this, [this]() { // client does not respond to XSYNC requests in reasonable time, remove support if (!ready_for_painting) { // failed on initial pre-show request setReadyForPainting(); setupWindowManagementInterface(); return; } // failed during resize m_syncRequest.isPending = false; m_syncRequest.counter = XCB_NONE; m_syncRequest.alarm = XCB_NONE; delete m_syncRequest.timeout; delete m_syncRequest.failsafeTimeout; m_syncRequest.timeout = nullptr; m_syncRequest.failsafeTimeout = nullptr; m_syncRequest.lastTimestamp = XCB_CURRENT_TIME; } ); m_syncRequest.failsafeTimeout->setSingleShot(true); } // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client. // see events.cpp X11Client::syncEvent() m_syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000); // We increment before the notify so that after the notify // syncCounterSerial will equal the value we are expecting // in the acknowledgement const uint32_t oldLo = m_syncRequest.value.lo; m_syncRequest.value.lo++; if (oldLo > m_syncRequest.value.lo) { m_syncRequest.value.hi++; } if (m_syncRequest.lastTimestamp >= xTime()) { updateXTime(); } // Send the message to client sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request, m_syncRequest.value.lo, m_syncRequest.value.hi); m_syncRequest.isPending = true; m_syncRequest.lastTimestamp = xTime(); } bool X11Client::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol)); } bool X11Client::acceptsFocus() const { return info->input(); } void X11Client::setBlockingCompositing(bool block) { const bool usedToBlock = blocks_compositing; blocks_compositing = rules()->checkBlockCompositing(block && options->windowsBlockCompositing()); if (usedToBlock != blocks_compositing) { emit blockingCompositingChanged(blocks_compositing ? this : nullptr); } } void X11Client::updateAllowedActions(bool force) { if (!isManaged() && !force) return; NET::Actions old_allowed_actions = NET::Actions(allowed_actions); allowed_actions = NET::Actions(); if (isMovable()) allowed_actions |= NET::ActionMove; if (isResizable()) allowed_actions |= NET::ActionResize; if (isMinimizable()) allowed_actions |= NET::ActionMinimize; if (isShadeable()) allowed_actions |= NET::ActionShade; // Sticky state not supported if (isMaximizable()) allowed_actions |= NET::ActionMax; if (userCanSetFullScreen()) allowed_actions |= NET::ActionFullScreen; allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.) if (isCloseable()) allowed_actions |= NET::ActionClose; if (old_allowed_actions == allowed_actions) return; // TODO: This could be delayed and compressed - It's only for pagers etc. anyway info->setAllowedActions(allowed_actions); // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes) const NET::Actions relevant = ~(NET::ActionMove|NET::ActionResize); if ((allowed_actions & relevant) != (old_allowed_actions & relevant)) { if ((allowed_actions & NET::ActionMinimize) != (old_allowed_actions & NET::ActionMinimize)) { emit minimizeableChanged(allowed_actions & NET::ActionMinimize); } if ((allowed_actions & NET::ActionShade) != (old_allowed_actions & NET::ActionShade)) { emit shadeableChanged(allowed_actions & NET::ActionShade); } if ((allowed_actions & NET::ActionMax) != (old_allowed_actions & NET::ActionMax)) { emit maximizeableChanged(allowed_actions & NET::ActionMax); } } } void X11Client::debug(QDebug& stream) const { stream.nospace(); print(stream); } Xcb::StringProperty X11Client::fetchActivities() const { #ifdef KWIN_BUILD_ACTIVITIES return Xcb::StringProperty(window(), atoms->activities); #else return Xcb::StringProperty(); #endif } void X11Client::readActivities(Xcb::StringProperty &property) { #ifdef KWIN_BUILD_ACTIVITIES QStringList newActivitiesList; QString prop = QString::fromUtf8(property); activitiesDefined = !prop.isEmpty(); if (prop == Activities::nullUuid()) { //copied from setOnAllActivities to avoid a redundant XChangeProperty. if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } if (prop.isEmpty()) { //note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL' if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } newActivitiesList = prop.split(u','); if (newActivitiesList == activityList) return; //expected change, it's ok. //otherwise, somebody else changed it. we need to validate before reacting. //if the activities are not synced, and there are existing clients with //activities specified, somebody has restarted kwin. we can not validate //activities in this case. we need to trust the old values. if (Activities::self() && Activities::self()->serviceStatus() != KActivities::Consumer::Unknown) { QStringList allActivities = Activities::self()->all(); if (allActivities.isEmpty()) { qCDebug(KWIN_CORE) << "no activities!?!?"; //don't touch anything, there's probably something bad going on and we don't wanna make it worse return; } for (int i = 0; i < newActivitiesList.size(); ++i) { if (! allActivities.contains(newActivitiesList.at(i))) { qCDebug(KWIN_CORE) << "invalid:" << newActivitiesList.at(i); newActivitiesList.removeAt(i--); } } } setOnActivities(newActivitiesList); #else Q_UNUSED(property) #endif } void X11Client::checkActivities() { #ifdef KWIN_BUILD_ACTIVITIES Xcb::StringProperty property = fetchActivities(); readActivities(property); #endif } void X11Client::setSessionActivityOverride(bool needed) { sessionActivityOverride = needed; updateActivities(false); } Xcb::Property X11Client::fetchFirstInTabBox() const { return Xcb::Property(false, m_client, atoms->kde_first_in_window_list, atoms->kde_first_in_window_list, 0, 1); } void X11Client::readFirstInTabBox(Xcb::Property &property) { setFirstInTabBox(property.toBool(32, atoms->kde_first_in_window_list)); } void X11Client::updateFirstInTabBox() { // TODO: move into KWindowInfo Xcb::Property property = fetchFirstInTabBox(); readFirstInTabBox(property); } Xcb::StringProperty X11Client::fetchColorScheme() const { return Xcb::StringProperty(m_client, atoms->kde_color_sheme); } void X11Client::readColorScheme(Xcb::StringProperty &property) { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString::fromUtf8(property))); } void X11Client::updateColorScheme() { Xcb::StringProperty property = fetchColorScheme(); readColorScheme(property); } bool X11Client::isClient() const { return true; } NET::WindowType X11Client::windowType(bool direct, int supportedTypes) const { // TODO: does it make sense to cache the returned window type for SUPPORTED_MANAGED_WINDOW_TYPES_MASK? if (supportedTypes == 0) { supportedTypes = SUPPORTED_MANAGED_WINDOW_TYPES_MASK; } NET::WindowType wt = info->windowType(NET::WindowTypes(supportedTypes)); if (direct) { return wt; } NET::WindowType wt2 = rules()->checkType(wt); if (wt != wt2) { wt = wt2; info->setWindowType(wt); // force hint change } // hacks here if (wt == NET::Unknown) // this is more or less suggested in NETWM spec wt = isTransient() ? NET::Dialog : NET::Normal; return wt; } void X11Client::cancelFocusOutTimer() { if (m_focusOutTimer) { m_focusOutTimer->stop(); } } xcb_window_t X11Client::frameId() const { return m_frame; } +QRect X11Client::inputGeometry() const +{ + // Notice that the buffer geometry corresponds to the geometry of the frame window. + if (isDecorated()) { + return m_bufferGeometry + decoration()->resizeOnlyBorders(); + } + return m_bufferGeometry; +} + QRect X11Client::bufferGeometry() const { return m_bufferGeometry; } QMargins X11Client::bufferMargins() const { return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom()); } QPoint X11Client::framePosToClientPos(const QPoint &point) const { int x = point.x(); int y = point.y(); if (isDecorated()) { x += borderLeft(); y += borderTop(); } else { x -= m_clientFrameExtents.left(); y -= m_clientFrameExtents.top(); } return QPoint(x, y); } QPoint X11Client::clientPosToFramePos(const QPoint &point) const { int x = point.x(); int y = point.y(); if (isDecorated()) { x -= borderLeft(); y -= borderTop(); } else { x += m_clientFrameExtents.left(); y += m_clientFrameExtents.top(); } return QPoint(x, y); } QSize X11Client::frameSizeToClientSize(const QSize &size) const { int width = size.width(); int height = size.height(); if (isDecorated()) { width -= borderLeft() + borderRight(); height -= borderTop() + borderBottom(); } else { width += m_clientFrameExtents.left() + m_clientFrameExtents.right(); height += m_clientFrameExtents.top() + m_clientFrameExtents.bottom(); } return QSize(width, height); } QSize X11Client::clientSizeToFrameSize(const QSize &size) const { int width = size.width(); int height = size.height(); if (isDecorated()) { width += borderLeft() + borderRight(); height += borderTop() + borderBottom(); } else { width -= m_clientFrameExtents.left() + m_clientFrameExtents.right(); height -= m_clientFrameExtents.top() + m_clientFrameExtents.bottom(); } return QSize(width, height); } QRect X11Client::frameRectToBufferRect(const QRect &rect) const { if (isDecorated()) { return rect; } return frameRectToClientRect(rect); } +QMatrix4x4 X11Client::inputTransformation() const +{ + QMatrix4x4 matrix; + matrix.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y()); + return matrix; +} + Xcb::Property X11Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); } void X11Client::readShowOnScreenEdge(Xcb::Property &property) { //value comes in two parts, edge in the lower byte //then the type in the upper byte // 0 = autohide // 1 = raise in front on activate const uint32_t value = property.value(ElectricNone); ElectricBorder border = ElectricNone; switch (value & 0xFF) { case 0: border = ElectricTop; break; case 1: border = ElectricRight; break; case 2: border = ElectricBottom; break; case 3: border = ElectricLeft; break; } if (border != ElectricNone) { disconnect(m_edgeRemoveConnection); disconnect(m_edgeGeometryTrackingConnection); bool successfullyHidden = false; if (((value >> 8) & 0xFF) == 1) { setKeepBelow(true); successfullyHidden = keepBelow(); //request could have failed due to user kwin rules m_edgeRemoveConnection = connect(this, &AbstractClient::keepBelowChanged, this, [this](){ if (!keepBelow()) { ScreenEdges::self()->reserve(this, ElectricNone); } }); } else { hideClient(true); successfullyHidden = isHiddenInternal(); m_edgeGeometryTrackingConnection = connect(this, &X11Client::frameGeometryChanged, this, [this, border](){ hideClient(true); ScreenEdges::self()->reserve(this, border); }); } if (successfullyHidden) { ScreenEdges::self()->reserve(this, border); } else { ScreenEdges::self()->reserve(this, ElectricNone); } } else if (!property.isNull() && property->type != XCB_ATOM_NONE) { // property value is incorrect, delete the property // so that the client knows that it is not hidden xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } else { // restore // TODO: add proper unreserve //this will call showOnScreenEdge to reset the state disconnect(m_edgeGeometryTrackingConnection); ScreenEdges::self()->reserve(this, ElectricNone); } } void X11Client::updateShowOnScreenEdge() { Xcb::Property property = fetchShowOnScreenEdge(); readShowOnScreenEdge(property); } void X11Client::showOnScreenEdge() { disconnect(m_edgeRemoveConnection); hideClient(false); setKeepBelow(false); xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } void X11Client::addDamage(const QRegion &damage) { if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (m_syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } repaints_region += damage.translated(bufferGeometry().topLeft() - frameGeometry().topLeft()); Toplevel::addDamage(damage); } bool X11Client::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const { const X11Client *c2 = dynamic_cast(other); if (!c2) { return false; } return X11Client::belongToSameApplication(this, c2, checks); } QSize X11Client::resizeIncrements() const { return m_geometryHints.resizeIncrements(); } Xcb::StringProperty X11Client::fetchApplicationMenuServiceName() const { return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name); } void X11Client::readApplicationMenuServiceName(Xcb::StringProperty &property) { updateApplicationMenuServiceName(QString::fromUtf8(property)); } void X11Client::checkApplicationMenuServiceName() { Xcb::StringProperty property = fetchApplicationMenuServiceName(); readApplicationMenuServiceName(property); } Xcb::StringProperty X11Client::fetchApplicationMenuObjectPath() const { return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path); } void X11Client::readApplicationMenuObjectPath(Xcb::StringProperty &property) { updateApplicationMenuObjectPath(QString::fromUtf8(property)); } void X11Client::checkApplicationMenuObjectPath() { Xcb::StringProperty property = fetchApplicationMenuObjectPath(); readApplicationMenuObjectPath(property); } void X11Client::handleSync() { setReadyForPainting(); setupWindowManagementInterface(); m_syncRequest.isPending = false; if (m_syncRequest.failsafeTimeout) { m_syncRequest.failsafeTimeout->stop(); } if (isResize()) { if (m_syncRequest.timeout) { m_syncRequest.timeout->stop(); } performMoveResize(); updateWindowPixmap(); } else // setReadyForPainting does as well, but there's a small chance for resize syncs after the resize ended addRepaintFull(); } void X11Client::move(int x, int y, ForceGeometry_t force) { const QPoint framePosition(x, y); m_clientGeometry.moveTopLeft(framePosToClientPos(framePosition)); const QPoint bufferPosition = isDecorated() ? framePosition : m_clientGeometry.topLeft(); // resuming geometry updates is handled only in setGeometry() Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); if (!areGeometryUpdatesBlocked() && framePosition != rules()->checkPosition(framePosition)) { qCDebug(KWIN_CORE) << "forced position fail:" << framePosition << ":" << rules()->checkPosition(framePosition); } m_frameGeometry.moveTopLeft(framePosition); if (force == NormalGeometrySet && m_bufferGeometry.topLeft() == bufferPosition) { return; } m_bufferGeometry.moveTopLeft(bufferPosition); if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) { // Maximum, nothing needed. } else if (force == ForceGeometrySet) { setPendingGeometryUpdate(PendingGeometryForced); } else { setPendingGeometryUpdate(PendingGeometryNormal); } return; } updateServerGeometry(); updateWindowRules(Rules::Position); screens()->setCurrent(this); workspace()->updateStackingOrder(); // client itself is not damaged if (frameGeometryBeforeUpdateBlocking() != frameGeometry()) { emit frameGeometryChanged(this, frameGeometryBeforeUpdateBlocking()); } addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); } bool X11Client::belongToSameApplication(const X11Client *c1, const X11Client *c2, SameApplicationChecks checks) { bool same_app = false; // tests that definitely mean they belong together if (c1 == c2) same_app = true; else if (c1->isTransient() && c2->hasTransient(c1, true)) same_app = true; // c1 has c2 as mainwindow else if (c2->isTransient() && c1->hasTransient(c2, true)) same_app = true; // c2 has c1 as mainwindow else if (c1->group() == c2->group()) same_app = true; // same group else if (c1->wmClientLeader() == c2->wmClientLeader() && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), && c2->wmClientLeader() != c2->window()) // don't use in this test then same_app = true; // same client leader // tests that mean they most probably don't belong together else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) || c1->wmClientMachine(false) != c2->wmClientMachine(false)) ; // different processes else if (c1->wmClientLeader() != c2->wmClientLeader() && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), && c2->wmClientLeader() != c2->window() // don't use in this test then && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) ; // different client leader else if (!resourceMatch(c1, c2)) ; // different apps else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive)) && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) ; // "different" apps else if (c1->pid() == 0 || c2->pid() == 0) ; // old apps that don't have _NET_WM_PID, consider them different // if they weren't found to match above else same_app = true; // looks like it's the same app return same_app; } // Non-transient windows with window role containing '#' are always // considered belonging to different applications (unless // the window role is exactly the same). KMainWindow sets // window role this way by default, and different KMainWindow // usually "are" different application from user's point of view. // This help with no-focus-stealing for e.g. konqy reusing. // On the other hand, if one of the windows is active, they are // considered belonging to the same application. This is for // the cases when opening new mainwindow directly from the application, // e.g. 'Open New Window' in konqy ( active_hack == true ). bool X11Client::sameAppWindowRoleMatch(const X11Client *c1, const X11Client *c2, bool active_hack) { if (c1->isTransient()) { while (const X11Client *t = dynamic_cast(c1->transientFor())) c1 = t; if (c1->groupTransient()) return c1->group() == c2->group(); #if 0 // if a group transient is in its own group, it didn't possibly have a group, // and therefore should be considered belonging to the same app like // all other windows from the same app || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; #endif } if (c2->isTransient()) { while (const X11Client *t = dynamic_cast(c2->transientFor())) c2 = t; if (c2->groupTransient()) return c1->group() == c2->group(); #if 0 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; #endif } int pos1 = c1->windowRole().indexOf('#'); int pos2 = c2->windowRole().indexOf('#'); if ((pos1 >= 0 && pos2 >= 0)) { if (!active_hack) // without the active hack for focus stealing prevention, return c1 == c2; // different mainwindows are always different apps if (!c1->isActive() && !c2->isActive()) return c1 == c2; else return true; } return true; } /* Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3 WM_TRANSIENT_FOR is basically means "this is my mainwindow". For NET::Unknown windows, transient windows are considered to be NET::Dialog windows, for compatibility with non-NETWM clients. KWin may adjust the value of this property in some cases (window pointing to itself or creating a loop, keeping NET::Splash windows above other windows from the same app, etc.). X11Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after possibly being adjusted by KWin. X11Client::transient_for points to the Client this Client is transient for, or is NULL. If X11Client::transient_for_id is poiting to the root window, the window is considered to be transient for the whole window group, as suggested in NETWM 7.3. In the case of group transient window, X11Client::transient_for is NULL, and X11Client::groupTransient() returns true. Such window is treated as if it were transient for every window in its window group that has been mapped _before_ it (or, to be exact, was added to the same group before it). Otherwise two group transients can create loops, which can lead very very nasty things (bug #67914 and all its dupes). X11Client::original_transient_for_id is the value of the property, which may be different if X11Client::transient_for_id if e.g. forcing NET::Splash to be kept on top of its window group, or when the mainwindow is not mapped yet, in which case the window is temporarily made group transient, and when the mainwindow is mapped, transiency is re-evaluated. This can get a bit complicated with with e.g. two Konqueror windows created by the same process. They should ideally appear like two independent applications to the user. This should be accomplished by all windows in the same process having the same window group (needs to be changed in Qt at the moment), and using non-group transients poiting to their relevant mainwindow for toolwindows etc. KWin should handle both group and non-group transient dialogs well. In other words: - non-transient windows : isTransient() == false - normal transients : transientFor() != NULL - group transients : groupTransient() == true - list of mainwindows : mainClients() (call once and loop over the result) - list of transients : transients() - every window in the group : group()->members() */ Xcb::TransientFor X11Client::fetchTransient() const { return Xcb::TransientFor(window()); } void X11Client::readTransientProperty(Xcb::TransientFor &transientFor) { xcb_window_t new_transient_for_id = XCB_WINDOW_NONE; if (transientFor.getTransientFor(&new_transient_for_id)) { m_originalTransientForId = new_transient_for_id; new_transient_for_id = verifyTransientFor(new_transient_for_id, true); } else { m_originalTransientForId = XCB_WINDOW_NONE; new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false); } setTransient(new_transient_for_id); } void X11Client::readTransient() { Xcb::TransientFor transientFor = fetchTransient(); readTransientProperty(transientFor); } void X11Client::setTransient(xcb_window_t new_transient_for_id) { if (new_transient_for_id != m_transientForId) { removeFromMainClients(); X11Client *transient_for = nullptr; m_transientForId = new_transient_for_id; if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) { transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId); Q_ASSERT(transient_for != nullptr); // verifyTransient() had to check this transient_for->addTransient(this); } // checkGroup() will check 'check_active_modal' setTransientFor(transient_for); checkGroup(nullptr, true); // force, because transiency has changed workspace()->updateClientLayer(this); workspace()->resetUpdateToolWindowsTimer(); emit transientChanged(); } } void X11Client::removeFromMainClients() { if (transientFor()) transientFor()->removeTransient(this); if (groupTransient()) { for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) (*it)->removeTransient(this); } } // *sigh* this transiency handling is madness :( // This one is called when destroying/releasing a window. // It makes sure this client is removed from all grouping // related lists. void X11Client::cleanGrouping() { // qDebug() << "CLEANGROUPING:" << this; // for ( auto it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL:" << *it; // QList mains; // mains = mainClients(); // for ( auto it = mains.begin(); // it != mains.end(); // ++it ) // qDebug() << "MN:" << *it; removeFromMainClients(); // qDebug() << "CLEANGROUPING2:" << this; // for ( auto it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL2:" << *it; // mains = mainClients(); // for ( auto it = mains.begin(); // it != mains.end(); // ++it ) // qDebug() << "MN2:" << *it; for (auto it = transients().constBegin(); it != transients().constEnd(); ) { if ((*it)->transientFor() == this) { removeTransient(*it); it = transients().constBegin(); // restart, just in case something more has changed with the list } else ++it; } // qDebug() << "CLEANGROUPING3:" << this; // for ( auto it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL3:" << *it; // mains = mainClients(); // for ( auto it = mains.begin(); // it != mains.end(); // ++it ) // qDebug() << "MN3:" << *it; // HACK // removeFromMainClients() did remove 'this' from transient // lists of all group members, but then made windows that // were transient for 'this' group transient, which again // added 'this' to those transient lists :( QList group_members = group()->members(); group()->removeMember(this); in_group = nullptr; for (auto it = group_members.constBegin(); it != group_members.constEnd(); ++it) (*it)->removeTransient(this); // qDebug() << "CLEANGROUPING4:" << this; // for ( auto it = group_members.begin(); // it != group_members.end(); // ++it ) // qDebug() << "CL4:" << *it; m_transientForId = XCB_WINDOW_NONE; } // Make sure that no group transient is considered transient // for a window that is (directly or indirectly) transient for it // (including another group transients). // Non-group transients not causing loops are checked in verifyTransientFor(). void X11Client::checkGroupTransients() { for (auto it1 = group()->members().constBegin(); it1 != group()->members().constEnd(); ++it1) { if (!(*it1)->groupTransient()) // check all group transients in the group continue; // TODO optimize to check only the changed ones? for (auto it2 = group()->members().constBegin(); it2 != group()->members().constEnd(); ++it2) { // group transients can be transient only for others in the group, // so don't make them transient for the ones that are transient for it if (*it1 == *it2) continue; for (AbstractClient* cl = (*it2)->transientFor(); cl != nullptr; cl = cl->transientFor()) { if (cl == *it1) { // don't use removeTransient(), that would modify *it2 too (*it2)->removeTransientFromList(*it1); continue; } } // if *it1 and *it2 are both group transients, and are transient for each other, // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later, // and should be therefore on top of *it1 // TODO This could possibly be optimized, it also requires hasTransient() to check for loops. if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true)) (*it2)->removeTransientFromList(*it1); // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3 // is added, make it transient only for W2, not for W1, because it's already indirectly // transient for it - the indirect transiency actually shouldn't break anything, // but it can lead to exponentially expensive operations (#95231) // TODO this is pretty slow as well for (auto it3 = group()->members().constBegin(); it3 != group()->members().constEnd(); ++it3) { if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3) continue; if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) { if ((*it2)->hasTransient(*it3, true)) (*it2)->removeTransientFromList(*it1); if ((*it3)->hasTransient(*it2, true)) (*it3)->removeTransientFromList(*it1); } } } } } /** * Check that the window is not transient for itself, and similar nonsense. */ xcb_window_t X11Client::verifyTransientFor(xcb_window_t new_transient_for, bool set) { xcb_window_t new_property_value = new_transient_for; // make sure splashscreens are shown above all their app's windows, even though // they're in Normal layer if (isSplash() && new_transient_for == XCB_WINDOW_NONE) new_transient_for = rootWindow(); if (new_transient_for == XCB_WINDOW_NONE) { if (set) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window new_property_value = new_transient_for = rootWindow(); else return XCB_WINDOW_NONE; } if (new_transient_for == window()) { // pointing to self // also fix the property itself qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ; new_property_value = new_transient_for = rootWindow(); } // The transient_for window may be embedded in another application, // so kwin cannot see it. Try to find the managed client for the // window and fix the transient_for property if possible. xcb_window_t before_search = new_transient_for; while (new_transient_for != XCB_WINDOW_NONE && new_transient_for != rootWindow() && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { Xcb::Tree tree(new_transient_for); if (tree.isNull()) { break; } new_transient_for = tree->parent; } if (X11Client *new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { if (new_transient_for != before_search) { qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window " << before_search << ", child of " << new_transient_for_client << ", adjusting."; new_property_value = new_transient_for; // also fix the property } } else new_transient_for = before_search; // nice try // loop detection // group transients cannot cause loops, because they're considered transient only for non-transient // windows in the group int count = 20; xcb_window_t loop_pos = new_transient_for; while (loop_pos != XCB_WINDOW_NONE && loop_pos != rootWindow()) { X11Client *pos = workspace()->findClient(Predicate::WindowMatch, loop_pos); if (pos == nullptr) break; loop_pos = pos->m_transientForId; if (--count == 0 || pos == this) { qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ; new_transient_for = rootWindow(); } } if (new_transient_for != rootWindow() && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == nullptr) { // it's transient for a specific window, but that window is not mapped new_transient_for = rootWindow(); } if (new_property_value != m_originalTransientForId) Xcb::setTransientFor(window(), new_property_value); return new_transient_for; } void X11Client::addTransient(AbstractClient* cl) { AbstractClient::addTransient(cl); if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal()) check_active_modal = true; // qDebug() << "ADDTRANS:" << this << ":" << cl; // qDebug() << kBacktrace(); // for ( auto it = transients_list.begin(); // it != transients_list.end(); // ++it ) // qDebug() << "AT:" << (*it); } void X11Client::removeTransient(AbstractClient* cl) { // qDebug() << "REMOVETRANS:" << this << ":" << cl; // qDebug() << kBacktrace(); // cl is transient for this, but this is going away // make cl group transient AbstractClient::removeTransient(cl); if (cl->transientFor() == this) { if (X11Client *c = dynamic_cast(cl)) { c->m_transientForId = XCB_WINDOW_NONE; c->setTransientFor(nullptr); // SELI // SELI cl->setTransient( rootWindow()); c->setTransient(XCB_WINDOW_NONE); } } } // A new window has been mapped. Check if it's not a mainwindow for this already existing window. void X11Client::checkTransient(xcb_window_t w) { if (m_originalTransientForId != w) return; w = verifyTransientFor(w, true); setTransient(w); } // returns true if cl is the transient_for window for this client, // or recursively the transient_for window bool X11Client::hasTransient(const AbstractClient* cl, bool indirect) const { if (const X11Client *c = dynamic_cast(cl)) { // checkGroupTransients() uses this to break loops, so hasTransient() must detect them QList set; return hasTransientInternal(c, indirect, set); } return false; } bool X11Client::hasTransientInternal(const X11Client *cl, bool indirect, QList &set) const { if (const X11Client *t = dynamic_cast(cl->transientFor())) { if (t == this) return true; if (!indirect) return false; if (set.contains(cl)) return false; set.append(cl); return hasTransientInternal(t, indirect, set); } if (!cl->isTransient()) return false; if (group() != cl->group()) return false; // cl is group transient, search from top if (transients().contains(const_cast< X11Client *>(cl))) return true; if (!indirect) return false; if (set.contains(this)) return false; set.append(this); for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) { const X11Client *c = qobject_cast(*it); if (!c) { continue; } if (c->hasTransientInternal(cl, indirect, set)) return true; } return false; } QList X11Client::mainClients() const { if (!isTransient()) return QList(); if (const AbstractClient *t = transientFor()) return QList{const_cast< AbstractClient* >(t)}; QList result; Q_ASSERT(group()); for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) if ((*it)->hasTransient(this, false)) result.append(*it); return result; } AbstractClient* X11Client::findModal(bool allow_itself) { for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) if (AbstractClient* ret = (*it)->findModal(true)) return ret; if (isModal() && allow_itself) return this; return nullptr; } // X11Client::window_group only holds the contents of the hint, // but it should be used only to find the group, not for anything else // Argument is only when some specific group needs to be set. void X11Client::checkGroup(Group* set_group, bool force) { Group* old_group = in_group; if (old_group != nullptr) old_group->ref(); // turn off automatic deleting if (set_group != nullptr) { if (set_group != in_group) { if (in_group != nullptr) in_group->removeMember(this); in_group = set_group; in_group->addMember(this); } } else if (info->groupLeader() != XCB_WINDOW_NONE) { Group* new_group = workspace()->findGroup(info->groupLeader()); X11Client *t = qobject_cast(transientFor()); if (t != nullptr && t->group() != new_group) { // move the window to the right group (e.g. a dialog provided // by different app, but transient for this one, so make it part of that group) new_group = t->group(); } if (new_group == nullptr) // doesn't exist yet new_group = new Group(info->groupLeader()); if (new_group != in_group) { if (in_group != nullptr) in_group->removeMember(this); in_group = new_group; in_group->addMember(this); } } else { if (X11Client *t = qobject_cast(transientFor())) { // doesn't have window group set, but is transient for something // so make it part of that group Group* new_group = t->group(); if (new_group != in_group) { if (in_group != nullptr) in_group->removeMember(this); in_group = t->group(); in_group->addMember(this); } } else if (groupTransient()) { // group transient which actually doesn't have a group :( // try creating group with other windows with the same client leader Group* new_group = workspace()->findClientLeaderGroup(this); if (new_group == nullptr) new_group = new Group(XCB_WINDOW_NONE); if (new_group != in_group) { if (in_group != nullptr) in_group->removeMember(this); in_group = new_group; in_group->addMember(this); } } else { // Not transient without a group, put it in its client leader group. // This might be stupid if grouping was used for e.g. taskbar grouping // or minimizing together the whole group, but as long as it is used // only for dialogs it's better to keep windows from one app in one group. Group* new_group = workspace()->findClientLeaderGroup(this); if (in_group != nullptr && in_group != new_group) { in_group->removeMember(this); in_group = nullptr; } if (new_group == nullptr) new_group = new Group(XCB_WINDOW_NONE); if (in_group != new_group) { in_group = new_group; in_group->addMember(this); } } } if (in_group != old_group || force) { for (auto it = transients().constBegin(); it != transients().constEnd(); ) { auto *c = *it; // group transients in the old group are no longer transient for it if (c->groupTransient() && c->group() != group()) { removeTransientFromList(c); it = transients().constBegin(); // restart, just in case something more has changed with the list } else ++it; } if (groupTransient()) { // no longer transient for ones in the old group if (old_group != nullptr) { for (auto it = old_group->members().constBegin(); it != old_group->members().constEnd(); ++it) (*it)->removeTransient(this); } // and make transient for all in the new group for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) { if (*it == this) break; // this means the window is only transient for windows mapped before it (*it)->addTransient(this); } } // group transient splashscreens should be transient even for windows // in group mapped later for (auto it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) { if (!(*it)->isSplash()) continue; if (!(*it)->groupTransient()) continue; if (*it == this || hasTransient(*it, true)) // TODO indirect? continue; addTransient(*it); } } if (old_group != nullptr) old_group->deref(); // can be now deleted if empty checkGroupTransients(); checkActiveModal(); workspace()->updateClientLayer(this); } // used by Workspace::findClientLeaderGroup() void X11Client::changeClientLeaderGroup(Group* gr) { // transientFor() != NULL are in the group of their mainwindow, so keep them there if (transientFor() != nullptr) return; // also don't change the group for window which have group set if (info->groupLeader()) return; checkGroup(gr); // change group } bool X11Client::check_active_modal = false; void X11Client::checkActiveModal() { // if the active window got new modal transient, activate it. // cannot be done in AddTransient(), because there may temporarily // exist loops, breaking findModal X11Client *check_modal = dynamic_cast(workspace()->mostRecentlyActivatedClient()); if (check_modal != nullptr && check_modal->check_active_modal) { X11Client *new_modal = dynamic_cast(check_modal->findModal()); if (new_modal != nullptr && new_modal != check_modal) { if (!new_modal->isManaged()) return; // postpone check until end of manage() workspace()->activateClient(new_modal); } check_modal->check_active_modal = false; } } /** * Calculate the appropriate frame size for the given client size \a * wsize. * * \a wsize is adapted according to the window's size hints (minimum, * maximum and incremental size changes). */ QSize X11Client::sizeForClientSize(const QSize& wsize, SizeMode mode, bool noframe) const { int w = wsize.width(); int h = wsize.height(); if (w < 1 || h < 1) { qCWarning(KWIN_CORE) << "sizeForClientSize() with empty size!" ; } if (w < 1) w = 1; if (h < 1) h = 1; // basesize, minsize, maxsize, paspect and resizeinc have all values defined, // even if they're not set in flags - see getWmNormalHints() QSize min_size = minSize(); QSize max_size = maxSize(); if (isDecorated()) { QSize decominsize(0, 0); QSize border_size(borderLeft() + borderRight(), borderTop() + borderBottom()); if (border_size.width() > decominsize.width()) // just in case decominsize.setWidth(border_size.width()); if (border_size.height() > decominsize.height()) decominsize.setHeight(border_size.height()); if (decominsize.width() > min_size.width()) min_size.setWidth(decominsize.width()); if (decominsize.height() > min_size.height()) min_size.setHeight(decominsize.height()); } w = qMin(max_size.width(), w); h = qMin(max_size.height(), h); w = qMax(min_size.width(), w); h = qMax(min_size.height(), h); int w1 = w; int h1 = h; int width_inc = m_geometryHints.resizeIncrements().width(); int height_inc = m_geometryHints.resizeIncrements().height(); int basew_inc = m_geometryHints.baseSize().width(); int baseh_inc = m_geometryHints.baseSize().height(); if (!m_geometryHints.hasBaseSize()) { basew_inc = m_geometryHints.minSize().width(); baseh_inc = m_geometryHints.minSize().height(); } w = int((w - basew_inc) / width_inc) * width_inc + basew_inc; h = int((h - baseh_inc) / height_inc) * height_inc + baseh_inc; // code for aspect ratios based on code from FVWM /* * The math looks like this: * * minAspectX dwidth maxAspectX * ---------- <= ------- <= ---------- * minAspectY dheight maxAspectY * * If that is multiplied out, then the width and height are * invalid in the following situations: * * minAspectX * dheight > minAspectY * dwidth * maxAspectX * dheight < maxAspectY * dwidth * */ if (m_geometryHints.hasAspect()) { double min_aspect_w = m_geometryHints.minAspect().width(); // use doubles, because the values can be MAX_INT double min_aspect_h = m_geometryHints.minAspect().height(); // and multiplying would go wrong otherwise double max_aspect_w = m_geometryHints.maxAspect().width(); double max_aspect_h = m_geometryHints.maxAspect().height(); // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments, // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time, // and I have no idea how it works, let's hope nobody relies on that. const QSize baseSize = m_geometryHints.baseSize(); w -= baseSize.width(); h -= baseSize.height(); int max_width = max_size.width() - baseSize.width(); int min_width = min_size.width() - baseSize.width(); int max_height = max_size.height() - baseSize.height(); int min_height = min_size.height() - baseSize.height(); #define ASPECT_CHECK_GROW_W \ if ( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if ( w + delta <= max_width ) \ w += delta; \ } #define ASPECT_CHECK_SHRINK_H_GROW_W \ if ( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \ if ( h - delta >= min_height ) \ h -= delta; \ else \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if ( w + delta <= max_width ) \ w += delta; \ } \ } #define ASPECT_CHECK_GROW_H \ if ( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if ( h + delta <= max_height ) \ h += delta; \ } #define ASPECT_CHECK_SHRINK_W_GROW_H \ if ( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \ if ( w - delta >= min_width ) \ w -= delta; \ else \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if ( h + delta <= max_height ) \ h += delta; \ } \ } switch(mode) { case SizeModeAny: #if 0 // make SizeModeAny equal to SizeModeFixedW - prefer keeping fixed width, // so that changing aspect ratio to a different value and back keeps the same size (#87298) { ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_H ASPECT_CHECK_GROW_W break; } #endif case SizeModeFixedW: { // the checks are order so that attempts to modify height are first ASPECT_CHECK_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W break; } case SizeModeFixedH: { ASPECT_CHECK_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_GROW_H break; } case SizeModeMax: { // first checks that try to shrink ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W ASPECT_CHECK_GROW_H break; } } #undef ASPECT_CHECK_SHRINK_H_GROW_W #undef ASPECT_CHECK_SHRINK_W_GROW_H #undef ASPECT_CHECK_GROW_W #undef ASPECT_CHECK_GROW_H w += baseSize.width(); h += baseSize.height(); } if (!rules()->checkStrictGeometry(!isFullScreen())) { // disobey increments and aspect by explicit rule w = w1; h = h1; } QSize size(w, h); if (!noframe) { size = clientSizeToFrameSize(size); } return rules()->checkSize(size); } /** * Gets the client's normal WM hints and reconfigures itself respectively. */ void X11Client::getWmNormalHints() { const bool hadFixedAspect = m_geometryHints.hasAspect(); // roundtrip to X server m_geometryHints.fetch(); m_geometryHints.read(); if (!hadFixedAspect && m_geometryHints.hasAspect()) { // align to eventual new constraints maximize(max_mode); } if (isManaged()) { // update to match restrictions QSize new_size = adjustedSize(); if (new_size != size() && !isFullScreen()) { QRect origClientGeometry = m_clientGeometry; resizeWithChecks(new_size); if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea(MovementArea, this); if (area.contains(origClientGeometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(origClientGeometry)) keepInArea(area); } } } updateAllowedActions(); // affects isResizeable() } QSize X11Client::minSize() const { return rules()->checkMinSize(m_geometryHints.minSize()); } QSize X11Client::maxSize() const { return rules()->checkMaxSize(m_geometryHints.maxSize()); } QSize X11Client::basicUnit() const { return m_geometryHints.resizeIncrements(); } /** * Auxiliary function to inform the client about the current window * configuration. */ void X11Client::sendSyntheticConfigureNotify() { xcb_configure_notify_event_t c; memset(&c, 0, sizeof(c)); c.response_type = XCB_CONFIGURE_NOTIFY; c.event = window(); c.window = window(); c.x = m_clientGeometry.x(); c.y = m_clientGeometry.y(); c.width = m_clientGeometry.width(); c.height = m_clientGeometry.height(); c.border_width = 0; c.above_sibling = XCB_WINDOW_NONE; c.override_redirect = 0; xcb_send_event(connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast(&c)); xcb_flush(connection()); } QPoint X11Client::gravityAdjustment(xcb_gravity_t gravity) const { int dx = 0; int dy = 0; // dx, dy specify how the client window moves to make space for the frame. // In general we have to compute the reference point and from that figure // out how much we need to shift the client, however given that we ignore // the border width attribute and the extents of the server-side decoration // are known in advance, we can simplify the math quite a bit and express // the required window gravity adjustment in terms of border sizes. switch(gravity) { case XCB_GRAVITY_NORTH_WEST: // move down right default: dx = borderLeft(); dy = borderTop(); break; case XCB_GRAVITY_NORTH: // move right dx = 0; dy = borderTop(); break; case XCB_GRAVITY_NORTH_EAST: // move down left dx = -borderRight(); dy = borderTop(); break; case XCB_GRAVITY_WEST: // move right dx = borderLeft(); dy = 0; break; case XCB_GRAVITY_CENTER: dx = (borderLeft() - borderRight()) / 2; dy = (borderTop() - borderBottom()) / 2; break; case XCB_GRAVITY_STATIC: // don't move dx = 0; dy = 0; break; case XCB_GRAVITY_EAST: // move left dx = -borderRight(); dy = 0; break; case XCB_GRAVITY_SOUTH_WEST: // move up right dx = borderLeft() ; dy = -borderBottom(); break; case XCB_GRAVITY_SOUTH: // move up dx = 0; dy = -borderBottom(); break; case XCB_GRAVITY_SOUTH_EAST: // move up left dx = -borderRight(); dy = -borderBottom(); break; } return QPoint(dx, dy); } const QPoint X11Client::calculateGravitation(bool invert) const { const QPoint adjustment = gravityAdjustment(m_geometryHints.windowGravity()); // translate from client movement to frame movement const int dx = adjustment.x() - borderLeft(); const int dy = adjustment.y() - borderTop(); if (!invert) return QPoint(x() + dx, y() + dy); else return QPoint(x() - dx, y() - dy); } void X11Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool) { const int configurePositionMask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; const int configureSizeMask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; const int configureGeometryMask = configurePositionMask | configureSizeMask; // "maximized" is a user setting -> we do not allow the client to resize itself // away from this & against the users explicit wish qCDebug(KWIN_CORE) << this << bool(value_mask & configureGeometryMask) << bool(maximizeMode() & MaximizeVertical) << bool(maximizeMode() & MaximizeHorizontal); // we want to (partially) ignore the request when the window is somehow maximized or quicktiled bool ignore = !app_noborder && (quickTileMode() != QuickTileMode(QuickTileFlag::None) || maximizeMode() != MaximizeRestore); // however, the user shall be able to force obedience despite and also disobedience in general ignore = rules()->checkIgnoreGeometry(ignore); if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it. updateQuickTileMode(QuickTileFlag::None); max_mode = MaximizeRestore; emit quickTileModeChanged(); } else if (!app_noborder && quickTileMode() == QuickTileMode(QuickTileFlag::None) && (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) { // ignoring can be, because either we do, or the user does explicitly not want it. // for partially maximized windows we want to allow configures in the other dimension. // so we've to ask the user again - to know whether we just ignored for the partial maximization. // the problem here is, that the user can explicitly permit configure requests - even for maximized windows! // we cannot distinguish that from passing "false" for partially maximized windows. ignore = rules()->checkIgnoreGeometry(false); if (!ignore) { // the user is not interested, so we fix up dimensions if (maximizeMode() == MaximizeVertical) value_mask &= ~(XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT); if (maximizeMode() == MaximizeHorizontal) value_mask &= ~(XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH); if (!(value_mask & configureGeometryMask)) { ignore = true; // the modification turned the request void } } } if (ignore) { qCDebug(KWIN_CORE) << "DENIED"; return; // nothing to (left) to do for use - bugs #158974, #252314, #321491 } qCDebug(KWIN_CORE) << "PERMITTED" << this << bool(value_mask & configureGeometryMask); if (gravity == 0) // default (nonsense) value for the argument gravity = m_geometryHints.windowGravity(); if (value_mask & configurePositionMask) { QPoint new_pos = framePosToClientPos(pos()); new_pos -= gravityAdjustment(xcb_gravity_t(gravity)); if (value_mask & XCB_CONFIG_WINDOW_X) { new_pos.setX(rx); } if (value_mask & XCB_CONFIG_WINDOW_Y) { new_pos.setY(ry); } // clever(?) workaround for applications like xv that want to set // the location to the current location but miscalculate the // frame size due to kwin being a double-reparenting window // manager if (new_pos.x() == m_clientGeometry.x() && new_pos.y() == m_clientGeometry.y() && gravity == XCB_GRAVITY_NORTH_WEST && !from_tool) { new_pos.setX(x()); new_pos.setY(y()); } new_pos += gravityAdjustment(xcb_gravity_t(gravity)); new_pos = clientPosToFramePos(new_pos); int nw = clientSize().width(); int nh = clientSize().height(); if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { nw = rw; } if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { nh = rh; } QSize ns = sizeForClientSize(QSize(nw, nh)); // enforces size if needed new_pos = rules()->checkPosition(new_pos); int newScreen = screens()->number(QRect(new_pos, ns).center()); if (newScreen != rules()->checkScreen(newScreen)) return; // not allowed by rule QRect origClientGeometry = m_clientGeometry; GeometryUpdatesBlocker blocker(this); move(new_pos); plainResize(ns); QRect area = workspace()->clientArea(WorkArea, this); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen() && area.contains(origClientGeometry)) keepInArea(area); // this is part of the kicker-xinerama-hack... it should be // safe to remove when kicker gets proper ExtendedStrut support; // see Workspace::updateClientArea() and // X11Client::adjustedClientArea() if (hasStrut()) workspace() -> updateClientArea(); } if (value_mask & configureSizeMask && !(value_mask & configurePositionMask)) { // pure resize int nw = clientSize().width(); int nh = clientSize().height(); if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { nw = rw; } if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { nh = rh; } QSize ns = sizeForClientSize(QSize(nw, nh)); if (ns != size()) { // don't restore if some app sets its own size again QRect origClientGeometry = m_clientGeometry; GeometryUpdatesBlocker blocker(this); resizeWithChecks(ns, xcb_gravity_t(gravity)); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea(MovementArea, this); if (area.contains(origClientGeometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(origClientGeometry)) keepInArea(area); } } } setGeometryRestore(frameGeometry()); // No need to send synthetic configure notify event here, either it's sent together // with geometry change, or there's no need to send it. // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. } void X11Client::resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force) { Q_ASSERT(!shade_geometry_change); if (isShade()) { if (h == borderTop() + borderBottom()) { qCWarning(KWIN_CORE) << "Shaded geometry passed for size:" ; } } int newx = x(); int newy = y(); QRect area = workspace()->clientArea(WorkArea, this); // don't allow growing larger than workarea if (w > area.width()) w = area.width(); if (h > area.height()) h = area.height(); QSize tmp = adjustedSize(QSize(w, h)); // checks size constraints, including min/max size w = tmp.width(); h = tmp.height(); if (gravity == 0) { gravity = m_geometryHints.windowGravity(); } switch(gravity) { case XCB_GRAVITY_NORTH_WEST: // top left corner doesn't move default: break; case XCB_GRAVITY_NORTH: // middle of top border doesn't move newx = (newx + width() / 2) - (w / 2); break; case XCB_GRAVITY_NORTH_EAST: // top right corner doesn't move newx = newx + width() - w; break; case XCB_GRAVITY_WEST: // middle of left border doesn't move newy = (newy + height() / 2) - (h / 2); break; case XCB_GRAVITY_CENTER: // middle point doesn't move newx = (newx + width() / 2) - (w / 2); newy = (newy + height() / 2) - (h / 2); break; case XCB_GRAVITY_STATIC: // top left corner of _client_ window doesn't move // since decoration doesn't change, equal to NorthWestGravity break; case XCB_GRAVITY_EAST: // // middle of right border doesn't move newx = newx + width() - w; newy = (newy + height() / 2) - (h / 2); break; case XCB_GRAVITY_SOUTH_WEST: // bottom left corner doesn't move newy = newy + height() - h; break; case XCB_GRAVITY_SOUTH: // middle of bottom border doesn't move newx = (newx + width() / 2) - (w / 2); newy = newy + height() - h; break; case XCB_GRAVITY_SOUTH_EAST: // bottom right corner doesn't move newx = newx + width() - w; newy = newy + height() - h; break; } setFrameGeometry(newx, newy, w, h, force); } // _NET_MOVERESIZE_WINDOW void X11Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height) { int gravity = flags & 0xff; int value_mask = 0; if (flags & (1 << 8)) { value_mask |= XCB_CONFIG_WINDOW_X; } if (flags & (1 << 9)) { value_mask |= XCB_CONFIG_WINDOW_Y; } if (flags & (1 << 10)) { value_mask |= XCB_CONFIG_WINDOW_WIDTH; } if (flags & (1 << 11)) { value_mask |= XCB_CONFIG_WINDOW_HEIGHT; } configureRequest(value_mask, x, y, width, height, gravity, true); } bool X11Client::isMovable() const { if (!hasNETSupport() && !m_motif.move()) { return false; } if (isFullScreen()) return false; if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position return false; return true; } bool X11Client::isMovableAcrossScreens() const { if (!hasNETSupport() && !m_motif.move()) { return false; } if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position return false; return true; } bool X11Client::isResizable() const { if (!hasNETSupport() && !m_motif.resize()) { return false; } if (isFullScreen()) return false; if (isSpecialWindow() || isSplash() || isToolbar()) return false; if (rules()->checkSize(QSize()).isValid()) // forced size return false; const Position mode = moveResizePointerMode(); if ((mode == PositionTop || mode == PositionTopLeft || mode == PositionTopRight || mode == PositionLeft || mode == PositionBottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint) return false; QSize min = minSize(); QSize max = maxSize(); return min.width() < max.width() || min.height() < max.height(); } bool X11Client::isMaximizable() const { if (!isResizable() || isToolbar()) // SELI isToolbar() ? return false; if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore) return true; return false; } /** * Reimplemented to inform the client about the new window position. */ void X11Client::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t force) { // this code is also duplicated in X11Client::plainResize() // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry, // simply because there are too many places dealing with geometry. Those places // ignore shaded state and use normal geometry, which they usually should get // from adjustedSize(). Such geometry comes here, and if the window is shaded, // the geometry is used only for client_size, since that one is not used when // shading. Then the frame geometry is adjusted for the shaded geometry. // This gets more complicated in the case the code does only something like // setGeometry( geometry()) - geometry() will return the shaded frame geometry. // Such code is wrong and should be changed to handle the case when the window is shaded, // for example using X11Client::clientSize() QRect frameGeometry(x, y, w, h); if (shade_geometry_change) ; // nothing else if (isShade()) { if (frameGeometry.height() == borderTop() + borderBottom()) { qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; } else { m_clientGeometry = frameRectToClientRect(frameGeometry); frameGeometry.setHeight(borderTop() + borderBottom()); } } else { m_clientGeometry = frameRectToClientRect(frameGeometry); } const QRect bufferGeometry = frameRectToBufferRect(frameGeometry); if (!areGeometryUpdatesBlocked() && frameGeometry != rules()->checkGeometry(frameGeometry)) { qCDebug(KWIN_CORE) << "forced geometry fail:" << frameGeometry << ":" << rules()->checkGeometry(frameGeometry); } m_frameGeometry = frameGeometry; if (force == NormalGeometrySet && m_bufferGeometry == bufferGeometry && pendingGeometryUpdate() == PendingGeometryNone) { return; } m_bufferGeometry = bufferGeometry; if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } updateServerGeometry(); updateWindowRules(Rules::Position|Rules::Size); // keep track of old maximize mode // to detect changes screens()->setCurrent(this); workspace()->updateStackingOrder(); // Need to regenerate decoration pixmaps when the buffer size is changed. if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) { discardWindowPixmap(); } if (frameGeometryBeforeUpdateBlocking() != m_frameGeometry) { emit frameGeometryChanged(this, frameGeometryBeforeUpdateBlocking()); } emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking()); addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); } void X11Client::plainResize(int w, int h, ForceGeometry_t force) { QSize frameSize(w, h); QSize bufferSize; // this code is also duplicated in X11Client::setGeometry(), and it's also commented there if (shade_geometry_change) ; // nothing else if (isShade()) { if (frameSize.height() == borderTop() + borderBottom()) { qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; } else { m_clientGeometry.setSize(frameSizeToClientSize(frameSize)); frameSize.setHeight(borderTop() + borderBottom()); } } else { m_clientGeometry.setSize(frameSizeToClientSize(frameSize)); } if (isDecorated()) { bufferSize = frameSize; } else { bufferSize = m_clientGeometry.size(); } if (!areGeometryUpdatesBlocked() && frameSize != rules()->checkSize(frameSize)) { qCDebug(KWIN_CORE) << "forced size fail:" << frameSize << ":" << rules()->checkSize(frameSize); } m_frameGeometry.setSize(frameSize); // resuming geometry updates is handled only in setGeometry() Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); if (force == NormalGeometrySet && m_bufferGeometry.size() == bufferSize) { return; } m_bufferGeometry.setSize(bufferSize); if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } updateServerGeometry(); updateWindowRules(Rules::Position|Rules::Size); screens()->setCurrent(this); workspace()->updateStackingOrder(); if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) { discardWindowPixmap(); } if (frameGeometryBeforeUpdateBlocking() != frameGeometry()) { emit frameGeometryChanged(this, frameGeometryBeforeUpdateBlocking()); } emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking()); addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); } void X11Client::updateServerGeometry() { const QRect oldBufferGeometry = bufferGeometryBeforeUpdateBlocking(); if (oldBufferGeometry.size() != m_bufferGeometry.size() || pendingGeometryUpdate() == PendingGeometryForced) { resizeDecoration(); // If the client is being interactively resized, then the frame window, the wrapper window, // and the client window have correct geometry at this point, so we don't have to configure // them again. If the client doesn't support frame counters, always update geometry. const bool needsGeometryUpdate = !isResize() || m_syncRequest.counter == XCB_NONE; if (needsGeometryUpdate) { m_frame.setGeometry(m_bufferGeometry); } if (!isShade()) { if (needsGeometryUpdate) { m_wrapper.setGeometry(QRect(clientPos(), clientSize())); m_client.resize(clientSize()); } // SELI - won't this be too expensive? // THOMAS - yes, but gtk+ clients will not resize without ... sendSyntheticConfigureNotify(); } updateShape(); } else { if (isMoveResize()) { if (compositing()) { // Defer the X update until we leave this mode needsXWindowMove = true; } else { m_frame.move(m_bufferGeometry.topLeft()); // sendSyntheticConfigureNotify() on finish shall be sufficient } } else { m_frame.move(m_bufferGeometry.topLeft()); sendSyntheticConfigureNotify(); } // Unconditionally move the input window: it won't affect rendering m_decoInputExtent.move(pos() + inputPos()); } } static bool changeMaximizeRecursion = false; void X11Client::changeMaximize(bool horizontal, bool vertical, bool adjust) { if (changeMaximizeRecursion) return; if (!isResizable() || isToolbar()) // SELI isToolbar() ? return; QRect clientArea; if (isElectricBorderMaximizing()) clientArea = workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop()); else clientArea = workspace()->clientArea(MaximizeArea, this); MaximizeMode old_mode = max_mode; // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) max_mode = MaximizeMode(max_mode ^ MaximizeVertical); if (horizontal) max_mode = MaximizeMode(max_mode ^ MaximizeHorizontal); } // if the client insist on a fix aspect ratio, we check whether the maximizing will get us // out of screen bounds and take that as a "full maximization with aspect check" then if (m_geometryHints.hasAspect() && // fixed aspect (max_mode == MaximizeVertical || max_mode == MaximizeHorizontal) && // ondimensional maximization rules()->checkStrictGeometry(true)) { // obey aspect const QSize minAspect = m_geometryHints.minAspect(); const QSize maxAspect = m_geometryHints.maxAspect(); if (max_mode == MaximizeVertical || (old_mode & MaximizeVertical)) { const double fx = minAspect.width(); // use doubles, because the values can be MAX_INT const double fy = maxAspect.height(); // use doubles, because the values can be MAX_INT if (fx*clientArea.height()/fy > clientArea.width()) // too big max_mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull; } else { // max_mode == MaximizeHorizontal const double fx = maxAspect.width(); const double fy = minAspect.height(); if (fy*clientArea.width()/fx > clientArea.height()) // too big max_mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull; } } max_mode = rules()->checkMaximize(max_mode); if (!adjust && max_mode == old_mode) return; GeometryUpdatesBlocker blocker(this); // maximing one way and unmaximizing the other way shouldn't happen, // so restore first and then maximize the other way if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal) || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) { changeMaximize(false, false, false); // restore } // save sizes for restoring, if maximalizing QSize sz; if (isShade()) sz = sizeForClientSize(clientSize()); else sz = size(); if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { QRect savedGeometry = geometryRestore(); if (!adjust && !(old_mode & MaximizeVertical)) { savedGeometry.setTop(y()); savedGeometry.setHeight(sz.height()); } if (!adjust && !(old_mode & MaximizeHorizontal)) { savedGeometry.setLeft(x()); savedGeometry.setWidth(sz.width()); } setGeometryRestore(savedGeometry); } // call into decoration update borders if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && max_mode == KWin::MaximizeFull)) { changeMaximizeRecursion = true; const auto c = decoration()->client().data(); if ((max_mode & MaximizeVertical) != (old_mode & MaximizeVertical)) { emit c->maximizedVerticallyChanged(max_mode & MaximizeVertical); } if ((max_mode & MaximizeHorizontal) != (old_mode & MaximizeHorizontal)) { emit c->maximizedHorizontallyChanged(max_mode & MaximizeHorizontal); } if ((max_mode == MaximizeFull) != (old_mode == MaximizeFull)) { emit c->maximizedChanged(max_mode & MaximizeFull); } changeMaximizeRecursion = false; } if (options->borderlessMaximizedWindows()) { // triggers a maximize change. // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry changeMaximizeRecursion = true; setNoBorder(rules()->checkNoBorder(app_noborder || (m_motif.hasDecoration() && m_motif.noBorder()) || max_mode == MaximizeFull)); changeMaximizeRecursion = false; } const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; // Conditional quick tiling exit points if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { if (old_mode == MaximizeFull && !clientArea.contains(geometryRestore().center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason //quick_tile_mode = QuickTileFlag::None; // And exit quick tile mode manually } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) || (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) { // Modifying geometry of a tiled window updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry } } switch(max_mode) { case MaximizeVertical: { if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull if (geometryRestore().width() == 0 || !clientArea.contains(geometryRestore().center())) { // needs placement plainResize(adjustedSize(QSize(width() * 2 / 3, clientArea.height()), SizeModeFixedH), geom_mode); Placement::self()->placeSmart(this, clientArea); } else { setFrameGeometry(QRect(QPoint(geometryRestore().x(), clientArea.top()), adjustedSize(QSize(geometryRestore().width(), clientArea.height()), SizeModeFixedH)), geom_mode); } } else { QRect r(x(), clientArea.top(), width(), clientArea.height()); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizeModeFixedH)); setFrameGeometry(r, geom_mode); } info->setState(NET::MaxVert, NET::Max); break; } case MaximizeHorizontal: { if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull if (geometryRestore().height() == 0 || !clientArea.contains(geometryRestore().center())) { // needs placement plainResize(adjustedSize(QSize(clientArea.width(), height() * 2 / 3), SizeModeFixedW), geom_mode); Placement::self()->placeSmart(this, clientArea); } else { setFrameGeometry(QRect(QPoint(clientArea.left(), geometryRestore().y()), adjustedSize(QSize(clientArea.width(), geometryRestore().height()), SizeModeFixedW)), geom_mode); } } else { QRect r(clientArea.left(), y(), clientArea.width(), height()); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizeModeFixedW)); setFrameGeometry(r, geom_mode); } info->setState(NET::MaxHoriz, NET::Max); break; } case MaximizeRestore: { QRect restore = frameGeometry(); // when only partially maximized, geom_restore may not have the other dimension remembered if (old_mode & MaximizeVertical) { restore.setTop(geometryRestore().top()); restore.setBottom(geometryRestore().bottom()); } if (old_mode & MaximizeHorizontal) { restore.setLeft(geometryRestore().left()); restore.setRight(geometryRestore().right()); } if (!restore.isValid()) { QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3); if (geometryRestore().width() > 0) { s.setWidth(geometryRestore().width()); } if (geometryRestore().height() > 0) { s.setHeight(geometryRestore().height()); } plainResize(adjustedSize(s)); Placement::self()->placeSmart(this, clientArea); restore = frameGeometry(); if (geometryRestore().width() > 0) { restore.moveLeft(geometryRestore().x()); } if (geometryRestore().height() > 0) { restore.moveTop(geometryRestore().y()); } setGeometryRestore(restore); // relevant for mouse pos calculation, bug #298646 } if (m_geometryHints.hasAspect()) { restore.setSize(adjustedSize(restore.size(), SizeModeAny)); } setFrameGeometry(restore, geom_mode); if (!clientArea.contains(geometryRestore().center())) { // Not restoring to the same screen Placement::self()->place(this, clientArea); } info->setState(NET::States(), NET::Max); updateQuickTileMode(QuickTileFlag::None); break; } case MaximizeFull: { QRect r(clientArea); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizeModeMax)); if (r.size() != clientArea.size()) { // to avoid off-by-one errors... if (isElectricBorderMaximizing() && r.width() < clientArea.width()) { r.moveLeft(qMax(clientArea.left(), Cursor::pos().x() - r.width()/2)); r.moveRight(qMin(clientArea.right(), r.right())); } else { r.moveCenter(clientArea.center()); const bool closeHeight = r.height() > 97*clientArea.height()/100; const bool closeWidth = r.width() > 97*clientArea.width() /100; const bool overHeight = r.height() > clientArea.height(); const bool overWidth = r.width() > clientArea.width(); if (closeWidth || closeHeight) { Position titlePos = titlebarPosition(); const QRect screenArea = workspace()->clientArea(ScreenArea, clientArea.center(), desktop()); if (closeHeight) { bool tryBottom = titlePos == PositionBottom; if ((overHeight && titlePos == PositionTop) || screenArea.top() == clientArea.top()) r.setTop(clientArea.top()); else tryBottom = true; if (tryBottom && (overHeight || screenArea.bottom() == clientArea.bottom())) r.setBottom(clientArea.bottom()); } if (closeWidth) { bool tryLeft = titlePos == PositionLeft; if ((overWidth && titlePos == PositionRight) || screenArea.right() == clientArea.right()) r.setRight(clientArea.right()); else tryLeft = true; if (tryLeft && (overWidth || screenArea.left() == clientArea.left())) r.setLeft(clientArea.left()); } } } r.moveTopLeft(rules()->checkPosition(r.topLeft())); } setFrameGeometry(r, geom_mode); if (options->electricBorderMaximize() && r.top() == clientArea.top()) updateQuickTileMode(QuickTileFlag::Maximize); else updateQuickTileMode(QuickTileFlag::None); info->setState(NET::Max, NET::Max); break; } default: break; } updateAllowedActions(); updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size); emit quickTileModeChanged(); } bool X11Client::userCanSetFullScreen() const { if (!isFullScreenable()) { return false; } return isNormalWindow() || isDialog(); } void X11Client::setFullScreen(bool set, bool user) { set = rules()->checkFullScreen(set); const bool wasFullscreen = isFullScreen(); if (wasFullscreen == set) { return; } if (user && !userCanSetFullScreen()) { return; } setShade(ShadeNone); if (wasFullscreen) { workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event } else { geom_fs_restore = frameGeometry(); } if (set) { m_fullscreenMode = FullScreenNormal; workspace()->raiseClient(this); } else { m_fullscreenMode = FullScreenNone; } StackingUpdatesBlocker blocker1(workspace()); GeometryUpdatesBlocker blocker2(this); // active fullscreens get different layer workspace()->updateClientLayer(this); info->setState(isFullScreen() ? NET::FullScreen : NET::States(), NET::FullScreen); updateDecoration(false, false); if (set) { if (info->fullscreenMonitors().isSet()) { setFrameGeometry(fullscreenMonitorsArea(info->fullscreenMonitors())); } else { setFrameGeometry(workspace()->clientArea(FullScreenArea, this)); } } else { Q_ASSERT(!geom_fs_restore.isNull()); const int currentScreen = screen(); setFrameGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size()))); if(currentScreen != screen()) { workspace()->sendClientToScreen(this, currentScreen); } } updateWindowRules(Rules::Fullscreen | Rules::Position | Rules::Size); emit clientFullScreenSet(this, set, user); emit fullScreenChanged(); } void X11Client::updateFullscreenMonitors(NETFullscreenMonitors topology) { int nscreens = screens()->count(); // qDebug() << "incoming request with top: " << topology.top << " bottom: " << topology.bottom // << " left: " << topology.left << " right: " << topology.right // << ", we have: " << nscreens << " screens."; if (topology.top >= nscreens || topology.bottom >= nscreens || topology.left >= nscreens || topology.right >= nscreens) { qCWarning(KWIN_CORE) << "fullscreenMonitors update failed. request higher than number of screens."; return; } info->setFullscreenMonitors(topology); if (isFullScreen()) setFrameGeometry(fullscreenMonitorsArea(topology)); } /** * Calculates the bounding rectangle defined by the 4 monitor indices indicating the * top, bottom, left, and right edges of the window when the fullscreen state is enabled. */ QRect X11Client::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const { QRect top, bottom, left, right, total; top = screens()->geometry(requestedTopology.top); bottom = screens()->geometry(requestedTopology.bottom); left = screens()->geometry(requestedTopology.left); right = screens()->geometry(requestedTopology.right); total = top.united(bottom.united(left.united(right))); // qDebug() << "top: " << top << " bottom: " << bottom // << " left: " << left << " right: " << right; // qDebug() << "returning rect: " << total; return total; } static GeometryTip* geometryTip = nullptr; void X11Client::positionGeometryTip() { Q_ASSERT(isMove() || isResize()); // Position and Size display if (effects && static_cast(effects)->provides(Effect::GeometryTip)) return; // some effect paints this for us if (options->showGeometryTip()) { if (!geometryTip) { geometryTip = new GeometryTip(&m_geometryHints); } QRect wgeom(moveResizeGeometry()); // position of the frame, size of the window itself wgeom.setWidth(wgeom.width() - (width() - clientSize().width())); wgeom.setHeight(wgeom.height() - (height() - clientSize().height())); if (isShade()) wgeom.setHeight(0); geometryTip->setGeometry(wgeom); if (!geometryTip->isVisible()) geometryTip->show(); geometryTip->raise(); } } bool X11Client::doStartMoveResize() { bool has_grab = false; // This reportedly improves smoothness of the moveresize operation, // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug* // (https://lists.kde.org/?t=107302193400001&r=1&w=2) QRect r = workspace()->clientArea(FullArea, this); m_moveResizeGrabWindow.create(r, XCB_WINDOW_CLASS_INPUT_ONLY, 0, nullptr, rootWindow()); m_moveResizeGrabWindow.map(); m_moveResizeGrabWindow.raise(); updateXTime(); const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(connection(), false, m_moveResizeGrabWindow, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursor::x11Cursor(cursor()), xTime()); ScopedCPointer pointerGrab(xcb_grab_pointer_reply(connection(), cookie, nullptr)); if (!pointerGrab.isNull() && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) { has_grab = true; } if (!has_grab && grabXKeyboard(frameId())) has_grab = move_resize_has_keyboard_grab = true; if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize m_moveResizeGrabWindow.reset(); return false; } return true; } void X11Client::leaveMoveResize() { if (needsXWindowMove) { // Do the deferred move m_frame.move(m_bufferGeometry.topLeft()); needsXWindowMove = false; } if (!isResize()) sendSyntheticConfigureNotify(); // tell the client about it's new final position if (geometryTip) { geometryTip->hide(); delete geometryTip; geometryTip = nullptr; } if (move_resize_has_keyboard_grab) ungrabXKeyboard(); move_resize_has_keyboard_grab = false; xcb_ungrab_pointer(connection(), xTime()); m_moveResizeGrabWindow.reset(); if (m_syncRequest.counter == XCB_NONE) { // don't forget to sanitize since the timeout will no more fire m_syncRequest.isPending = false; } delete m_syncRequest.timeout; m_syncRequest.timeout = nullptr; AbstractClient::leaveMoveResize(); } bool X11Client::isWaitingForMoveResizeSync() const { return m_syncRequest.isPending && isResize(); } void X11Client::doResizeSync() { if (!m_syncRequest.timeout) { m_syncRequest.timeout = new QTimer(this); connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Client::performMoveResize); m_syncRequest.timeout->setSingleShot(true); } if (m_syncRequest.counter != XCB_NONE) { m_syncRequest.timeout->start(250); sendSyncRequest(); } else { // for clients not supporting the XSYNC protocol, we m_syncRequest.isPending = true; // limit the resizes to 30Hz to take pointless load from X11 m_syncRequest.timeout->start(33); // and the client, the mouse is still moved at full speed } // and no human can control faster resizes anyway const QRect moveResizeClientGeometry = frameRectToClientRect(moveResizeGeometry()); const QRect moveResizeBufferGeometry = frameRectToBufferRect(moveResizeGeometry()); // According to the Composite extension spec, a window will get a new pixmap allocated each time // it is mapped or resized. Given that we redirect frame windows and not client windows, we have // to resize the frame window in order to forcefully reallocate offscreen storage. If we don't do // this, then we might render partially updated client window. I know, it sucks. m_frame.setGeometry(moveResizeBufferGeometry); m_wrapper.setGeometry(QRect(clientPos(), moveResizeClientGeometry.size())); m_client.resize(moveResizeClientGeometry.size()); } void X11Client::doPerformMoveResize() { if (m_syncRequest.counter == XCB_NONE) { // client w/o XSYNC support. allow the next resize event m_syncRequest.isPending = false; // NEVER do this for clients with a valid counter } // (leads to sync request races in some clients) } /** * Returns \a area with the client's strut taken into account. * * Used from Workspace in updateClientArea. */ // TODO move to Workspace? QRect X11Client::adjustedClientArea(const QRect &desktopArea, const QRect& area) const { QRect r = area; NETExtendedStrut str = strut(); QRect stareaL = QRect( 0, str . left_start, str . left_width, str . left_end - str . left_start + 1); QRect stareaR = QRect( desktopArea . right() - str . right_width + 1, str . right_start, str . right_width, str . right_end - str . right_start + 1); QRect stareaT = QRect( str . top_start, 0, str . top_end - str . top_start + 1, str . top_width); QRect stareaB = QRect( str . bottom_start, desktopArea . bottom() - str . bottom_width + 1, str . bottom_end - str . bottom_start + 1, str . bottom_width); QRect screenarea = workspace()->clientArea(ScreenArea, this); // HACK: workarea handling is not xinerama aware, so if this strut // reserves place at a xinerama edge that's inside the virtual screen, // ignore the strut for workspace setting. if (area == QRect(QPoint(0, 0), screens()->displaySize())) { if (stareaL.left() < screenarea.left()) stareaL = QRect(); if (stareaR.right() > screenarea.right()) stareaR = QRect(); if (stareaT.top() < screenarea.top()) stareaT = QRect(); if (stareaB.bottom() < screenarea.bottom()) stareaB = QRect(); } // Handle struts at xinerama edges that are inside the virtual screen. // They're given in virtual screen coordinates, make them affect only // their xinerama screen. stareaL.setLeft(qMax(stareaL.left(), screenarea.left())); stareaR.setRight(qMin(stareaR.right(), screenarea.right())); stareaT.setTop(qMax(stareaT.top(), screenarea.top())); stareaB.setBottom(qMin(stareaB.bottom(), screenarea.bottom())); if (stareaL . intersects(area)) { // qDebug() << "Moving left of: " << r << " to " << stareaL.right() + 1; r . setLeft(stareaL . right() + 1); } if (stareaR . intersects(area)) { // qDebug() << "Moving right of: " << r << " to " << stareaR.left() - 1; r . setRight(stareaR . left() - 1); } if (stareaT . intersects(area)) { // qDebug() << "Moving top of: " << r << " to " << stareaT.bottom() + 1; r . setTop(stareaT . bottom() + 1); } if (stareaB . intersects(area)) { // qDebug() << "Moving bottom of: " << r << " to " << stareaB.top() - 1; r . setBottom(stareaB . top() - 1); } return r; } NETExtendedStrut X11Client::strut() const { NETExtendedStrut ext = info->extendedStrut(); NETStrut str = info->strut(); const QSize displaySize = screens()->displaySize(); 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 = displaySize.height(); } if (str.right != 0) { ext.right_width = str.right; ext.right_start = 0; ext.right_end = displaySize.height(); } if (str.top != 0) { ext.top_width = str.top; ext.top_start = 0; ext.top_end = displaySize.width(); } if (str.bottom != 0) { ext.bottom_width = str.bottom; ext.bottom_start = 0; ext.bottom_end = displaySize.width(); } } return ext; } StrutRect X11Client::strutRect(StrutArea area) const { Q_ASSERT(area != StrutAreaAll); // Not valid const QSize displaySize = screens()->displaySize(); NETExtendedStrut strutArea = strut(); switch(area) { case StrutAreaTop: if (strutArea.top_width != 0) return StrutRect(QRect( strutArea.top_start, 0, strutArea.top_end - strutArea.top_start, strutArea.top_width ), StrutAreaTop); break; case StrutAreaRight: if (strutArea.right_width != 0) return StrutRect(QRect( displaySize.width() - strutArea.right_width, strutArea.right_start, strutArea.right_width, strutArea.right_end - strutArea.right_start ), StrutAreaRight); break; case StrutAreaBottom: if (strutArea.bottom_width != 0) return StrutRect(QRect( strutArea.bottom_start, displaySize.height() - strutArea.bottom_width, strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width ), StrutAreaBottom); break; case StrutAreaLeft: if (strutArea.left_width != 0) return StrutRect(QRect( 0, strutArea.left_start, strutArea.left_width, strutArea.left_end - strutArea.left_start ), StrutAreaLeft); break; default: abort(); // Not valid } return StrutRect(); // Null rect } StrutRects X11Client::strutRects() const { StrutRects region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); return region; } bool X11Client::hasStrut() const { NETExtendedStrut ext = strut(); if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0) return false; return true; } bool X11Client::hasOffscreenXineramaStrut() const { // Get strut as a QRegion QRegion region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); // Remove all visible areas so that only the invisible remain for (int i = 0; i < screens()->count(); i ++) region -= screens()->geometry(i); // If there's anything left then we have an offscreen strut return !region.isEmpty(); } void X11Client::applyWindowRules() { AbstractClient::applyWindowRules(); setBlockingCompositing(info->isBlockingCompositing()); } void X11Client::damageNotifyEvent() { if (m_syncRequest.isPending && isResize()) { emit damaged(this, QRect()); m_isDamaged = true; return; } if (!readyForPainting()) { // avoid "setReadyForPainting()" function calling overhead if (m_syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } AbstractClient::damageNotifyEvent(); } void X11Client::updateWindowPixmap() { if (effectWindow() && effectWindow()->sceneWindow()) { effectWindow()->sceneWindow()->updatePixmap(); } } } // namespace diff --git a/x11client.h b/x11client.h index 14d4f438a..4bed4075f 100644 --- a/x11client.h +++ b/x11client.h @@ -1,689 +1,692 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #pragma once // kwin #include "options.h" #include "rules.h" #include "abstract_client.h" #include "xcbutils.h" // Qt #include #include #include #include #include // X #include // TODO: Cleanup the order of things in this .h file class QTimer; class KStartupInfoData; class KStartupInfoId; namespace KWin { /** * @brief Defines Predicates on how to search for a Client. * * Used by Workspace::findClient. */ enum class Predicate { WindowMatch, WrapperIdMatch, FrameIdMatch, InputIdMatch }; class KWIN_EXPORT X11Client : public AbstractClient { Q_OBJECT /** * By how much the window wishes to grow/shrink at least. Usually QSize(1,1). * MAY BE DISOBEYED BY THE WM! It's only for information, do NOT rely on it at all. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(QSize basicUnit READ basicUnit) /** * A client can block compositing. That is while the Client is alive and the state is set, * Compositing is suspended and is resumed when there are no Clients blocking compositing any * more. * * This is actually set by a window property, unfortunately not used by the target application * group. For convenience it's exported as a property to the scripts. * * Use with care! */ Q_PROPERTY(bool blocksCompositing READ isBlockingCompositing WRITE setBlockingCompositing NOTIFY blockingCompositingChanged) /** * Whether the Client uses client side window decorations. * Only GTK+ are detected. */ Q_PROPERTY(bool clientSideDecorated READ isClientSideDecorated NOTIFY clientSideDecoratedChanged) public: explicit X11Client(); xcb_window_t wrapperId() const; xcb_window_t inputId() const { return m_decoInputExtent; } xcb_window_t frameId() const override; + QRect inputGeometry() const override; QRect bufferGeometry() const override; QMargins bufferMargins() const override; QPoint framePosToClientPos(const QPoint &point) const override; QPoint clientPosToFramePos(const QPoint &point) const override; QSize frameSizeToClientSize(const QSize &size) const override; QSize clientSizeToFrameSize(const QSize &size) const override; QRect frameRectToBufferRect(const QRect &rect) const; + QMatrix4x4 inputTransformation() const override; + bool isTransient() const override; bool groupTransient() const override; bool wasOriginallyGroupTransient() const; QList mainClients() const override; // Call once before loop , is not indirect bool hasTransient(const AbstractClient* c, bool indirect) const override; void checkTransient(xcb_window_t w); AbstractClient* findModal(bool allow_itself = false) override; const Group* group() const override; Group* group() override; void checkGroup(Group* gr = nullptr, bool force = false); void changeClientLeaderGroup(Group* gr); void updateWindowRules(Rules::Types selection) override; void applyWindowRules() override; void updateFullscreenMonitors(NETFullscreenMonitors topology); bool hasNETSupport() const; QSize minSize() const override; QSize maxSize() const override; QSize basicUnit() const; QSize clientSize() const override; QPoint inputPos() const { return input_offset; } // Inside of geometry() bool windowEvent(xcb_generic_event_t *e); NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; bool manage(xcb_window_t w, bool isMapped); void releaseWindow(bool on_shutdown = false); void destroyClient(); QStringList activities() const override; void setOnActivity(const QString &activity, bool enable); void setOnAllActivities(bool set) override; void setOnActivities(QStringList newActivitiesList) override; void updateActivities(bool includeTransients); void blockActivityUpdates(bool b = true) override; /// Is not minimized and not hidden. I.e. normally visible on some virtual desktop. bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override; // For compositing ShadeMode shadeMode() const override; // Prefer isShade() void setShade(ShadeMode mode) override; bool isShadeable() const override; bool isMaximizable() const override; MaximizeMode maximizeMode() const override; bool isMinimizable() const override; QRect iconGeometry() const override; bool isFullScreenable() const override; void setFullScreen(bool set, bool user = true) override; bool isFullScreen() const override; bool userCanSetFullScreen() const override; QRect geometryFSRestore() const { return geom_fs_restore; // only for session saving } int fullScreenMode() const { return m_fullscreenMode; // only for session saving } bool userNoBorder() const; bool noBorder() const override; void setNoBorder(bool set) override; bool userCanSetNoBorder() const override; void checkNoBorder() override; int sessionStackingOrder() const; // Auxiliary functions, depend on the windowType bool wantsInput() const override; bool isResizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isCloseable() const override; ///< May be closed by the user (May have a close button) void takeFocus() override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void updateShape(); using AbstractClient::move; void move(int x, int y, ForceGeometry_t force = NormalGeometrySet) override; using AbstractClient::setFrameGeometry; void setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; /// plainResize() simply resizes void plainResize(int w, int h, ForceGeometry_t force = NormalGeometrySet); void plainResize(const QSize& s, ForceGeometry_t force = NormalGeometrySet); /// resizeWithChecks() resizes according to gravity, and checks workarea position using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; void resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); void resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); QSize sizeForClientSize(const QSize&, SizeMode mode = SizeModeAny, bool noframe = false) const override; bool providesContextHelp() const override; Options::WindowOperation mouseButtonToWindowOperation(Qt::MouseButtons button); bool performMouseCommand(Options::MouseCommand, const QPoint& globalPos) override; QRect adjustedClientArea(const QRect& desktop, const QRect& area) const; xcb_colormap_t colormap() const; /// Updates visibility depending on being shaded, virtual desktop, etc. void updateVisibility(); /// Hides a client - Basically like minimize, but without effects, it's simply hidden void hideClient(bool hide) override; bool hiddenPreview() const; ///< Window is mapped in order to get a window pixmap bool setupCompositing() override; void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; void setBlockingCompositing(bool block); inline bool isBlockingCompositing() { return blocks_compositing; } QString captionNormal() const override { return cap_normal; } QString captionSuffix() const override { return cap_suffix; } using AbstractClient::keyPressEvent; void keyPressEvent(uint key_code, xcb_timestamp_t time); // FRAME ?? void updateMouseGrab() override; xcb_window_t moveResizeGrabWindow() const; QPoint gravityAdjustment(xcb_gravity_t gravity) const; const QPoint calculateGravitation(bool invert) const; void NETMoveResize(int x_root, int y_root, NET::Direction direction); void NETMoveResizeWindow(int flags, int x, int y, int width, int height); void restackWindow(xcb_window_t above, int detail, NET::RequestSource source, xcb_timestamp_t timestamp, bool send_event = false); void gotPing(xcb_timestamp_t timestamp); void updateUserTime(xcb_timestamp_t time = XCB_TIME_CURRENT_TIME); xcb_timestamp_t userTime() const override; bool hasUserTimeSupport() const; /// Does 'delete c;' static void deleteClient(X11Client *c); static bool belongToSameApplication(const X11Client *c1, const X11Client *c2, SameApplicationChecks checks = SameApplicationChecks()); static bool sameAppWindowRoleMatch(const X11Client *c1, const X11Client *c2, bool active_hack); void killWindow() override; void toggleShade(); void showContextHelp() override; void cancelShadeHoverTimer(); void checkActiveModal(); StrutRect strutRect(StrutArea area) const; StrutRects strutRects() const; bool hasStrut() const override; /** * If shown is true the client is mapped and raised, if false * the client is unmapped and hidden, this function is called * when the tabbing group of the client switches its visible * client. */ void setClientShown(bool shown) override; /** * Whether or not the window has a strut that expands through the invisible area of * an xinerama setup where the monitors are not the same resolution. */ bool hasOffscreenXineramaStrut() const; QRect transparentRect() const override; bool isClientSideDecorated() const; bool wantsShadowToBeRendered() const override; void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const override; Xcb::Property fetchFirstInTabBox() const; void readFirstInTabBox(Xcb::Property &property); void updateFirstInTabBox(); Xcb::StringProperty fetchColorScheme() const; void readColorScheme(Xcb::StringProperty &property); void updateColorScheme() override; //sets whether the client should be faked as being on all activities (and be shown during session save) void setSessionActivityOverride(bool needed); bool isClient() const override; template void print(T &stream) const; void cancelFocusOutTimer(); /** * Restores the Client after it had been hidden due to show on screen edge functionality. * In addition the property gets deleted so that the Client knows that it is visible again. */ void showOnScreenEdge() override; Xcb::StringProperty fetchApplicationMenuServiceName() const; void readApplicationMenuServiceName(Xcb::StringProperty &property); void checkApplicationMenuServiceName(); Xcb::StringProperty fetchApplicationMenuObjectPath() const; void readApplicationMenuObjectPath(Xcb::StringProperty &property); void checkApplicationMenuObjectPath(); struct SyncRequest { xcb_sync_counter_t counter; xcb_sync_int64_t value; xcb_sync_alarm_t alarm; xcb_timestamp_t lastTimestamp; QTimer *timeout, *failsafeTimeout; bool isPending; }; const SyncRequest &syncRequest() const { return m_syncRequest; } void handleSync(); static void cleanupX11(); public Q_SLOTS: void closeWindow() override; void updateCaption() override; private Q_SLOTS: void shadeHover(); void shadeUnhover(); private: // Use Workspace::createClient() ~X11Client() override; ///< Use destroyClient() or releaseWindow() // Handlers for X11 events bool mapRequestEvent(xcb_map_request_event_t *e); void unmapNotifyEvent(xcb_unmap_notify_event_t *e); void destroyNotifyEvent(xcb_destroy_notify_event_t *e); void configureRequestEvent(xcb_configure_request_event_t *e); void propertyNotifyEvent(xcb_property_notify_event_t *e) override; void clientMessageEvent(xcb_client_message_event_t *e) override; void enterNotifyEvent(xcb_enter_notify_event_t *e); void leaveNotifyEvent(xcb_leave_notify_event_t *e); void focusInEvent(xcb_focus_in_event_t *e); void focusOutEvent(xcb_focus_out_event_t *e); void damageNotifyEvent() override; bool buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time = XCB_CURRENT_TIME); bool buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root); bool motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root); protected: void debug(QDebug& stream) const override; void addDamage(const QRegion &damage) override; bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const override; void doSetActive() override; void doSetKeepAbove() override; void doSetKeepBelow() override; void doSetDesktop() override; void doMinimize() override; void doSetSkipPager() override; void doSetSkipTaskbar() override; void doSetSkipSwitcher() override; void doSetDemandsAttention() override; bool belongsToDesktop() const override; bool doStartMoveResize() override; void doPerformMoveResize() override; bool isWaitingForMoveResizeSync() const override; void doResizeSync() override; QSize resizeIncrements() const override; bool acceptsFocus() const override; //Signals for the scripting interface //Signals make an excellent way for communication //in between objects as compared to simple function //calls Q_SIGNALS: void clientManaging(KWin::X11Client *); void clientFullScreenSet(KWin::X11Client *, bool, bool); /** * Emitted whenever the Client want to show it menu */ void showRequest(); /** * Emitted whenever the Client's menu is closed */ void menuHidden(); /** * Emitted whenever the Client's menu is available */ void appMenuAvailable(); /** * Emitted whenever the Client's menu is unavailable */ void appMenuUnavailable(); /** * Emitted whenever the Client's block compositing state changes. */ void blockingCompositingChanged(KWin::X11Client *client); void clientSideDecoratedChanged(); private: void exportMappingState(int s); // ICCCM 4.1.3.1, 4.1.4, NETWM 2.5.1 bool isManaged() const; ///< Returns false if this client is not yet managed void updateAllowedActions(bool force = false); QRect fullscreenMonitorsArea(NETFullscreenMonitors topology) const; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; void getWmNormalHints(); void getMotifHints(); void getIcons(); void fetchName(); void fetchIconicName(); QString readName() const; void setCaption(const QString& s, bool force = false); bool hasTransientInternal(const X11Client *c, bool indirect, QList &set) const; void setShortcutInternal() override; void configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool); NETExtendedStrut strut() const; int checkShadeGeometry(int w, int h); void getSyncCounter(); void sendSyncRequest(); void leaveMoveResize() override; void positionGeometryTip() override; void grabButton(int mod); void ungrabButton(int mod); void resizeDecoration(); void createDecoration(const QRect &oldgeom); void pingWindow(); void killProcess(bool ask, xcb_timestamp_t timestamp = XCB_TIME_CURRENT_TIME); void updateUrgency(); static void sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1 = 0, uint32_t data2 = 0, uint32_t data3 = 0, xcb_timestamp_t timestamp = xTime()); void embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth); void detectNoBorder(); void destroyDecoration() override; void updateFrameExtents(); void setClientFrameExtents(const NETStrut &strut); void internalShow(); void internalHide(); void internalKeep(); void map(); void unmap(); void updateHiddenPreview(); void updateInputShape(); void updateServerGeometry(); void updateWindowPixmap(); xcb_timestamp_t readUserTimeMapTimestamp(const KStartupInfoId* asn_id, const KStartupInfoData* asn_data, bool session) const; xcb_timestamp_t readUserCreationTime() const; void startupIdChanged(); void updateInputWindow(); Xcb::Property fetchShowOnScreenEdge() const; void readShowOnScreenEdge(Xcb::Property &property); /** * Reads the property and creates/destroys the screen edge if required * and shows/hides the client. */ void updateShowOnScreenEdge(); Xcb::Window m_client; Xcb::Window m_wrapper; Xcb::Window m_frame; QStringList activityList; int m_activityUpdatesBlocked; bool m_blockedActivityUpdatesRequireTransients; Xcb::Window m_moveResizeGrabWindow; bool move_resize_has_keyboard_grab; bool m_managed; Xcb::GeometryHints m_geometryHints; void sendSyntheticConfigureNotify(); enum MappingState { Withdrawn, ///< Not handled, as per ICCCM WithdrawnState Mapped, ///< The frame is mapped Unmapped, ///< The frame is not mapped Kept ///< The frame should be unmapped, but is kept (For compositing) }; MappingState mapping_state; Xcb::TransientFor fetchTransient() const; void readTransientProperty(Xcb::TransientFor &transientFor); void readTransient(); xcb_window_t verifyTransientFor(xcb_window_t transient_for, bool set); void addTransient(AbstractClient* cl) override; void removeTransient(AbstractClient* cl) override; void removeFromMainClients(); void cleanGrouping(); void checkGroupTransients(); void setTransient(xcb_window_t new_transient_for_id); xcb_window_t m_transientForId; xcb_window_t m_originalTransientForId; ShadeMode shade_mode; X11Client *shade_below; uint deleting : 1; ///< True when doing cleanup and destroying the client Xcb::MotifHints m_motif; uint hidden : 1; ///< Forcibly hidden by calling hide() uint noborder : 1; uint app_noborder : 1; ///< App requested no border via window type, shape extension, etc. uint ignore_focus_stealing : 1; ///< Don't apply focus stealing prevention to this client bool blocks_compositing; enum FullScreenMode { FullScreenNone, FullScreenNormal } m_fullscreenMode; MaximizeMode max_mode; QRect m_bufferGeometry = QRect(0, 0, 100, 100); QRect m_clientGeometry = QRect(0, 0, 100, 100); QRect geom_fs_restore; QTimer* shadeHoverTimer; xcb_colormap_t m_colormap; QString cap_normal, cap_iconic, cap_suffix; Group* in_group; QTimer* ping_timer; qint64 m_killHelperPID; xcb_timestamp_t m_pingTimestamp; xcb_timestamp_t m_userTime; NET::Actions allowed_actions; bool shade_geometry_change; SyncRequest m_syncRequest; static bool check_active_modal; ///< \see X11Client::checkActiveModal() int sm_stacking_order; friend struct ResetupRulesProcedure; friend bool performTransiencyCheck(); Xcb::StringProperty fetchActivities() const; void readActivities(Xcb::StringProperty &property); void checkActivities(); bool activitiesDefined; //whether the x property was actually set bool sessionActivityOverride; bool needsXWindowMove; Xcb::Window m_decoInputExtent; QPoint input_offset; QTimer *m_focusOutTimer; QList m_connections; QMetaObject::Connection m_edgeRemoveConnection; QMetaObject::Connection m_edgeGeometryTrackingConnection; QMargins m_clientFrameExtents; }; inline xcb_window_t X11Client::wrapperId() const { return m_wrapper; } inline bool X11Client::isClientSideDecorated() const { return !m_clientFrameExtents.isNull(); } inline bool X11Client::groupTransient() const { return m_transientForId == rootWindow(); } // Needed because verifyTransientFor() may set transient_for_id to root window, // if the original value has a problem (window doesn't exist, etc.) inline bool X11Client::wasOriginallyGroupTransient() const { return m_originalTransientForId == rootWindow(); } inline bool X11Client::isTransient() const { return m_transientForId != XCB_WINDOW_NONE; } inline const Group* X11Client::group() const { return in_group; } inline Group* X11Client::group() { return in_group; } inline bool X11Client::isShown(bool shaded_is_shown) const { return !isMinimized() && (!isShade() || shaded_is_shown) && !hidden; } inline bool X11Client::isHiddenInternal() const { return hidden; } inline ShadeMode X11Client::shadeMode() const { return shade_mode; } inline MaximizeMode X11Client::maximizeMode() const { return max_mode; } inline bool X11Client::isFullScreen() const { return m_fullscreenMode != FullScreenNone; } inline bool X11Client::hasNETSupport() const { return info->hasNETSupport(); } inline xcb_colormap_t X11Client::colormap() const { return m_colormap; } inline int X11Client::sessionStackingOrder() const { return sm_stacking_order; } inline bool X11Client::isManaged() const { return m_managed; } inline QSize X11Client::clientSize() const { return m_clientGeometry.size(); } inline void X11Client::plainResize(const QSize& s, ForceGeometry_t force) { plainResize(s.width(), s.height(), force); } inline void X11Client::resizeWithChecks(int w, int h, AbstractClient::ForceGeometry_t force) { resizeWithChecks(w, h, XCB_GRAVITY_BIT_FORGET, force); } inline void X11Client::resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force) { resizeWithChecks(s.width(), s.height(), gravity, force); } inline bool X11Client::hasUserTimeSupport() const { return info->userTime() != -1U; } inline xcb_window_t X11Client::moveResizeGrabWindow() const { return m_moveResizeGrabWindow; } inline bool X11Client::hiddenPreview() const { return mapping_state == Kept; } template inline void X11Client::print(T &stream) const { stream << "\'Client:" << window() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; } } // namespace Q_DECLARE_METATYPE(KWin::X11Client *) Q_DECLARE_METATYPE(QList)