diff --git a/abstract_client.cpp b/abstract_client.cpp index 91538e27f9..8eb6476012 100644 --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -1627,4 +1627,12 @@ QRect AbstractClient::iconGeometry() const return candidateGeom.translated(candidatePanel->pos()); } +QRect AbstractClient::inputGeometry() const +{ + if (isDecorated()) { + return Toplevel::inputGeometry() + decoration()->resizeOnlyBorders(); + } + return Toplevel::inputGeometry(); +} + } diff --git a/abstract_client.h b/abstract_client.h index d77de4d8d5..142743dcf9 100644 --- a/abstract_client.h +++ b/abstract_client.h @@ -578,6 +578,8 @@ public: */ virtual void showContextHelp(); + QRect inputGeometry() const override; + // TODO: remove boolean trap static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, bool active_hack = false); diff --git a/autotests/integration/debug_console_test.cpp b/autotests/integration/debug_console_test.cpp index f75ba9f3bd..a21d9170a8 100644 --- a/autotests/integration/debug_console_test.cpp +++ b/autotests/integration/debug_console_test.cpp @@ -52,6 +52,7 @@ private Q_SLOTS: void testWaylandClient_data(); void testWaylandClient(); void testInternalWindow(); + void testClosingDebugConsole(); }; void DebugConsoleTest::initTestCase() @@ -502,6 +503,28 @@ void DebugConsoleTest::testInternalWindow() QCOMPARE(rowsRemovedSpy.first().first().value(), internalTopLevelIndex); } +void DebugConsoleTest::testClosingDebugConsole() +{ + // this test verifies that the DebugConsole gets destroyed when closing the window + // BUG: 369858 + + DebugConsole *console = new DebugConsole; + QSignalSpy destroyedSpy(console, &QObject::destroyed); + QVERIFY(destroyedSpy.isValid()); + + QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QVERIFY(clientAddedSpy.isValid()); + console->show(); + QCOMPARE(console->windowHandle()->isVisible(), true); + QTRY_COMPARE(clientAddedSpy.count(), 1); + ShellClient *c = clientAddedSpy.first().first().value(); + QVERIFY(c->isInternal()); + QCOMPARE(c->internalWindow(), console->windowHandle()); + QVERIFY(c->isDecorated()); + c->closeWindow(); + QVERIFY(destroyedSpy.wait()); +} + } WAYLANDTEST_MAIN(KWin::DebugConsoleTest) diff --git a/autotests/integration/decoration_input_test.cpp b/autotests/integration/decoration_input_test.cpp index b9cde01e19..72c205235b 100644 --- a/autotests/integration/decoration_input_test.cpp +++ b/autotests/integration/decoration_input_test.cpp @@ -70,6 +70,8 @@ private Q_SLOTS: void testPressToMove(); void testTapToMove_data(); void testTapToMove(); + void testResizeOutsideWindow_data(); + void testResizeOutsideWindow(); private: AbstractClient *showWindow(Test::ShellSurfaceType type); @@ -498,6 +500,69 @@ void DecorationInputTest::testTapToMove() QCOMPARE(c->pos(), oldPos + offset2 + offset3); } +void DecorationInputTest::testResizeOutsideWindow_data() +{ + QTest::addColumn("type"); + QTest::addColumn("edge"); + QTest::addColumn("expectedCursor"); + + QTest::newRow("wlShell - left") << Test::ShellSurfaceType::WlShell << Qt::LeftEdge << Qt::SizeHorCursor; + QTest::newRow("xdgShellV5 - left") << Test::ShellSurfaceType::XdgShellV5 << Qt::LeftEdge << Qt::SizeHorCursor; + QTest::newRow("wlShell - right") << Test::ShellSurfaceType::WlShell << Qt::RightEdge << Qt::SizeHorCursor; + QTest::newRow("xdgShellV5 - right") << Test::ShellSurfaceType::XdgShellV5 << Qt::RightEdge << Qt::SizeHorCursor; + QTest::newRow("wlShell - bottom") << Test::ShellSurfaceType::WlShell << Qt::BottomEdge << Qt::SizeVerCursor; + QTest::newRow("xdgShellV5 - bottom") << Test::ShellSurfaceType::XdgShellV5 << Qt::BottomEdge << Qt::SizeVerCursor; +} + +void DecorationInputTest::testResizeOutsideWindow() +{ + // this test verifies that one can resize the window outside the decoration with NoSideBorder + + // first adjust config + kwinApp()->config()->group("org.kde.kdecoration2").writeEntry("BorderSize", QStringLiteral("None")); + kwinApp()->config()->sync(); + workspace()->slotReconfigure(); + + // now create window + QFETCH(Test::ShellSurfaceType, type); + AbstractClient *c = showWindow(type); + QVERIFY(c); + QVERIFY(c->isDecorated()); + QVERIFY(!c->noBorder()); + c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); + QVERIFY(c->geometry() != c->inputGeometry()); + QVERIFY(c->inputGeometry().contains(c->geometry())); + QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); + QVERIFY(startMoveResizedSpy.isValid()); + + // go to border + quint32 timestamp = 1; + QFETCH(Qt::Edge, edge); + switch (edge) { + case Qt::LeftEdge: + MOTION(QPoint(c->geometry().x() -1, c->geometry().center().y())); + break; + case Qt::RightEdge: + MOTION(QPoint(c->geometry().x() + c->geometry().width() +1, c->geometry().center().y())); + break; + case Qt::BottomEdge: + MOTION(QPoint(c->geometry().center().x(), c->geometry().y() + c->geometry().height() + 1)); + break; + default: + break; + } + QVERIFY(!c->geometry().contains(KWin::Cursor::pos())); + + // pressing should trigger resize + PRESS; + QVERIFY(!c->isResize()); + QVERIFY(startMoveResizedSpy.wait()); + QVERIFY(c->isResize()); + + RELEASE; + QVERIFY(!c->isResize()); +} + } WAYLANDTEST_MAIN(KWin::DecorationInputTest) diff --git a/autotests/integration/globalshortcuts_test.cpp b/autotests/integration/globalshortcuts_test.cpp index ab46fbd520..41b8d27251 100644 --- a/autotests/integration/globalshortcuts_test.cpp +++ b/autotests/integration/globalshortcuts_test.cpp @@ -26,6 +26,8 @@ along with this program. If not, see . #include "wayland_server.h" #include "workspace.h" +#include + #include #include @@ -43,6 +45,7 @@ private Q_SLOTS: void cleanup(); void testConsumedShift(); + void testRepeatedTrigger(); }; void GlobalShortcutsTest::initTestCase() @@ -96,5 +99,43 @@ void GlobalShortcutsTest::testConsumedShift() kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); } +void GlobalShortcutsTest::testRepeatedTrigger() +{ + // this test verifies that holding a key, triggers repeated global shortcut + // in addition pressing another key should stop triggering the shortcut + + QScopedPointer action(new QAction(nullptr)); + action->setProperty("componentName", QStringLiteral(KWIN_NAME)); + action->setObjectName(QStringLiteral("globalshortcuts-test-consumed-shift")); + QSignalSpy triggeredSpy(action.data(), &QAction::triggered); + QVERIFY(triggeredSpy.isValid()); + KGlobalAccel::self()->setShortcut(action.data(), QList{Qt::Key_Percent}, KGlobalAccel::NoAutoloading); + input()->registerShortcut(Qt::Key_Percent, action.data()); + + // we need to configure the key repeat first. It is only enabled on libinput + waylandServer()->seat()->setKeyRepeatInfo(25, 300); + + // press shift+5 + quint32 timestamp = 0; + kwinApp()->platform()->keyboardKeyPressed(KEY_WAKEUP, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); + kwinApp()->platform()->keyboardKeyPressed(KEY_5, timestamp++); + QTRY_COMPARE(triggeredSpy.count(), 1); + // and should repeat + QVERIFY(triggeredSpy.wait()); + QVERIFY(triggeredSpy.wait()); + // now release the key + kwinApp()->platform()->keyboardKeyReleased(KEY_5, timestamp++); + QEXPECT_FAIL("", "BUG 369091", Continue); + QVERIFY(!triggeredSpy.wait(500)); + + kwinApp()->platform()->keyboardKeyReleased(KEY_WAKEUP, timestamp++); + QVERIFY(!triggeredSpy.wait(500)); + + // release shift + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); +} + WAYLANDTEST_MAIN(GlobalShortcutsTest) #include "globalshortcuts_test.moc" diff --git a/debug_console.cpp b/debug_console.cpp index 813c9fe806..db18569fea 100644 --- a/debug_console.cpp +++ b/debug_console.cpp @@ -536,6 +536,22 @@ void DebugConsole::initGLTab() m_ui->openGLExtensionsLabel->setText(extensionsString(openGLExtensions())); } +void DebugConsole::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + + // delay the connection to the show event as in ctor the windowHandle returns null + connect(windowHandle(), &QWindow::visibleChanged, this, + [this] (bool visible) { + if (visible) { + // ignore + return; + } + deleteLater(); + } + ); +} + DebugConsoleDelegate::DebugConsoleDelegate(QObject *parent) : QStyledItemDelegate(parent) { diff --git a/debug_console.h b/debug_console.h index f67b43a748..fe80d1b0e7 100644 --- a/debug_console.h +++ b/debug_console.h @@ -94,13 +94,16 @@ public: QString displayText(const QVariant &value, const QLocale &locale) const override; }; -class DebugConsole : public QWidget +class KWIN_EXPORT DebugConsole : public QWidget { Q_OBJECT public: DebugConsole(); virtual ~DebugConsole(); +protected: + void showEvent(QShowEvent *event) override; + private: void initGLTab(); diff --git a/input.cpp b/input.cpp index 8cabc1c3a9..f9237b58e0 100644 --- a/input.cpp +++ b/input.cpp @@ -1451,7 +1451,7 @@ Toplevel *InputRedirection::findToplevel(const QPoint &pos) continue; } } - if (t->geometry().contains(pos) && acceptsInput(t, pos)) { + if (t->inputGeometry().contains(pos) && acceptsInput(t, pos)) { return t; } } while (it != stacking.begin()); diff --git a/plugins/platforms/drm/drm.json b/plugins/platforms/drm/drm.json index ba19fc4650..cd75cb94da 100644 --- a/plugins/platforms/drm/drm.json +++ b/plugins/platforms/drm/drm.json @@ -29,6 +29,7 @@ "Description[uk]": "Обробляти через вузол DRM.", "Description[x-test]": "xxRender through drm node.xx", "Description[zh_CN]": "通过 drm 结点渲染。", + "Description[zh_TW]": "透過 drm 節點成像。", "Id": "KWinWaylandDrmBackend", "Name": "drm", "Name[ca@valencia]": "DRM", diff --git a/plugins/platforms/fbdev/fbdev.json b/plugins/platforms/fbdev/fbdev.json index 258cdb2bb6..103a7cc534 100644 --- a/plugins/platforms/fbdev/fbdev.json +++ b/plugins/platforms/fbdev/fbdev.json @@ -29,6 +29,7 @@ "Description[uk]": "Обробляти до буфера кадрів.", "Description[x-test]": "xxRender to framebuffer.xx", "Description[zh_CN]": "渲染到帧缓冲。", + "Description[zh_TW]": "成像至 framebuffer。", "Id": "KWinWaylandFbdevBackend", "Name": "framebuffer", "Name[ca@valencia]": "Framebuffer", @@ -44,7 +45,8 @@ "Name[sr@latin]": "Kadrobafer", "Name[sr]": "Кадробафер", "Name[sv]": "rambuffer", - "Name[x-test]": "xxframebufferxx" + "Name[x-test]": "xxframebufferxx", + "Name[zh_TW]": "影格緩衝區(framebuffer)" }, "input": false } \ No newline at end of file diff --git a/plugins/platforms/hwcomposer/hwcomposer.json b/plugins/platforms/hwcomposer/hwcomposer.json index bed756cb42..445940533c 100644 --- a/plugins/platforms/hwcomposer/hwcomposer.json +++ b/plugins/platforms/hwcomposer/hwcomposer.json @@ -27,6 +27,7 @@ "Description[uk]": "Обробляти за допомогою апаратного засобу композиції через libhybris.", "Description[x-test]": "xxRender through hwcomposer through libhybris.xx", "Description[zh_CN]": "使用 libhybris 通过 hwcomposer 渲染。", + "Description[zh_TW]": "透過 libhybris 成像到 hwcomposer。", "Id": "KWinWaylandHwcomposerBackend", "Name": "hwcomposer", "Name[pl]": "sprzętowy kompozytor", diff --git a/plugins/platforms/virtual/virtual.json b/plugins/platforms/virtual/virtual.json index 0e4f1e352f..d55b5af49a 100644 --- a/plugins/platforms/virtual/virtual.json +++ b/plugins/platforms/virtual/virtual.json @@ -29,6 +29,7 @@ "Description[uk]": "Обробляти до віртуального буфера кадрів.", "Description[x-test]": "xxRender to a virtual framebuffer.xx", "Description[zh_CN]": "渲染到虚拟帧缓冲。", + "Description[zh_TW]": "成像到虛擬影格緩衝區。", "Id": "KWinWaylandVirtualBackend", "Name": "virtual", "Name[ca@valencia]": "Virtual", @@ -45,7 +46,8 @@ "Name[sr@latin]": "Virtuelno", "Name[sr]": "Виртуелно", "Name[sv]": "virtuell", - "Name[x-test]": "xxvirtualxx" + "Name[x-test]": "xxvirtualxx", + "Name[zh_TW]": "虛擬" }, "input": true } \ No newline at end of file diff --git a/plugins/platforms/wayland/wayland.json b/plugins/platforms/wayland/wayland.json index f90449426c..b927cceb60 100644 --- a/plugins/platforms/wayland/wayland.json +++ b/plugins/platforms/wayland/wayland.json @@ -27,6 +27,7 @@ "Description[uk]": "Обробляти у вкладене вікно запущеного засобу композиції Wayland.", "Description[x-test]": "xxRender to a nested window on running Wayland compositor.xx", "Description[zh_CN]": "渲染到 Wayland 混成器上的嵌套窗口中", + "Description[zh_TW]": "成像到執行中的 Wayland 的巢狀視窗。", "Id": "KWinWaylandWaylandBackend", "Name": "wayland", "Name[ca@valencia]": "Wayland", diff --git a/plugins/platforms/x11/common/eglonxbackend.cpp b/plugins/platforms/x11/common/eglonxbackend.cpp index 35c401bfd1..78e0ce4de0 100644 --- a/plugins/platforms/x11/common/eglonxbackend.cpp +++ b/plugins/platforms/x11/common/eglonxbackend.cpp @@ -181,8 +181,10 @@ bool EglOnXBackend::initRenderingContext() setHavePlatformBase(havePlatformBase); if (havePlatformBase) { // Make sure that the X11 platform is supported - if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_x11"))) { - qCWarning(KWIN_CORE) << "EGL_EXT_platform_base is supported, but EGL_EXT_platform_x11 is not. Cannot create EGLDisplay on X11"; + if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_x11")) && + !hasClientExtension(QByteArrayLiteral("EGL_KHR_platform_x11"))) { + qCWarning(KWIN_CORE) << "EGL_EXT_platform_base is supported, but neither EGL_EXT_platform_x11 nor EGL_KHR_platform_x11 is supported." + << "Cannot create EGLDisplay on X11"; return false; } diff --git a/plugins/platforms/x11/standalone/x11.json b/plugins/platforms/x11/standalone/x11.json index 7274b07e6a..d1c848c856 100644 --- a/plugins/platforms/x11/standalone/x11.json +++ b/plugins/platforms/x11/standalone/x11.json @@ -25,6 +25,7 @@ "Description[uk]": "Додаток платформи для окремого x11 у kwin_x11.", "Description[x-test]": "xxPlatform plugin for standalone x11 in kwin_x11.xx", "Description[zh_CN]": "kwin_x11 中的独立 x11 的平台插件", + "Description[zh_TW]": "在 kwin_x11 中供 standalone 的 x11 使用的平臺外掛程式。", "Id": "KWinX11Platform", "Name": "x11-standalone", "Name[ca@valencia]": "X11-autònoma", diff --git a/plugins/platforms/x11/windowed/x11.json b/plugins/platforms/x11/windowed/x11.json index a6b4a4899c..a26285f3cf 100644 --- a/plugins/platforms/x11/windowed/x11.json +++ b/plugins/platforms/x11/windowed/x11.json @@ -28,6 +28,7 @@ "Description[uk]": "Обробляти у вкладене вікно системи керування вікнами X11.", "Description[x-test]": "xxRender to a nested window on X11 windowing system.xx", "Description[zh_CN]": "渲染到 X11 窗口系统上的嵌套窗口中", + "Description[zh_TW]": "成像到 X11 視窗系統的巢狀視窗。", "Id": "KWinWaylandX11Backend", "Name": "x11", "Name[ca@valencia]": "X11", diff --git a/toplevel.cpp b/toplevel.cpp index 2fb5448c1f..aec48615c5 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -538,5 +538,10 @@ quint32 Toplevel::windowId() const return window(); } +QRect Toplevel::inputGeometry() const +{ + return geometry(); +} + } // namespace diff --git a/toplevel.h b/toplevel.h index 0db6145fb3..7f78944a4f 100644 --- a/toplevel.h +++ b/toplevel.h @@ -219,6 +219,13 @@ public: **/ virtual quint32 windowId() const; QRect geometry() const; + /** + * The geometry of the Toplevel which accepts input events. This might be larger + * than the actual geometry, e.g. to support resizing outside the window. + * + * Default implementation returns same as geometry. + **/ + virtual QRect inputGeometry() const; QSize size() const; QPoint pos() const; QRect rect() const;