diff --git a/CMakeLists.txt b/CMakeLists.txt index 63171670e3..d487792a47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,9 +6,9 @@ cmake_minimum_required(VERSION 3.23 FATAL_ERROR) project("The Compositor Modules" VERSION 0.1.0) -set(QT_MIN_VERSION "6.4.0") -set(KF6_MIN_VERSION "5.240.0") -set(KDE_PLASMA_VERSION "5.27") +set(QT_MIN_VERSION "6.6.0") +set(KF6_MIN_VERSION "6.0.0") +set(KDE_PLASMA_VERSION "6.0.0") set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 20) @@ -138,7 +138,7 @@ set_package_properties(KScreenLocker PROPERTIES PURPOSE "For screenlocker integration in the Wayland session" ) -find_package(Breeze 5.9.0 CONFIG) +find_package(Breeze 6.0.0 CONFIG) set_package_properties(Breeze PROPERTIES TYPE OPTIONAL PURPOSE "For setting the default window decoration plugin" diff --git a/autotests/integration/global_shortcuts.cpp b/autotests/integration/global_shortcuts.cpp index a774bb1e95..4cede6df71 100644 --- a/autotests/integration/global_shortcuts.cpp +++ b/autotests/integration/global_shortcuts.cpp @@ -257,6 +257,52 @@ TEST_CASE("global shortcuts", "[input]") QTRY_COMPARE(triggeredSpy.count(), 0); } + SECTION("keypad") + { + auto zero_action = std::make_unique(); + zero_action->setProperty("componentName", QStringLiteral("kwin")); + zero_action->setObjectName(QStringLiteral("globalshortcuts-test-keypad-0")); + + QSignalSpy zero_action_spy(zero_action.get(), &QAction::triggered); + KGlobalAccel::self()->setShortcut( + zero_action.get(), + QList{Qt::MetaModifier | Qt::KeypadModifier | Qt::Key_0}, + KGlobalAccel::NoAutoloading); + + auto insert_action = std::make_unique(); + insert_action->setProperty("componentName", QStringLiteral("kwin")); + insert_action->setObjectName(QStringLiteral("globalshortcuts-test-keypad-ins")); + + QSignalSpy insert_action_spy(insert_action.get(), &QAction::triggered); + KGlobalAccel::self()->setShortcut( + insert_action.get(), + QList{Qt::MetaModifier | Qt::KeypadModifier | Qt::Key_Insert}, + KGlobalAccel::NoAutoloading); + + // Turn on numlock + quint32 timestamp = 0; + keyboard_key_pressed(KEY_NUMLOCK, timestamp++); + keyboard_key_released(KEY_NUMLOCK, timestamp++); + + keyboard_key_pressed(KEY_LEFTMETA, timestamp++); + keyboard_key_pressed(KEY_KP0, timestamp++); + keyboard_key_released(KEY_KP0, timestamp++); + keyboard_key_released(KEY_LEFTMETA, timestamp++); + TRY_REQUIRE(zero_action_spy.size() == 1); + REQUIRE(insert_action_spy.empty()); + + // Turn off numlock + keyboard_key_pressed(KEY_NUMLOCK, timestamp++); + keyboard_key_released(KEY_NUMLOCK, timestamp++); + + keyboard_key_pressed(KEY_LEFTMETA, timestamp++); + keyboard_key_pressed(KEY_KP0, timestamp++); + keyboard_key_released(KEY_KP0, timestamp++); + keyboard_key_released(KEY_LEFTMETA, timestamp++); + TRY_REQUIRE(insert_action_spy.size() == 1); + REQUIRE(zero_action_spy.size() == 1); + } + SECTION("x11 window shortcut") { auto c = xcb_connection_create(); diff --git a/autotests/integration/lib/setup.cpp b/autotests/integration/lib/setup.cpp index 5da033b7f8..4122ee245c 100644 --- a/autotests/integration/lib/setup.cpp +++ b/autotests/integration/lib/setup.cpp @@ -65,6 +65,11 @@ setup::setup(std::string const& test_name, qunsetenv("XKB_DEFAULT_VARIANT"); qunsetenv("XKB_DEFAULT_OPTIONS"); + auto breezerc = KSharedConfig::openConfig(QStringLiteral("breezerc")); + breezerc->group(QStringLiteral("Common")) + .writeEntry(QStringLiteral("OutlineIntensity"), QStringLiteral("OutlineOff")); + breezerc->sync(); + base = std::make_unique(base::wayland::platform_arguments{ .config = base::config(KConfig::OpenFlag::SimpleConfig, ""), .socket_name = socket_name, diff --git a/autotests/integration/modifier_only_shortcut.cpp b/autotests/integration/modifier_only_shortcut.cpp index 2e4aaaed7e..f0ec1b2864 100644 --- a/autotests/integration/modifier_only_shortcut.cpp +++ b/autotests/integration/modifier_only_shortcut.cpp @@ -270,15 +270,14 @@ TEST_CASE("modifier only shortcut", "[input]") keyboard_key_pressed(KEY_CAPSLOCK, timestamp++); keyboard_key_released(KEY_CAPSLOCK, timestamp++); QTRY_COMPARE(input::xkb::get_active_keyboard_modifiers(*setup.base->mod.input), - Qt::ShiftModifier); + Qt::NoModifier); QTRY_COMPARE(triggeredSpy.count(), 1); // currently caps lock is on - // shift still triggers keyboard_key_pressed(modifier, timestamp++); keyboard_key_released(modifier, timestamp++); QTRY_COMPARE(input::xkb::get_active_keyboard_modifiers(*setup.base->mod.input), - Qt::ShiftModifier); + Qt::NoModifier); QTRY_COMPARE(triggeredSpy.count(), 2); // meta should also trigger diff --git a/autotests/integration/move_resize_window.cpp b/autotests/integration/move_resize_window.cpp index 7838d0695c..5eca534f10 100644 --- a/autotests/integration/move_resize_window.cpp +++ b/autotests/integration/move_resize_window.cpp @@ -679,7 +679,8 @@ TEST_CASE("move resize window", "[win]") // use NETRootInfo to trigger a move request win::x11::net::root_info root(c.get(), win::x11::net::Properties()); - root.moveResizeRequest(w, origGeo.center().x(), origGeo.center().y(), win::x11::net::Move); + root.moveResizeRequest( + w, origGeo.center().x(), origGeo.center().y(), win::x11::net::Move, XCB_BUTTON_INDEX_1); xcb_flush(c.get()); QVERIFY(moveStartSpy.wait()); @@ -697,7 +698,8 @@ TEST_CASE("move resize window", "[win]") root.moveResizeRequest(w, client->geo.frame.center().x(), client->geo.frame.center().y(), - win::x11::net::MoveResizeCancel); + win::x11::net::MoveResizeCancel, + XCB_BUTTON_INDEX_1); xcb_flush(c.get()); QVERIFY(moveEndSpy.wait()); diff --git a/autotests/integration/tabbox.cpp b/autotests/integration/tabbox.cpp index cd800407b6..f9cbefb09f 100644 --- a/autotests/integration/tabbox.cpp +++ b/autotests/integration/tabbox.cpp @@ -213,13 +213,12 @@ TEST_CASE("tabbox", "[win]") quint32 timestamp = 0; keyboard_key_pressed(KEY_CAPSLOCK, timestamp++); keyboard_key_released(KEY_CAPSLOCK, timestamp++); - QCOMPARE(input::xkb::get_active_keyboard_modifiers(*setup.base->mod.input), - Qt::ShiftModifier); + QCOMPARE(input::xkb::get_active_keyboard_modifiers(*setup.base->mod.input), Qt::NoModifier); // press alt+tab keyboard_key_pressed(KEY_LEFTALT, timestamp++); REQUIRE(input::xkb::get_active_keyboard_modifiers(*setup.base->mod.input) - == (Qt::ShiftModifier | Qt::AltModifier)); + == Qt::AltModifier); keyboard_key_pressed(KEY_TAB, timestamp++); keyboard_key_released(KEY_TAB, timestamp++); @@ -239,9 +238,9 @@ TEST_CASE("tabbox", "[win]") QCOMPARE(setup.base->mod.space->tabbox->is_grabbed(), false); // Has walked backwards to the previously lowest client in the stacking order. - QCOMPARE(get_wayland_window(setup.base->mod.space->stacking.active), c1); + QCOMPARE(get_wayland_window(setup.base->mod.space->stacking.active), c2); QCOMPARE(setup.base->mod.space->stacking.order.stack, - (std::deque{c2, c3, c1})); + (std::deque{c1, c3, c2})); surface3.reset(); QVERIFY(wait_for_destroyed(c3)); diff --git a/autotests/integration/xdg-shell_rules.cpp b/autotests/integration/xdg-shell_rules.cpp index 6a4ca2dc56..176434286f 100644 --- a/autotests/integration/xdg-shell_rules.cpp +++ b/autotests/integration/xdg-shell_rules.cpp @@ -4025,6 +4025,98 @@ TEST_CASE("xdg-shell rules", "[win]") QVERIFY(wait_for_destroyed(client)); } + SECTION("closeable dont affect") + { + // Initialize RuleBook with the test rule. + auto [config, group] = get_config(); + group.writeEntry("closeable", false); + group.writeEntry("closeablerule", enum_index(win::rules::action::dont_affect)); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", enum_index(win::rules::name_match::exact)); + group.sync(); + setup.base->mod.space->rule_book->config = config; + win::space_reconfigure(*setup.base->mod.space); + + // Create the test client. + wayland_window* client; + std::unique_ptr surface; + std::unique_ptr shellSurface; + std::tie(client, surface, shellSurface) = createWindow("org.kde.foo"); + QVERIFY(client); + QVERIFY(client->control->active); + REQUIRE(client->isCloseable()); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(wait_for_destroyed(client)); + } + + SECTION("closeable force") + { + // Initialize RuleBook with the test rule. + auto [config, group] = get_config(); + group.writeEntry("closeable", false); + group.writeEntry("closeablerule", enum_index(win::rules::action::force)); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", enum_index(win::rules::name_match::exact)); + group.sync(); + setup.base->mod.space->rule_book->config = config; + win::space_reconfigure(*setup.base->mod.space); + + // Create the test client. + wayland_window* client; + std::unique_ptr surface; + std::unique_ptr shellSurface; + std::tie(client, surface, shellSurface) = createWindow("org.kde.foo"); + QVERIFY(client); + QVERIFY(client->control->active); + REQUIRE(!client->isCloseable()); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(wait_for_destroyed(client)); + } + + SECTION("closeable force temporarily") + { + // Initialize RuleBook with the test rule. + auto [config, group] = get_config(); + group.writeEntry("closeable", false); + group.writeEntry("closeablerule", enum_index(win::rules::action::force_temporarily)); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", enum_index(win::rules::name_match::exact)); + group.sync(); + setup.base->mod.space->rule_book->config = config; + win::space_reconfigure(*setup.base->mod.space); + + // Create the test client. + wayland_window* client; + std::unique_ptr surface; + std::unique_ptr shellSurface; + std::tie(client, surface, shellSurface) = createWindow("org.kde.foo"); + QVERIFY(client); + QVERIFY(client->control->active); + REQUIRE(!client->isCloseable()); + + // The rule should be discarded when the client is closed. + shellSurface.reset(); + surface.reset(); + QVERIFY(wait_for_destroyed(client)); + std::tie(client, surface, shellSurface) = createWindow("org.kde.foo"); + QVERIFY(client); + REQUIRE(client->isCloseable()); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(wait_for_destroyed(client)); + } + SECTION("match after name change") { auto [config, group] = get_config(); diff --git a/como-config.cmake.in b/como-config.cmake.in index 359e182d7b..42af56d37a 100644 --- a/como-config.cmake.in +++ b/como-config.cmake.in @@ -4,6 +4,7 @@ include(CMakeFindDependencyMacro) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/modules/") find_dependency(Wrapland) +find_dependency(epoxy) find_dependency(Pixman) find_dependency(wlroots @WLROOTS_MIN_VERSION@) find_dependency(Qt6Gui @QT_MIN_VERSION@) diff --git a/como/data/update_default_rules.cpp b/como/data/update_default_rules.cpp index ec82147216..f405fa9cb2 100644 --- a/como/data/update_default_rules.cpp +++ b/como/data/update_default_rules.cpp @@ -6,9 +6,11 @@ SPDX-License-Identifier: GPL-2.0-or-later // read additional window rules and add them to kwinrulesrc +#include +#include +#include #include #include -#include #include #include diff --git a/como/desktop/kde/dbus/KWinDBusInterfaceConfig.cmake.in b/como/desktop/kde/dbus/KWinDBusInterfaceConfig.cmake.in index 8c51bdfc7a..96130bad54 100644 --- a/como/desktop/kde/dbus/KWinDBusInterfaceConfig.cmake.in +++ b/como/desktop/kde/dbus/KWinDBusInterfaceConfig.cmake.in @@ -8,7 +8,7 @@ set(KWIN_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.KWin.xml") set(KWIN_COMPOSITING_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.kwin.Compositing.xml") set(KWIN_EFFECTS_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.kwin.Effects.xml") set(KWIN_INPUTDEVICE_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.kwin.InputDevice.xml") -set(KWIN_COLORCORRECT_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.kwin.ColorCorrect.xml") +set(KWIN_NIGHTLIGHT_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.KWin.NightLight.xml") set(KWIN_WAYLAND_BIN_PATH "@CMAKE_INSTALL_FULL_BINDIR@/kwin_wayland") # Still needs to be set as plasma-workspace expects it at compile time. diff --git a/como/desktop/kde/dbus/kwin.h b/como/desktop/kde/dbus/kwin.h index 1f62299c31..4a0ee40d26 100644 --- a/como/desktop/kde/dbus/kwin.h +++ b/como/desktop/kde/dbus/kwin.h @@ -14,8 +14,10 @@ #include #include +#include +#include +#include #include -#include namespace como { diff --git a/como/input/filters/internal_window.h b/como/input/filters/internal_window.h index 6795aaa4fe..64b4a007c5 100644 --- a/como/input/filters/internal_window.h +++ b/como/input/filters/internal_window.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace como::input { @@ -51,15 +52,14 @@ class internal_window_filter : public event_filter } auto qt_event = button_to_qt_event(*this->redirect.pointer, event); - auto adapted_qt_event = QMouseEvent(qt_event.type(), - qt_event.pos() - internal->position(), - qt_event.pos(), - qt_event.button(), - qt_event.buttons(), - qt_event.modifiers()); - adapted_qt_event.setAccepted(false); - QCoreApplication::sendEvent(internal, &adapted_qt_event); - return adapted_qt_event.isAccepted(); + return QWindowSystemInterface::handleMouseEvent< + QWindowSystemInterface::SynchronousDelivery>(internal, + qt_event.position() - internal->position(), + qt_event.globalPosition(), + qt_event.buttons(), + qt_event.button(), + qt_event.type(), + qt_event.modifiers()); } bool motion(motion_event const& event) override diff --git a/como/input/wayland/kglobalaccel/runtime/global_accel_d.cpp b/como/input/wayland/kglobalaccel/runtime/global_accel_d.cpp index 9b39aac3ed..670a9483f7 100644 --- a/como/input/wayland/kglobalaccel/runtime/global_accel_d.cpp +++ b/como/input/wayland/kglobalaccel/runtime/global_accel_d.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include diff --git a/como/input/wayland/kglobalaccel/runtime/global_accel_d.h b/como/input/wayland/kglobalaccel/runtime/global_accel_d.h index 4f6b81d2e7..d989963fe4 100644 --- a/como/input/wayland/kglobalaccel/runtime/global_accel_d.h +++ b/como/input/wayland/kglobalaccel/runtime/global_accel_d.h @@ -7,9 +7,9 @@ #include #include +#include #include #include -#include struct KGlobalAccelDPrivate; diff --git a/como/input/xkb/keyboard.cpp b/como/input/xkb/keyboard.cpp index b78225c051..ae58e33aeb 100644 --- a/como/input/xkb/keyboard.cpp +++ b/como/input/xkb/keyboard.cpp @@ -148,8 +148,7 @@ void keyboard::update_modifiers() constexpr auto is_active = xkb_state_mod_index_is_active; auto mods = Qt::KeyboardModifiers(); - if (is_active(state, modifiers_indices.shift, XKB_STATE_MODS_EFFECTIVE) == 1 - || is_active(state, modifiers_indices.caps, XKB_STATE_MODS_EFFECTIVE) == 1) { + if (is_active(state, modifiers_indices.shift, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::ShiftModifier; } if (is_active(state, modifiers_indices.alt, XKB_STATE_MODS_EFFECTIVE) == 1) { @@ -268,6 +267,9 @@ Qt::KeyboardModifiers keyboard::modifiers_relevant_for_global_shortcuts(uint32_t if (is_active(state, modifiers_indices.meta, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::MetaModifier; } + if (keysym >= XKB_KEY_KP_Space && keysym <= XKB_KEY_KP_9) { + mods |= Qt::KeypadModifier; + } auto consumed_mods = qt_modifiers_consumed; if ((mods & Qt::ShiftModifier) && (consumed_mods == Qt::ShiftModifier)) { diff --git a/como/render/CMakeLists.txt b/como/render/CMakeLists.txt index 827c996f3e..bfe06efe55 100644 --- a/como/render/CMakeLists.txt +++ b/como/render/CMakeLists.txt @@ -156,7 +156,7 @@ qt6_add_dbus_adaptor(render_dbus_SRCS como::render::dbus::compositing_qobject ) qt6_add_dbus_adaptor(render_dbus_SRCS - dbus/org.kde.kwin.ColorCorrect.xml + dbus/org.kde.KWin.NightLight.xml post/color_correct_dbus_interface.h como::render::post::color_correct_dbus_interface ) @@ -300,7 +300,7 @@ install( ) install( FILES - dbus/org.kde.kwin.ColorCorrect.xml + dbus/org.kde.KWin.NightLight.xml dbus/org.kde.kwin.Compositing.xml effect/interface/org.kde.kwin.Effects.xml DESTINATION diff --git a/como/render/dbus/org.kde.kwin.ColorCorrect.xml b/como/render/dbus/org.kde.KWin.NightLight.xml similarity index 73% rename from como/render/dbus/org.kde.kwin.ColorCorrect.xml rename to como/render/dbus/org.kde.KWin.NightLight.xml index 137f948e0c..1589fc8974 100644 --- a/como/render/dbus/org.kde.kwin.ColorCorrect.xml +++ b/como/render/dbus/org.kde.KWin.NightLight.xml @@ -1,15 +1,15 @@ - - + + + + + + + + + + + @@ -73,36 +91,36 @@ - + diff --git a/como/render/effect/frame.cpp b/como/render/effect/frame.cpp index f2741453ee..62ef71c2fd 100644 --- a/como/render/effect/frame.cpp +++ b/como/render/effect/frame.cpp @@ -250,7 +250,6 @@ effect_frame_impl::effect_frame_impl(EffectsHandler& effects, this, [this](const QRect& oldGeometry, const QRect& newGeometry) { this->effects.addRepaint(oldGeometry); - m_geometry = newGeometry; this->effects.addRepaint(newGeometry); }); } @@ -274,7 +273,7 @@ void effect_frame_impl::setAlignment(Qt::Alignment alignment) m_view->setAlignment(alignment); } -const QFont& effect_frame_impl::font() const +QFont effect_frame_impl::font() const { return m_view->font(); } @@ -289,10 +288,9 @@ void effect_frame_impl::free() m_view->hide(); } -const QRect& effect_frame_impl::geometry() const +QRect effect_frame_impl::geometry() const { - // Can't forward to OffscreenQuickView::geometry() because we return a reference. - return m_geometry; + return m_view->geometry(); } void effect_frame_impl::setGeometry(const QRect& geometry, bool force) @@ -301,7 +299,7 @@ void effect_frame_impl::setGeometry(const QRect& geometry, bool force) m_view->setGeometry(geometry); } -const QIcon& effect_frame_impl::icon() const +QIcon effect_frame_impl::icon() const { return m_view->icon(); } @@ -316,7 +314,7 @@ void effect_frame_impl::setIcon(const QIcon& icon) } } -const QSize& effect_frame_impl::iconSize() const +QSize effect_frame_impl::iconSize() const { return m_view->iconSize(); } @@ -347,7 +345,7 @@ void effect_frame_impl::render(const QRegion& region, double opacity, double fra effects.renderOffscreenQuickView(m_view); } -const QString& effect_frame_impl::text() const +QString effect_frame_impl::text() const { return m_view->text(); } diff --git a/como/render/effect/frame.h b/como/render/effect/frame.h index 0e26570511..15041d8079 100644 --- a/como/render/effect/frame.h +++ b/como/render/effect/frame.h @@ -115,16 +115,16 @@ class COMO_EXPORT effect_frame_impl : public QObject, public EffectFrame double frameOpacity = 1.0) override; Qt::Alignment alignment() const override; void setAlignment(Qt::Alignment alignment) override; - const QFont& font() const override; + QFont font() const override; void setFont(const QFont& font) override; - const QRect& geometry() const override; + QRect geometry() const override; void setGeometry(const QRect& geometry, bool force = false) override; - const QIcon& icon() const override; + QIcon icon() const override; void setIcon(const QIcon& icon) override; - const QSize& iconSize() const override; + QSize iconSize() const override; void setIconSize(const QSize& size) override; void setPosition(const QPoint& point) override; - const QString& text() const override; + QString text() const override; void setText(const QString& text) override; EffectFrameStyle style() const override; bool isCrossFade() const override; @@ -139,7 +139,6 @@ class COMO_EXPORT effect_frame_impl : public QObject, public EffectFrame Q_DISABLE_COPY(effect_frame_impl) effect_frame_quick_scene* m_view; - QRect m_geometry; }; } diff --git a/como/render/effect/interface/effect_frame.h b/como/render/effect/interface/effect_frame.h index 1d7f374db3..b27c200473 100644 --- a/como/render/effect/interface/effect_frame.h +++ b/como/render/effect/interface/effect_frame.h @@ -53,19 +53,19 @@ class COMO_EXPORT EffectFrame virtual void setAlignment(Qt::Alignment alignment) = 0; virtual Qt::Alignment alignment() const = 0; virtual void setGeometry(const QRect& geometry, bool force = false) = 0; - virtual const QRect& geometry() const = 0; + virtual QRect geometry() const = 0; virtual void setText(const QString& text) = 0; - virtual const QString& text() const = 0; + virtual QString text() const = 0; virtual void setFont(const QFont& font) = 0; - virtual const QFont& font() const = 0; + virtual QFont font() const = 0; /** * Set the icon that will appear on the left-hand size of the frame. */ virtual void setIcon(const QIcon& icon) = 0; - virtual const QIcon& icon() const = 0; + virtual QIcon icon() const = 0; virtual void setIconSize(const QSize& size) = 0; - virtual const QSize& iconSize() const = 0; + virtual QSize iconSize() const = 0; /** * @returns The style of this EffectFrame. diff --git a/como/render/effect/interface/effect_togglable_state.cpp b/como/render/effect/interface/effect_togglable_state.cpp index f7866a71ae..81f3dfb708 100644 --- a/como/render/effect/interface/effect_togglable_state.cpp +++ b/como/render/effect/interface/effect_togglable_state.cpp @@ -112,7 +112,7 @@ void EffectTogglableState::partialDeactivate(qreal factor) void EffectTogglableState::toggle() { - if (m_status == Status::Inactive) { + if (m_status != Status::Active) { activate(); Q_EMIT activated(); } else { @@ -234,14 +234,14 @@ EffectTogglableTouchBorder::EffectTogglableTouchBorder(EffectTogglableState* sta EffectTogglableTouchBorder::~EffectTogglableTouchBorder() { for (const ElectricBorder& border : std::as_const(m_touchBorderActivate)) { - effects->unregisterTouchBorder(border, m_state->toggleAction()); + effects->unregisterTouchBorder(border, m_state->activateAction()); } } void EffectTogglableTouchBorder::setBorders(const QList& touchActivateBorders) { for (const ElectricBorder& border : std::as_const(m_touchBorderActivate)) { - effects->unregisterTouchBorder(border, m_state->toggleAction()); + effects->unregisterTouchBorder(border, m_state->activateAction()); } m_touchBorderActivate.clear(); @@ -249,7 +249,7 @@ void EffectTogglableTouchBorder::setBorders(const QList& touchActivateBorde m_touchBorderActivate.append(ElectricBorder(border)); effects->registerRealtimeTouchBorder( ElectricBorder(border), - m_state->toggleAction(), + m_state->activateAction(), [this](ElectricBorder border, QSizeF const& deltaProgress, EffectScreen const* /*screen*/) { diff --git a/como/render/effect/interface/offscreen_quick_view.cpp b/como/render/effect/interface/offscreen_quick_view.cpp index 23eac93af3..d505c41830 100644 --- a/como/render/effect/interface/offscreen_quick_view.cpp +++ b/como/render/effect/interface/offscreen_quick_view.cpp @@ -37,15 +37,15 @@ namespace como class Q_DECL_HIDDEN OffscreenQuickView::Private { public: - QQuickWindow* m_view; - QQuickRenderControl* m_renderControl; - QScopedPointer m_offscreenSurface; - QScopedPointer m_glcontext; - QScopedPointer m_fbo; + std::unique_ptr m_view; + std::unique_ptr m_renderControl; + std::unique_ptr m_offscreenSurface; + std::unique_ptr m_glcontext; + std::unique_ptr m_fbo; QTimer* m_repaintTimer; QImage m_image; - QScopedPointer m_textureExport; + std::unique_ptr m_textureExport; // if we should capture a QImage after rendering into our BO. // Used for either software QtQuick rendering and nonGL kwin rendering bool m_useBlit = false; @@ -71,16 +71,16 @@ class Q_DECL_HIDDEN OffscreenQuickScene::Private { } - QScopedPointer qmlComponent; - QScopedPointer quickItem; + std::unique_ptr qmlComponent; + std::unique_ptr quickItem; }; OffscreenQuickView::OffscreenQuickView(ExportMode exportMode, bool alpha) : d(new OffscreenQuickView::Private) { - d->m_renderControl = new QQuickRenderControl(); + d->m_renderControl = std::make_unique(); - d->m_view = new QQuickWindow(d->m_renderControl); + d->m_view = std::make_unique(d->m_renderControl.get()); Q_ASSERT(d->m_view->setProperty("_KWIN_WINDOW_IS_OFFSCREEN", true) || true); d->m_view->setFlags(Qt::FramelessWindowHint); d->m_view->setColor(Qt::transparent); @@ -125,7 +125,7 @@ OffscreenQuickView::OffscreenQuickView(ExportMode exportMode, bool alpha) d->m_offscreenSurface->setFormat(d->m_glcontext->format()); d->m_offscreenSurface->create(); - d->m_glcontext->makeCurrent(d->m_offscreenSurface.data()); + d->m_glcontext->makeCurrent(d->m_offscreenSurface.get()); d->m_view->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(d->m_glcontext.get())); d->m_renderControl->initialize(); d->m_glcontext->doneCurrent(); @@ -141,19 +141,19 @@ OffscreenQuickView::OffscreenQuickView(ExportMode exportMode, bool alpha) auto updateSize = [this]() { contentItem()->setSize(d->m_view->size()); }; updateSize(); - connect(d->m_view, &QWindow::widthChanged, this, updateSize); - connect(d->m_view, &QWindow::heightChanged, this, updateSize); + connect(d->m_view.get(), &QWindow::widthChanged, this, updateSize); + connect(d->m_view.get(), &QWindow::heightChanged, this, updateSize); d->m_repaintTimer = new QTimer(this); d->m_repaintTimer->setSingleShot(true); d->m_repaintTimer->setInterval(10); connect(d->m_repaintTimer, &QTimer::timeout, this, &OffscreenQuickView::update); - connect(d->m_renderControl, + connect(d->m_renderControl.get(), &QQuickRenderControl::renderRequested, this, &OffscreenQuickView::handleRenderRequested); - connect(d->m_renderControl, + connect(d->m_renderControl.get(), &QQuickRenderControl::sceneChanged, this, &OffscreenQuickView::handleSceneChanged); @@ -169,25 +169,22 @@ OffscreenQuickView::OffscreenQuickView(ExportMode exportMode, bool alpha) OffscreenQuickView::~OffscreenQuickView() { - disconnect(d->m_renderControl, + disconnect(d->m_renderControl.get(), &QQuickRenderControl::renderRequested, this, &OffscreenQuickView::handleRenderRequested); - disconnect(d->m_renderControl, + disconnect(d->m_renderControl.get(), &QQuickRenderControl::sceneChanged, this, &OffscreenQuickView::handleSceneChanged); if (d->m_glcontext) { // close the view whilst we have an active GL context - d->m_glcontext->makeCurrent(d->m_offscreenSurface.data()); + d->m_glcontext->makeCurrent(d->m_offscreenSurface.get()); } - delete d->m_renderControl; // Always delete render control first. - d->m_renderControl = nullptr; - - delete d->m_view; - d->m_view = nullptr; + d->m_view.reset(); + d->m_renderControl.reset(); } bool OffscreenQuickView::automaticRepaint() const @@ -235,18 +232,18 @@ void OffscreenQuickView::update() bool usingGl = d->m_glcontext != nullptr; if (usingGl) { - if (!d->m_glcontext->makeCurrent(d->m_offscreenSurface.data())) { + if (!d->m_glcontext->makeCurrent(d->m_offscreenSurface.get())) { // probably a context loss event, kwin is about to reset all the effects anyway return; } - const QSize nativeSize = d->m_view->size() * d->m_view->effectiveDevicePixelRatio(); - if (d->m_fbo.isNull() || d->m_fbo->size() != nativeSize) { + const QSize nativeSize = d->m_view->size() * d->m_view->devicePixelRatio(); + if (!d->m_fbo || d->m_fbo->size() != nativeSize) { d->m_textureExport.reset(nullptr); QOpenGLFramebufferObjectFormat fboFormat; fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - fboFormat.setInternalTextureFormat(d->m_hasAlphaChannel ? GL_RGBA8 : GL_RGB8); + fboFormat.setInternalTextureFormat(GL_RGBA8); d->m_fbo.reset(new QOpenGLFramebufferObject(nativeSize, fboFormat)); if (!d->m_fbo->isValid()) { @@ -298,7 +295,8 @@ void OffscreenQuickView::forwardMouseEvent(QEvent* e) const QPoint widgetPos = d->m_view->mapFromGlobal(me->pos()); QMouseEvent cloneEvent( me->type(), widgetPos, me->pos(), me->button(), me->buttons(), me->modifiers()); - QCoreApplication::sendEvent(d->m_view, &cloneEvent); + cloneEvent.setAccepted(false); + QCoreApplication::sendEvent(d->m_view.get(), &cloneEvent); e->setAccepted(cloneEvent.isAccepted()); if (e->type() == QEvent::MouseButtonPress) { @@ -317,7 +315,7 @@ void OffscreenQuickView::forwardMouseEvent(QEvent* e) me->button(), me->buttons(), me->modifiers()); - QCoreApplication::sendEvent(d->m_view, &doubleClickEvent); + QCoreApplication::sendEvent(d->m_view.get(), &doubleClickEvent); } } @@ -330,7 +328,8 @@ void OffscreenQuickView::forwardMouseEvent(QEvent* e) auto const widgetPos = d->m_view->mapFromGlobal(he->position()); const QPointF oldWidgetPos = d->m_view->mapFromGlobal(he->oldPos()); QHoverEvent cloneEvent(he->type(), widgetPos, widgetPos, oldWidgetPos, he->modifiers()); - QCoreApplication::sendEvent(d->m_view, &cloneEvent); + cloneEvent.setAccepted(false); + QCoreApplication::sendEvent(d->m_view.get(), &cloneEvent); e->setAccepted(cloneEvent.isAccepted()); return; } @@ -345,7 +344,8 @@ void OffscreenQuickView::forwardMouseEvent(QEvent* e) we->modifiers(), we->phase(), we->inverted()); - QCoreApplication::sendEvent(d->m_view, &cloneEvent); + cloneEvent.setAccepted(false); + QCoreApplication::sendEvent(d->m_view.get(), &cloneEvent); e->setAccepted(cloneEvent.isAccepted()); return; } @@ -359,7 +359,7 @@ void OffscreenQuickView::forwardKeyEvent(QKeyEvent* keyEvent) if (!d->m_visible) { return; } - QCoreApplication::sendEvent(d->m_view, keyEvent); + QCoreApplication::sendEvent(d->m_view.get(), keyEvent); } bool OffscreenQuickView::forwardTouchDown(qint32 id, const QPointF& pos, quint32 time) @@ -369,7 +369,8 @@ bool OffscreenQuickView::forwardTouchDown(qint32 id, const QPointF& pos, quint32 d->updateTouchState(Qt::TouchPointPressed, id, pos); QTouchEvent event(QEvent::TouchBegin, d->touchDevice, Qt::NoModifier, d->touchPoints); - QCoreApplication::sendEvent(d->m_view, &event); + event.setAccepted(false); + QCoreApplication::sendEvent(d->m_view.get(), &event); return event.isAccepted(); } @@ -381,7 +382,8 @@ bool OffscreenQuickView::forwardTouchMotion(qint32 id, const QPointF& pos, quint d->updateTouchState(Qt::TouchPointMoved, id, pos); QTouchEvent event(QEvent::TouchUpdate, d->touchDevice, Qt::NoModifier, d->touchPoints); - QCoreApplication::sendEvent(d->m_view, &event); + event.setAccepted(false); + QCoreApplication::sendEvent(d->m_view.get(), &event); return event.isAccepted(); } @@ -393,7 +395,8 @@ bool OffscreenQuickView::forwardTouchUp(qint32 id, quint32 time) d->updateTouchState(Qt::TouchPointReleased, id, QPointF{}); QTouchEvent event(QEvent::TouchEnd, d->touchDevice, Qt::NoModifier, d->touchPoints); - QCoreApplication::sendEvent(d->m_view, &event); + event.setAccepted(false); + QCoreApplication::sendEvent(d->m_view.get(), &event); return event.isAccepted(); } @@ -425,7 +428,7 @@ QQuickItem* OffscreenQuickView::contentItem() const QQuickWindow* OffscreenQuickView::window() const { - return d->m_view; + return d->m_view.get(); } void OffscreenQuickView::setVisible(bool visible) @@ -474,7 +477,7 @@ GLTexture* OffscreenQuickView::bufferAsTexture() d->m_fbo->texture(), d->m_fbo->format().internalTextureFormat(), d->m_fbo->size())); } } - return d->m_textureExport.data(); + return d->m_textureExport.get(); } QImage OffscreenQuickView::bufferAsImage() const @@ -491,13 +494,15 @@ void OffscreenQuickView::setGeometry(const QRect& rect) { const QRect oldGeometry = d->m_view->geometry(); d->m_view->setGeometry(rect); + // QWindow::setGeometry() won't sync output if there's no platform window. + d->m_view->setScreen(QGuiApplication::screenAt(rect.center())); Q_EMIT geometryChanged(oldGeometry, rect); } void OffscreenQuickView::Private::releaseResources() { if (m_glcontext) { - m_glcontext->makeCurrent(m_offscreenSurface.data()); + m_glcontext->makeCurrent(m_offscreenSurface.get()); m_view->releaseResources(); m_glcontext->doneCurrent(); } else { @@ -633,7 +638,7 @@ void OffscreenQuickScene::setSource(const QUrl& source, const QVariantMap& initi QQuickItem* OffscreenQuickScene::rootItem() const { - return d->quickItem.data(); + return d->quickItem.get(); } } diff --git a/como/render/effect/interface/quick_scene.cpp b/como/render/effect/interface/quick_scene.cpp index ee8fffaecc..edc0066429 100644 --- a/como/render/effect/interface/quick_scene.cpp +++ b/como/render/effect/interface/quick_scene.cpp @@ -460,6 +460,9 @@ void QuickSceneEffect::addScreen(EffectScreen const* screen) view.get(), &QuickSceneView::scheduleRepaint); view->scheduleRepaint(); + + // view is returned via invokables elsewhere + QJSEngine::setObjectOwnership(view.get(), QJSEngine::CppOwnership); d->views[screen] = std::move(view); } else if (incubator->isError()) { qCWarning(KWIN_CORE) @@ -506,6 +509,10 @@ void QuickSceneEffect::startInternal() Q_EMIT delegateChanged(); } + if (!d->delegate->isReady()) { + return; + } + effects->setActiveFullScreenEffect(this); d->running = true; diff --git a/como/render/post/color_correct_dbus_interface.cpp b/como/render/post/color_correct_dbus_interface.cpp index 511ed66f09..0284602554 100644 --- a/como/render/post/color_correct_dbus_interface.cpp +++ b/como/render/post/color_correct_dbus_interface.cpp @@ -5,8 +5,8 @@ SPDX-License-Identifier: GPL-2.0-or-later */ #include "color_correct_dbus_interface.h" -#include "colorcorrectadaptor.h" #include "night_color_data.h" +#include "nightlightadaptor.h" #include @@ -15,12 +15,12 @@ namespace como::render::post static void send_changed_properties(QVariantMap const& props) { - auto message = QDBusMessage::createSignal(QStringLiteral("/ColorCorrect"), + auto message = QDBusMessage::createSignal(QStringLiteral("/org/kde/KWin/NightLight"), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged")); message.setArguments({ - QStringLiteral("org.kde.kwin.ColorCorrect"), + QStringLiteral("org.kde.KWin.NightLight"), props, QStringList(), // invalidated_properties }); @@ -40,14 +40,14 @@ color_correct_dbus_interface::color_correct_dbus_interface( this, &color_correct_dbus_interface::removeInhibitorService); - new ColorCorrectAdaptor(this); - QDBusConnection::sessionBus().registerObject(QStringLiteral("/ColorCorrect"), this); - QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.NightColor")); + new NightLightAdaptor(this); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/NightLight"), this); + QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.KWin.NightLight")); } color_correct_dbus_interface::~color_correct_dbus_interface() { - QDBusConnection::sessionBus().unregisterService(QStringLiteral("org.kde.NightColor")); + QDBusConnection::sessionBus().unregisterService(QStringLiteral("org.kde.KWin.NightLight")); } bool color_correct_dbus_interface::isInhibited() const @@ -179,7 +179,7 @@ void color_correct_dbus_interface::send_transition_timings() const send_changed_properties(props); } -void color_correct_dbus_interface::nightColorAutoLocationUpdate(double latitude, double longitude) +void color_correct_dbus_interface::setLocation(double latitude, double longitude) { integration.loc_update(latitude, longitude); } @@ -228,4 +228,14 @@ void color_correct_dbus_interface::removeInhibitorService(const QString& service } } +void color_correct_dbus_interface::preview(uint /*previewTemp*/) +{ + // TODO(romangg): implement +} + +void color_correct_dbus_interface::stopPreview() +{ + // TODO(romangg): implement +} + } diff --git a/como/render/post/color_correct_dbus_interface.h b/como/render/post/color_correct_dbus_interface.h index 088808abff..c65467cbf8 100644 --- a/como/render/post/color_correct_dbus_interface.h +++ b/como/render/post/color_correct_dbus_interface.h @@ -36,7 +36,7 @@ struct color_correct_dbus_integration { class COMO_EXPORT color_correct_dbus_interface : public QObject, public QDBusContext { Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.ColorCorrect") + Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.NightLight") Q_PROPERTY(bool inhibited READ isInhibited) Q_PROPERTY(bool enabled READ isEnabled) Q_PROPERTY(bool running READ isRunning) @@ -79,21 +79,25 @@ class COMO_EXPORT color_correct_dbus_interface : public QObject, public QDBusCon public Q_SLOTS: /** * @brief For receiving auto location updates, primarily through the KDE Daemon - * @return void - * @since 5.12 */ - void nightColorAutoLocationUpdate(double latitude, double longitude); + void setLocation(double latitude, double longitude); /** * @brief Temporarily blocks Night Color. - * @since 5.18 */ uint inhibit(); /** * @brief Cancels the previous call to inhibit(). - * @since 5.18 */ void uninhibit(uint cookie); + /** + * @brief Previews a given temperature for a short time (15s). + */ + void preview(uint temperature); + /** + * @brief Stops an ongoing preview. + */ + void stopPreview(); private Q_SLOTS: void removeInhibitorService(const QString& serviceName); diff --git a/como/script/space.h b/como/script/space.h index 78e6a7da9e..adc59dd81b 100644 --- a/como/script/space.h +++ b/como/script/space.h @@ -421,7 +421,7 @@ public Q_SLOTS: * @since 5.0 */ void virtualScreenGeometryChanged(); - void currentDesktopChanged(); + void currentDesktopChanged(como::win::subspace* prev); protected: space() = default; diff --git a/como/script/window_thumbnail_item.cpp b/como/script/window_thumbnail_item.cpp index 1ab9c6449a..460885c4ad 100644 --- a/como/script/window_thumbnail_item.cpp +++ b/como/script/window_thumbnail_item.cpp @@ -18,6 +18,7 @@ SPDX-License-Identifier: GPL-2.0-or-later #include #include +#include #include #include #include @@ -25,18 +26,159 @@ SPDX-License-Identifier: GPL-2.0-or-later namespace como::scripting { + +namespace +{ +bool use_gl_thumbnails() +{ + static bool qt_quick_is_software + = QStringList({QStringLiteral("software"), QStringLiteral("softwarecontext")}) + .contains(QQuickWindow::sceneGraphBackend()); + return effects && effects->isOpenGLCompositing() && !qt_quick_is_software; +} +} + +window_thumbnail_source::window_thumbnail_source(QQuickWindow* view, + scripting::window* handle, + QUuid wId) + : m_view(view) + , m_handle(handle) + , wId{wId} +{ + connect(handle, &scripting::window::frameGeometryChanged, this, [this]() { + m_dirty = true; + Q_EMIT changed(); + }); + connect(handle, &scripting::window::damaged, this, [this]() { + m_dirty = true; + Q_EMIT changed(); + }); + + connect(effects, &EffectsHandler::frameRendered, this, &window_thumbnail_source::update); +} + +window_thumbnail_source::~window_thumbnail_source() +{ + if (!m_offscreenTexture) { + return; + } + + if (!QOpenGLContext::currentContext()) { + effects->makeOpenGLContextCurrent(); + } + + m_offscreenTarget.reset(); + m_offscreenTexture.reset(); + + if (m_acquireFence) { + glDeleteSync(m_acquireFence); + m_acquireFence = nullptr; + } +} + +std::shared_ptr +window_thumbnail_source::getOrCreate(QQuickWindow* window, scripting::window* handle, QUuid wId) +{ + using window_thumbnail_sourceKey = std::pair; + const window_thumbnail_sourceKey key{window, handle}; + + static std::map> sources; + auto& source = sources[key]; + if (!source.expired()) { + return source.lock(); + } + + auto s = std::make_shared(window, handle, wId); + source = s; + + QObject::connect(handle, &scripting::window::destroyed, [key]() { sources.erase(key); }); + QObject::connect(window, &QQuickWindow::destroyed, [key]() { sources.erase(key); }); + return s; +} + +window_thumbnail_source::Frame window_thumbnail_source::acquire() +{ + return Frame{ + .texture = m_offscreenTexture, + .fence = std::exchange(m_acquireFence, nullptr), + }; +} + +void window_thumbnail_source::update(effect::screen_paint_data& data) +{ + if (m_acquireFence || !m_dirty || !m_handle) { + return; + } + Q_ASSERT(m_view); + + auto const geometry = m_handle->visibleRect(); + auto const dpi = m_view->devicePixelRatio(); + auto const textureSize = dpi * geometry.size(); + + if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) { + m_offscreenTexture.reset(new GLTexture(GL_RGBA8, textureSize)); + m_offscreenTexture->setFilter(GL_LINEAR); + m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE); + m_offscreenTarget.reset(new GLFramebuffer(m_offscreenTexture.get())); + } + + QMatrix4x4 view; + view.ortho(geometry.x(), + geometry.x() + geometry.width(), + geometry.y(), + geometry.y() + geometry.height(), + -1, + 1); + + QMatrix4x4 proj; + proj.scale(dpi); + + auto effectWindow = effects->findWindow(wId); + + effect::window_paint_data win_data{ + *effectWindow, + { + .mask = Effect::PAINT_WINDOW_TRANSFORMED, + .region = infiniteRegion(), + }, + { + .targets = data.render.targets, + .view = view, + .projection = proj, + .viewport = geometry, + }, + }; + + render::push_framebuffer(win_data.render, m_offscreenTarget.get()); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + + // The thumbnail must be rendered using kwin's opengl context as VAOs are not + // shared across contexts. Unfortunately, this also introduces a latency of 1 + // frame, which is not ideal, but it is acceptable for things such as thumbnails. + effects->drawWindow(win_data); + render::pop_framebuffer(win_data.render); + + // The fence is needed to avoid the case where qtquick renderer starts using + // the texture while all rendering commands to it haven't completed yet. + m_dirty = false; + m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + + Q_EMIT changed(); +} + class ThumbnailTextureProvider : public QSGTextureProvider { public: explicit ThumbnailTextureProvider(QQuickWindow* window); QSGTexture* texture() const override; - void setTexture(QSharedPointer const& nativeTexture); + void setTexture(std::shared_ptr const& nativeTexture); void setTexture(QSGTexture* texture); private: QQuickWindow* m_window; - QSharedPointer m_nativeTexture; + std::shared_ptr m_nativeTexture; QScopedPointer m_texture; }; @@ -50,7 +192,7 @@ QSGTexture* ThumbnailTextureProvider::texture() const return m_texture.data(); } -void ThumbnailTextureProvider::setTexture(QSharedPointer const& nativeTexture) +void ThumbnailTextureProvider::setTexture(std::shared_ptr const& nativeTexture) { if (m_nativeTexture != nativeTexture) { auto const textureId = nativeTexture->texture(); @@ -94,23 +236,20 @@ window_thumbnail_item::window_thumbnail_item(QQuickItem* parent) : QQuickItem(parent) { setFlag(ItemHasContents); - update_render_notifier(); connect(render::singleton_interface::compositor, &render::compositor_qobject::aboutToToggleCompositing, this, - &window_thumbnail_item::destroyOffscreenTexture); + &window_thumbnail_item::reset_source); connect(render::singleton_interface::compositor, &render::compositor_qobject::compositingToggled, this, - &window_thumbnail_item::update_render_notifier); - connect(this, &QQuickItem::windowChanged, this, &window_thumbnail_item::update_render_notifier); + &window_thumbnail_item::update_source); + connect(this, &QQuickItem::windowChanged, this, &window_thumbnail_item::update_source); } window_thumbnail_item::~window_thumbnail_item() { - destroyOffscreenTexture(); - if (m_provider) { if (window()) { window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider), @@ -147,97 +286,61 @@ QSGTextureProvider* window_thumbnail_item::textureProvider() const return m_provider; } -void window_thumbnail_item::update_render_notifier() +void window_thumbnail_item::reset_source() { - disconnect(render_notifier); - - if (!window()) { - return; - } - - if (!use_gl_thumbnails()) { - return; - } - - render_notifier = connect(effects, - &EffectsHandler::frameRendered, - this, - &window_thumbnail_item::updateOffscreenTexture); + m_source.reset(); } -bool window_thumbnail_item::use_gl_thumbnails() const +void window_thumbnail_item::update_source() { - static bool qt_quick_is_software - = QStringList({QStringLiteral("software"), QStringLiteral("softwarecontext")}) - .contains(QQuickWindow::sceneGraphBackend()); - return effects && effects->isOpenGLCompositing() && !qt_quick_is_software; -} - -QSize window_thumbnail_item::sourceSize() const -{ - return m_sourceSize; -} - -void window_thumbnail_item::setSourceSize(const QSize& sourceSize) -{ - if (m_sourceSize != sourceSize) { - m_sourceSize = sourceSize; - invalidateOffscreenTexture(); - Q_EMIT sourceSizeChanged(); - } -} - -void window_thumbnail_item::destroyOffscreenTexture() -{ - if (!use_gl_thumbnails()) { - return; - } - - if (m_offscreenTexture) { - effects->makeOpenGLContextCurrent(); - m_offscreenTarget.reset(); - m_offscreenTexture.reset(); - - if (m_acquireFence) { - glDeleteSync(m_acquireFence); - m_acquireFence = nullptr; - } - effects->doneOpenGLContextCurrent(); + if (use_gl_thumbnails() && window() && m_client) { + m_source = window_thumbnail_source::getOrCreate(window(), m_client, m_wId); + connect(m_source.get(), + &window_thumbnail_source::changed, + this, + &window_thumbnail_item::update); + } else { + m_source.reset(); } } QSGNode* window_thumbnail_item::updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData*) { - if (effects && !m_offscreenTexture) { - return oldNode; - } + if (effects) { + if (!m_source) { + return oldNode; + } - // Wait for rendering commands to the offscreen texture complete if there are any. - if (m_acquireFence) { - glClientWaitSync(m_acquireFence, GL_SYNC_FLUSH_COMMANDS_BIT, 5000); - glDeleteSync(m_acquireFence); - m_acquireFence = nullptr; - } + auto [texture, acquireFence] = m_source->acquire(); + if (!texture) { + return oldNode; + } - if (!m_provider) { - m_provider = new ThumbnailTextureProvider(window()); - } + // Wait for rendering commands to the offscreen texture complete if there are any. + if (acquireFence) { + glWaitSync(acquireFence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(acquireFence); + } - if (m_offscreenTexture) { - m_provider->setTexture(m_offscreenTexture); + if (!m_provider) { + m_provider = new ThumbnailTextureProvider(window()); + } + m_provider->setTexture(texture); } else { + if (!m_provider) { + m_provider = new ThumbnailTextureProvider(window()); + } + auto const placeholderImage = fallbackImage(); m_provider->setTexture(window()->createTextureFromImage(placeholderImage)); - m_devicePixelRatio = placeholderImage.devicePixelRatio(); } - auto node = static_cast(oldNode); + QSGImageNode* node = static_cast(oldNode); if (!node) { node = window()->createImageNode(); node->setFiltering(QSGTexture::Linear); } node->setTexture(m_provider->texture()); - node->setTextureCoordinatesTransform(QSGImageNode::NoTransform); node->setRect(paintedRect()); @@ -271,6 +374,7 @@ void window_thumbnail_item::setWId(const QUuid& wId) } else { if (m_client) { m_client = nullptr; + update_source(); updateImplicitSize(); Q_EMIT clientChanged(); } @@ -289,14 +393,6 @@ void window_thumbnail_item::setClient(scripting::window* client) return; } if (m_client) { - disconnect(m_client, - &scripting::window::frameGeometryChanged, - this, - &window_thumbnail_item::invalidateOffscreenTexture); - disconnect(m_client, - &scripting::window::damaged, - this, - &window_thumbnail_item::invalidateOffscreenTexture); disconnect(m_client, &scripting::window::frameGeometryChanged, this, @@ -304,14 +400,6 @@ void window_thumbnail_item::setClient(scripting::window* client) } m_client = client; if (m_client) { - connect(m_client, - &scripting::window::frameGeometryChanged, - this, - &window_thumbnail_item::invalidateOffscreenTexture); - connect(m_client, - &scripting::window::damaged, - this, - &window_thumbnail_item::invalidateOffscreenTexture); connect(m_client, &scripting::window::frameGeometryChanged, this, @@ -320,7 +408,7 @@ void window_thumbnail_item::setClient(scripting::window* client) } else { setWId(QUuid()); } - invalidateOffscreenTexture(); + update_source(); updateImplicitSize(); Q_EMIT clientChanged(); } @@ -355,7 +443,7 @@ QRectF window_thumbnail_item::paintedRect() const if (!m_client) { return QRectF(); } - if (!m_offscreenTexture) { + if (!effects) { auto const iconSize = m_client->icon().actualSize(boundingRect().size().toSize()); return centeredSize(boundingRect(), iconSize); } @@ -379,82 +467,4 @@ QRectF window_thumbnail_item::paintedRect() const return paintedRect; } -void window_thumbnail_item::invalidateOffscreenTexture() -{ - m_dirty = true; - update(); -} - -void window_thumbnail_item::updateOffscreenTexture(effect::screen_paint_data& data) -{ - if (m_acquireFence || !m_dirty || !m_client) { - return; - } - Q_ASSERT(window()); - - auto const geometry = m_client->visibleRect(); - QSize textureSize = geometry.size(); - if (sourceSize().width() > 0) { - textureSize.setWidth(sourceSize().width()); - } - if (sourceSize().height() > 0) { - textureSize.setHeight(sourceSize().height()); - } - - m_devicePixelRatio = window()->devicePixelRatio(); - textureSize *= m_devicePixelRatio; - - if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) { - m_offscreenTexture.reset(new GLTexture(GL_RGBA8, textureSize)); - m_offscreenTexture->setFilter(GL_LINEAR); - m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE); - m_offscreenTarget.reset(new GLFramebuffer(m_offscreenTexture.data())); - } - - QMatrix4x4 view; - view.ortho(geometry.x(), - geometry.x() + geometry.width(), - geometry.y(), - geometry.y() + geometry.height(), - -1, - 1); - - QMatrix4x4 proj; - proj.scale(m_devicePixelRatio); - - auto effectWindow = effects->findWindow(m_wId); - - effect::window_paint_data win_data{ - *effectWindow, - { - .mask = Effect::PAINT_WINDOW_TRANSFORMED, - .region = infiniteRegion(), - }, - { - .targets = data.render.targets, - .view = view, - .projection = proj, - .viewport = geometry, - }, - }; - - render::push_framebuffer(win_data.render, m_offscreenTarget.data()); - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); - - // The thumbnail must be rendered using kwin's opengl context as VAOs are not - // shared across contexts. Unfortunately, this also introduces a latency of 1 - // frame, which is not ideal, but it is acceptable for things such as thumbnails. - effects->drawWindow(win_data); - render::pop_framebuffer(win_data.render); - - // The fence is needed to avoid the case where qtquick renderer starts using - // the texture while all rendering commands to it haven't completed yet. - m_dirty = false; - m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - - // We know that the texture has changed, so schedule an item update. - update(); -} - } diff --git a/como/script/window_thumbnail_item.h b/como/script/window_thumbnail_item.h index d962ca962f..e8309789a5 100644 --- a/como/script/window_thumbnail_item.h +++ b/como/script/window_thumbnail_item.h @@ -18,20 +18,53 @@ SPDX-License-Identifier: GPL-2.0-or-later namespace como { -class EffectWindow; class GLFramebuffer; class GLTexture; namespace scripting { + class ThumbnailTextureProvider; +class window_thumbnail_source : public QObject +{ + Q_OBJECT + +public: + window_thumbnail_source(QQuickWindow* view, scripting::window* handle, QUuid wId); + ~window_thumbnail_source() override; + + static std::shared_ptr + getOrCreate(QQuickWindow* window, scripting::window* handle, QUuid wId); + + struct Frame { + std::shared_ptr texture; + GLsync fence; + }; + + Frame acquire(); + +Q_SIGNALS: + void changed(); + +private: + void update(como::effect::screen_paint_data& data); + + QPointer m_view; + QPointer m_handle; + + std::shared_ptr m_offscreenTexture; + std::unique_ptr m_offscreenTarget; + GLsync m_acquireFence{nullptr}; + bool m_dirty = true; + QUuid wId; +}; + class COMO_EXPORT window_thumbnail_item : public QQuickItem { Q_OBJECT Q_PROPERTY(QUuid wId READ wId WRITE setWId NOTIFY wIdChanged) Q_PROPERTY(como::scripting::window* client READ client WRITE setClient NOTIFY clientChanged) - Q_PROPERTY(QSize sourceSize READ sourceSize WRITE setSourceSize NOTIFY sourceSizeChanged) public: explicit window_thumbnail_item(QQuickItem* parent = nullptr); @@ -43,9 +76,6 @@ class COMO_EXPORT window_thumbnail_item : public QQuickItem scripting::window* client() const; void setClient(scripting::window* window); - QSize sourceSize() const; - void setSourceSize(const QSize& sourceSize); - QSGTextureProvider* textureProvider() const override; bool isTextureProvider() const override; QSGNode* updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData*) override; @@ -56,30 +86,19 @@ class COMO_EXPORT window_thumbnail_item : public QQuickItem Q_SIGNALS: void wIdChanged(); void clientChanged(); - void sourceSizeChanged(); private: - bool use_gl_thumbnails() const; QImage fallbackImage() const; QRectF paintedRect() const; - void invalidateOffscreenTexture(); - void updateOffscreenTexture(como::effect::screen_paint_data& data); - void destroyOffscreenTexture(); void updateImplicitSize(); - void update_render_notifier(); + void update_source(); + void reset_source(); - QSize m_sourceSize; QUuid m_wId; QPointer m_client; - bool m_dirty = false; mutable ThumbnailTextureProvider* m_provider = nullptr; - QSharedPointer m_offscreenTexture; - QScopedPointer m_offscreenTarget; - GLsync m_acquireFence{nullptr}; - qreal m_devicePixelRatio = 1; - - QMetaObject::Connection render_notifier; + std::shared_ptr m_source; }; } diff --git a/como/win/cursor_shape.h b/como/win/cursor_shape.h index fd5316aacd..b578e416b3 100644 --- a/como/win/cursor_shape.h +++ b/como/win/cursor_shape.h @@ -70,6 +70,12 @@ inline std::vector cursor_shape_get_alternative_names(std::string c "cross-reverse", }, }, + { + "default", + { + "left_ptr", + }, + }, { "up_arrow", { diff --git a/como/win/tabbox/switchers/thumbnail_grid/contents/ui/main.qml b/como/win/tabbox/switchers/thumbnail_grid/contents/ui/main.qml index 1cef205053..33b3061836 100644 --- a/como/win/tabbox/switchers/thumbnail_grid/contents/ui/main.qml +++ b/como/win/tabbox/switchers/thumbnail_grid/contents/ui/main.qml @@ -15,207 +15,205 @@ import org.kde.kirigami 2.20 as Kirigami KWin.TabBoxSwitcher { id: tabBox - currentIndex: thumbnailGridView.currentIndex - - PlasmaCore.Dialog { - location: PlasmaCore.Types.Floating - visible: tabBox.visible - flags: Qt.X11BypassWindowManagerHint - x: tabBox.screenGeometry.x + tabBox.screenGeometry.width * 0.5 - dialogMainItem.width * 0.5 - y: tabBox.screenGeometry.y + tabBox.screenGeometry.height * 0.5 - dialogMainItem.height * 0.5 - - mainItem: Item { - id: dialogMainItem - - focus: true - - property int maxWidth: tabBox.screenGeometry.width * 0.9 - property int maxHeight: tabBox.screenGeometry.height * 0.7 - property real screenFactor: tabBox.screenGeometry.width / tabBox.screenGeometry.height - property int maxGridColumnsByWidth: Math.floor(maxWidth / thumbnailGridView.cellWidth) - - property int gridColumns: { // Simple greedy algorithm - // respect screenGeometry - const c = Math.min(thumbnailGridView.count, maxGridColumnsByWidth); - const residue = thumbnailGridView.count % c; - if (residue == 0) { - return c; - } - // start greedy recursion - return columnCountRecursion(c, c, c - residue); - } - - property int gridRows: Math.ceil(thumbnailGridView.count / gridColumns) - property int optimalWidth: thumbnailGridView.cellWidth * gridColumns - property int optimalHeight: thumbnailGridView.cellHeight * gridRows - width: Math.min(Math.max(thumbnailGridView.cellWidth, optimalWidth), maxWidth) - height: Math.min(Math.max(thumbnailGridView.cellHeight, optimalHeight), maxHeight) - - clip: true - - // Step for greedy algorithm - function columnCountRecursion(prevC, prevBestC, prevDiff) { - const c = prevC - 1; - - // don't increase vertical extent more than horizontal - // and don't exceed maxHeight - if (prevC * prevC <= thumbnailGridView.count + prevDiff || - maxHeight < Math.ceil(thumbnailGridView.count / c) * thumbnailGridView.cellHeight) { - return prevBestC; - } - const residue = thumbnailGridView.count % c; - // halts algorithm at some point - if (residue == 0) { - return c; + + Instantiator { + active: tabBox.visible + delegate: PlasmaCore.Dialog { + location: PlasmaCore.Types.Floating + visible: true + flags: Qt.X11BypassWindowManagerHint + x: tabBox.screenGeometry.x + tabBox.screenGeometry.width * 0.5 - dialogMainItem.width * 0.5 + y: tabBox.screenGeometry.y + tabBox.screenGeometry.height * 0.5 - dialogMainItem.height * 0.5 + + mainItem: FocusScope { + id: dialogMainItem + + focus: true + + property int maxWidth: tabBox.screenGeometry.width * 0.9 + property int maxHeight: tabBox.screenGeometry.height * 0.7 + property real screenFactor: tabBox.screenGeometry.width / tabBox.screenGeometry.height + property int maxGridColumnsByWidth: Math.floor(maxWidth / thumbnailGridView.cellWidth) + + property int gridColumns: { // Simple greedy algorithm + // respect screenGeometry + const c = Math.min(thumbnailGridView.count, maxGridColumnsByWidth); + const residue = thumbnailGridView.count % c; + if (residue == 0) { + return c; + } + // start greedy recursion + return columnCountRecursion(c, c, c - residue); } - // empty slots - const diff = c - residue; - - // compare it to previous count of empty slots - if (diff < prevDiff) { - return columnCountRecursion(c, c, diff); - } else if (diff == prevDiff) { - // when it's the same try again, we'll stop early enough thanks to the landscape mode condition + + property int gridRows: Math.ceil(thumbnailGridView.count / gridColumns) + property int optimalWidth: thumbnailGridView.cellWidth * gridColumns + property int optimalHeight: thumbnailGridView.cellHeight * gridRows + width: Math.min(Math.max(thumbnailGridView.cellWidth, optimalWidth), maxWidth) + height: Math.min(Math.max(thumbnailGridView.cellHeight, optimalHeight), maxHeight) + + clip: true + + // Step for greedy algorithm + function columnCountRecursion(prevC, prevBestC, prevDiff) { + const c = prevC - 1; + + // don't increase vertical extent more than horizontal + // and don't exceed maxHeight + if (prevC * prevC <= thumbnailGridView.count + prevDiff || + maxHeight < Math.ceil(thumbnailGridView.count / c) * thumbnailGridView.cellHeight) { + return prevBestC; + } + const residue = thumbnailGridView.count % c; + // halts algorithm at some point + if (residue == 0) { + return c; + } + // empty slots + const diff = c - residue; + + // compare it to previous count of empty slots + if (diff < prevDiff) { + return columnCountRecursion(c, c, diff); + } else if (diff == prevDiff) { + // when it's the same try again, we'll stop early enough thanks to the landscape mode condition + return columnCountRecursion(c, prevBestC, diff); + } + // when we've found a local minimum choose this one (greedy) return columnCountRecursion(c, prevBestC, diff); } - // when we've found a local minimum choose this one (greedy) - return columnCountRecursion(c, prevBestC, diff); - } - - // Just to get the margin sizes - KSvg.FrameSvgItem { - id: hoverItem - imagePath: "widgets/viewitem" - prefix: "hover" - visible: false - } - - GridView { - id: thumbnailGridView - anchors.fill: parent - focus: true - model: tabBox.model - - readonly property int iconSize: Kirigami.Units.iconSizes.huge - readonly property int captionRowHeight: Kirigami.Units.gridUnit * 2 - readonly property int columnSpacing: Kirigami.Units.gridUnit - readonly property int thumbnailWidth: Kirigami.Units.gridUnit * 16 - readonly property int thumbnailHeight: thumbnailWidth * (1.0/dialogMainItem.screenFactor) - cellWidth: hoverItem.margins.left + thumbnailWidth + hoverItem.margins.right - cellHeight: hoverItem.margins.top + captionRowHeight + thumbnailHeight + hoverItem.margins.bottom - - keyNavigationWraps: true - highlightMoveDuration: 0 - - delegate: MouseArea { - id: thumbnailGridItem - width: thumbnailGridView.cellWidth - height: thumbnailGridView.cellHeight - focus: GridView.isCurrentItem - hoverEnabled: true - - Accessible.name: model.caption - Accessible.role: Accessible.ListItem - - onClicked: { - thumbnailGridView.currentIndex = index; - } - ColumnLayout { - id: columnLayout - z: 0 - spacing: thumbnailGridView.columnSpacing - anchors.fill: parent - anchors.leftMargin: hoverItem.margins.left * 2 - anchors.topMargin: hoverItem.margins.top * 2 - anchors.rightMargin: hoverItem.margins.right * 2 - anchors.bottomMargin: hoverItem.margins.bottom * 2 - - - // KWin.WindowThumbnail needs a container - // otherwise it will be drawn the same size as the parent ColumnLayout - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - KWin.WindowThumbnail { - anchors.fill: parent - wId: windowId - } + // Just to get the margin sizes + KSvg.FrameSvgItem { + id: hoverItem + imagePath: "widgets/viewitem" + prefix: "hover" + visible: false + } - Kirigami.Icon { - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.bottom - anchors.verticalCenterOffset: Math.round(-thumbnailGridView.iconSize / 4) - width: thumbnailGridView.iconSize - height: thumbnailGridView.iconSize + GridView { + id: thumbnailGridView + anchors.fill: parent + focus: true + model: tabBox.model + currentIndex: tabBox.currentIndex + + readonly property int iconSize: Kirigami.Units.iconSizes.huge + readonly property int captionRowHeight: Kirigami.Units.gridUnit * 2 + readonly property int columnSpacing: Kirigami.Units.gridUnit + readonly property int thumbnailWidth: Kirigami.Units.gridUnit * 16 + readonly property int thumbnailHeight: thumbnailWidth * (1.0/dialogMainItem.screenFactor) + cellWidth: hoverItem.margins.left + thumbnailWidth + hoverItem.margins.right + cellHeight: hoverItem.margins.top + captionRowHeight + thumbnailHeight + hoverItem.margins.bottom + + keyNavigationWraps: true + highlightMoveDuration: 0 + + delegate: MouseArea { + id: thumbnailGridItem + width: thumbnailGridView.cellWidth + height: thumbnailGridView.cellHeight + focus: GridView.isCurrentItem + hoverEnabled: true + + Accessible.name: model.caption + Accessible.role: Accessible.ListItem + + onClicked: { + tabBox.model.activate(index); + } - source: model.icon - } + ColumnLayout { + id: columnLayout + z: 0 + spacing: thumbnailGridView.columnSpacing + anchors.fill: parent + anchors.leftMargin: hoverItem.margins.left * 2 + anchors.topMargin: hoverItem.margins.top * 2 + anchors.rightMargin: hoverItem.margins.right * 2 + anchors.bottomMargin: hoverItem.margins.bottom * 2 + + + // KWin.WindowThumbnail needs a container + // otherwise it will be drawn the same size as the parent ColumnLayout + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + KWin.WindowThumbnail { + anchors.fill: parent + wId: windowId + } + + Kirigami.Icon { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.bottom + anchors.verticalCenterOffset: Math.round(-thumbnailGridView.iconSize / 4) + width: thumbnailGridView.iconSize + height: thumbnailGridView.iconSize - PlasmaComponents3.ToolButton { - id: closeButton - anchors { - right: parent.right - top: parent.top - // Deliberately touch the inner edges of the frame - rightMargin: -columnLayout.anchors.rightMargin - topMargin: -columnLayout.anchors.topMargin + source: model.icon } - visible: model.closeable && typeof tabBox.model.close !== 'undefined' && - (thumbnailGridItem.containsMouse - || closeButton.hovered - || thumbnailGridItem.focus - || Kirigami.Settings.tabletMode - || Kirigami.Settings.hasTransientTouchInput - ) - icon.name: 'window-close-symbolic' - onClicked: { - tabBox.model.close(index); + + PlasmaComponents3.ToolButton { + id: closeButton + anchors { + right: parent.right + top: parent.top + // Deliberately touch the inner edges of the frame + rightMargin: -columnLayout.anchors.rightMargin + topMargin: -columnLayout.anchors.topMargin + } + visible: model.closeable && typeof tabBox.model.close !== 'undefined' && + (thumbnailGridItem.containsMouse + || closeButton.hovered + || thumbnailGridItem.focus + || Kirigami.Settings.tabletMode + || Kirigami.Settings.hasTransientTouchInput + ) + icon.name: 'window-close-symbolic' + onClicked: { + tabBox.model.close(index); + } } } - } - PlasmaComponents3.Label { - Layout.fillWidth: true - text: model.caption - font.weight: thumbnailGridItem.focus ? Font.Bold : Font.Normal - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - textFormat: Text.PlainText - elide: Text.ElideRight + PlasmaComponents3.Label { + Layout.fillWidth: true + text: model.caption + font.weight: thumbnailGridItem.focus ? Font.Bold : Font.Normal + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + textFormat: Text.PlainText + elide: Text.ElideRight + } } - } - } // GridView.delegate + } // GridView.delegate - highlight: KSvg.FrameSvgItem { - imagePath: "widgets/viewitem" - prefix: "hover" - } + highlight: KSvg.FrameSvgItem { + imagePath: "widgets/viewitem" + prefix: "hover" + } - Connections { - target: tabBox - function onCurrentIndexChanged() { - thumbnailGridView.currentIndex = tabBox.currentIndex; + onCurrentIndexChanged: tabBox.currentIndex = thumbnailGridView.currentIndex; + } // GridView + + Keys.onPressed: { + if (event.key == Qt.Key_Left) { + thumbnailGridView.moveCurrentIndexLeft(); + } else if (event.key == Qt.Key_Right) { + thumbnailGridView.moveCurrentIndexRight(); + } else if (event.key == Qt.Key_Up) { + thumbnailGridView.moveCurrentIndexUp(); + } else if (event.key == Qt.Key_Down) { + thumbnailGridView.moveCurrentIndexDown(); + } else { + return; } - } - } // GridView - - Keys.onPressed: { - if (event.key == Qt.Key_Left) { - thumbnailGridView.moveCurrentIndexLeft(); - } else if (event.key == Qt.Key_Right) { - thumbnailGridView.moveCurrentIndexRight(); - } else if (event.key == Qt.Key_Up) { - thumbnailGridView.moveCurrentIndexUp(); - } else if (event.key == Qt.Key_Down) { - thumbnailGridView.moveCurrentIndexDown(); - } else { - return; - } - thumbnailGridView.currentIndexChanged(thumbnailGridView.currentIndex); - } - } // Dialog.mainItem - } // Dialog + thumbnailGridView.currentIndexChanged(thumbnailGridView.currentIndex); + } + } // Dialog.mainItem + } // Dialog + } // Instantiator } diff --git a/como/win/tabbox/tabbox.h b/como/win/tabbox/tabbox.h index 69a278dd78..85221af547 100644 --- a/como/win/tabbox/tabbox.h +++ b/como/win/tabbox/tabbox.h @@ -352,7 +352,7 @@ class tabbox key( s_windowsRev, [this] { slot_walk_back_through_windows(); }, - Qt::ALT | Qt::SHIFT | Qt::Key_Backtab); + Qt::ALT | Qt::SHIFT | Qt::Key_Tab); key( s_app, [this] { slot_walk_through_current_app_windows(); }, @@ -435,15 +435,17 @@ class tabbox return Steady; // Before testing the unshifted key (Ctrl+A vs. Ctrl+Shift+a etc.), see whether this is - // +Shift+Tab and check that against +Shift+Backtab (as well) + // +Shift+Tab/Backtab and test that against +Shift+Backtab/Tab as well. Qt::KeyboardModifiers mods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier | Qt::GroupSwitchModifier; mods &= keyQt; - if ((keyQt & ~mods) == Qt::Key_Tab) { - if (contains(forward, QKeyCombination(mods, Qt::Key_Backtab).toCombined())) { + if (((keyQt & ~mods) == Qt::Key_Tab) || ((keyQt & ~mods) == Qt::Key_Backtab)) { + if (contains(forward, QKeyCombination(mods, Qt::Key_Backtab).toCombined()) + || contains(forward, QKeyCombination(mods, Qt::Key_Tab))) { return Forward; } - if (contains(backward, QKeyCombination(mods, Qt::Key_Backtab).toCombined())) { + if (contains(backward, QKeyCombination(mods, Qt::Key_Backtab).toCombined()) + || contains(backward, QKeyCombination(mods, Qt::Key_Tab))) { return Backward; } } diff --git a/como/win/user_actions_menu.h b/como/win/user_actions_menu.h index 9783b22e05..c6471c4203 100644 --- a/como/win/user_actions_menu.h +++ b/como/win/user_actions_menu.h @@ -281,16 +281,9 @@ class user_actions_menu } m_desktopMenu->addSeparator(); - const uint BASE = 10; - for (uint i = 1; i <= subs_manager->subspaces.size(); ++i) { - QString basic_name(QStringLiteral("%1 %2")); - if (i < BASE) { - basic_name.prepend(QLatin1Char('&')); - } - action = m_desktopMenu->addAction( - basic_name.arg(i).arg(subspace_manager_get_subspace_name(*subs_manager, i) - .replace(QLatin1Char('&'), QStringLiteral("&&")))); + action = m_desktopMenu->addAction(subspace_manager_get_subspace_name(*subs_manager, i) + .replace(QLatin1Char('&'), QStringLiteral("&&"))); action->setData(i); action->setCheckable(true); group->addAction(action); @@ -343,17 +336,10 @@ class user_actions_menu m_multipleDesktopsMenu->addSeparator(); - const uint BASE = 10; - for (uint i = 1; i <= subs_manager->subspaces.size(); ++i) { - QString basic_name(QStringLiteral("%1 %2")); - if (i < BASE) { - basic_name.prepend(QLatin1Char('&')); - } - - QAction* action = m_multipleDesktopsMenu->addAction( - basic_name.arg(i).arg(subspace_manager_get_subspace_name(*subs_manager, i) - .replace(QLatin1Char('&'), QStringLiteral("&&")))); + auto action = m_multipleDesktopsMenu->addAction( + subspace_manager_get_subspace_name(*subs_manager, i) + .replace(QLatin1Char('&'), QStringLiteral("&&"))); action->setData(QVariant::fromValue(user_actions_menu_desktop_action_data{i, false})); action->setCheckable(true); if (m_client diff --git a/como/win/wayland/window.h b/como/win/wayland/window.h index c2c11b18af..31a8ec8f09 100644 --- a/como/win/wayland/window.h +++ b/como/win/wayland/window.h @@ -896,7 +896,9 @@ class window bool isCloseable() const { - return toplevel && window_type != win_type::desktop && window_type != win_type::dock; + return toplevel + && this->control->rules.checkCloseable(window_type != win_type::desktop + && window_type != win_type::dock); } bool isMaximizable() const diff --git a/como/win/x11/event.h b/como/win/x11/event.h index e835a105ed..c800988789 100644 --- a/como/win/x11/event.h +++ b/como/win/x11/event.h @@ -500,7 +500,8 @@ bool button_press_event(Win* win, QPoint(x, y), QPoint(x_root, y_root), base::x11::xcb::to_qt_mouse_button(button), - base::x11::xcb::to_qt_mouse_buttons(state), + base::x11::xcb::to_qt_mouse_buttons(state) + | base::x11::xcb::to_qt_mouse_buttons(button), Qt::KeyboardModifiers()); return win::process_decoration_button_press(win, &ev, true); } @@ -535,7 +536,8 @@ bool button_press_event(Win* win, QPointF(x, y), QPointF(x_root, y_root), base::x11::xcb::to_qt_mouse_button(button), - base::x11::xcb::to_qt_mouse_buttons(state), + base::x11::xcb::to_qt_mouse_buttons(state) + | base::x11::xcb::to_qt_mouse_buttons(button), key_server::to_qt_keyboard_modifiers(state)); event.setAccepted(false); QCoreApplication::sendEvent(win::decoration(win), &event); @@ -751,7 +753,11 @@ void focus_out_event(Win* win, xcb_focus_out_event_t* e) // performs _NET_WM_MOVERESIZE template -void net_move_resize(Win* win, int x_root, int y_root, net::Direction direction) +void net_move_resize(Win* win, + int x_root, + int y_root, + net::Direction direction, + xcb_button_t /*button*/) { auto& mov_res = win->control->move_resize; auto& cursor = win->space.input->cursor; diff --git a/como/win/x11/net/root_info.cpp b/como/win/x11/net/root_info.cpp index 4397d0d2c1..7d4917f96d 100644 --- a/como/win/x11/net/root_info.cpp +++ b/como/win/x11/net/root_info.cpp @@ -1359,9 +1359,18 @@ void root_info::closeWindowRequest(xcb_window_t window) p->conn, netwm_sendevent_mask, p->root, window, p->atom(_NET_CLOSE_WINDOW), data); } -void root_info::moveResizeRequest(xcb_window_t window, int x_root, int y_root, Direction direction) -{ - uint32_t const data[5] = {uint32_t(x_root), uint32_t(y_root), uint32_t(direction), 0, 0}; +void root_info::moveResizeRequest(xcb_window_t window, + int x_root, + int y_root, + Direction direction, + xcb_button_t button, + RequestSource source) +{ + uint32_t const data[5] = {uint32_t(x_root), + uint32_t(y_root), + uint32_t(direction), + uint32_t(button), + uint32_t(source)}; send_client_message( p->conn, netwm_sendevent_mask, p->root, window, p->atom(_NET_WM_MOVERESIZE), data); @@ -1485,7 +1494,9 @@ void root_info::event(xcb_generic_event_t* event, moveResize(message->window, message->data.data32[0], message->data.data32[1], - message->data.data32[2]); + message->data.data32[2], + message->data.data32[3], + RequestSource(message->data.data32[4])); } else if (message->type == p->atom(_NET_MOVERESIZE_WINDOW)) { moveResizeWindow(message->window, message->data.data32[0], diff --git a/como/win/x11/net/root_info.h b/como/win/x11/net/root_info.h index 8f6101ed15..9397f4973a 100644 --- a/como/win/x11/net/root_info.h +++ b/como/win/x11/net/root_info.h @@ -129,7 +129,12 @@ class COMO_EXPORT root_info const root_info& operator=(const root_info& rootinfo); void closeWindowRequest(xcb_window_t window); - void moveResizeRequest(xcb_window_t window, int x_root, int y_root, Direction direction); + void moveResizeRequest(xcb_window_t window, + int x_root, + int y_root, + Direction direction, + xcb_button_t button = XCB_BUTTON_INDEX_ANY, + net::RequestSource source = net::RequestSource::FromUnknown); void moveResizeWindowRequest(xcb_window_t window, int flags, int x, int y, int width, int height); void showWindowMenuRequest(xcb_window_t window, int device_id, int x_root, int y_root); @@ -183,12 +188,19 @@ class COMO_EXPORT root_info Q_UNUSED(window); } - virtual void moveResize(xcb_window_t window, int x_root, int y_root, unsigned long direction) + virtual void moveResize(xcb_window_t window, + int x_root, + int y_root, + unsigned long direction, + xcb_button_t button, + RequestSource source) { Q_UNUSED(window); Q_UNUSED(x_root); Q_UNUSED(y_root); Q_UNUSED(direction); + Q_UNUSED(button); + Q_UNUSED(source); } virtual void gotPing(xcb_window_t window, xcb_timestamp_t timestamp) diff --git a/como/win/x11/netinfo.h b/como/win/x11/netinfo.h index 3115a913a2..b29db44f34 100644 --- a/como/win/x11/netinfo.h +++ b/como/win/x11/netinfo.h @@ -243,12 +243,18 @@ class root_info : public net::root_info } } - void moveResize(xcb_window_t w, int x_root, int y_root, unsigned long direction) override + void moveResize(xcb_window_t w, + int x_root, + int y_root, + unsigned long direction, + xcb_button_t button, + net::RequestSource /*source*/) override { if (auto win = find_controlled_window(space, predicate_match::window, w)) { // otherwise grabbing may have old timestamp - this message should include timestamp base::x11::update_time_from_clock(space.base); - x11::net_move_resize(win, x_root, y_root, static_cast(direction)); + x11::net_move_resize( + win, x_root, y_root, static_cast(direction), button); } } diff --git a/kconf_update/CMakeLists.txt b/kconf_update/CMakeLists.txt index 7cae94248c..a43f96f790 100644 --- a/kconf_update/CMakeLists.txt +++ b/kconf_update/CMakeLists.txt @@ -1,7 +1,15 @@ # SPDX-FileCopyrightText: 2023 Niccolò Venerandi # SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only +add_executable(kwin-6.0-delete-desktop-switching-shortcuts) +target_sources(kwin-6.0-delete-desktop-switching-shortcuts PRIVATE kwin-6.0-delete-desktop-switching-shortcuts.cpp) +target_link_libraries(kwin-6.0-delete-desktop-switching-shortcuts PRIVATE KF6::GlobalAccel) +install(TARGETS kwin-6.0-delete-desktop-switching-shortcuts DESTINATION ${KDE_INSTALL_LIBDIR}/kconf_update_bin/) + +add_executable(kwin-6.0-reset-active-mouse-screen) +target_sources(kwin-6.0-reset-active-mouse-screen PRIVATE kwin-6.0-reset-active-mouse-screen.cpp) +target_link_libraries(kwin-6.0-reset-active-mouse-screen PRIVATE KF6::ConfigCore) +install(TARGETS kwin-6.0-reset-active-mouse-screen DESTINATION ${KDE_INSTALL_LIBDIR}/kconf_update_bin/) + install(FILES kwin.upd DESTINATION ${KDE_INSTALL_KCONFUPDATEDIR}) -install(PROGRAMS kwin-6.0-overview-activities-shortcuts.py - DESTINATION ${KDE_INSTALL_KCONFUPDATEDIR}) diff --git a/kconf_update/kwin-6.0-delete-desktop-switching-shortcuts.cpp b/kconf_update/kwin-6.0-delete-desktop-switching-shortcuts.cpp new file mode 100644 index 0000000000..5acff0ed5e --- /dev/null +++ b/kconf_update/kwin-6.0-delete-desktop-switching-shortcuts.cpp @@ -0,0 +1,34 @@ +/* + SPDX-FileCopyrightText: 2023 Marco Martin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include +#include +#include + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + const QStringList actionNames{ + QStringLiteral("Walk Through Desktops"), + QStringLiteral("Walk Through Desktops (Reverse)"), + QStringLiteral("Walk Through Desktop List"), + QStringLiteral("Walk Through Desktop List (Reverse)"), + }; + + for (const QString &actionName : actionNames) { + QAction action; + action.setObjectName(actionName); + action.setProperty("componentName", QStringLiteral("kwin")); + action.setProperty("componentDisplayName", QStringLiteral("KWin")); + KGlobalAccel::self()->setShortcut(&action, {QKeySequence()}, KGlobalAccel::NoAutoloading); + KGlobalAccel::self()->removeAllShortcuts(&action); + } + + return 0; +} diff --git a/kconf_update/kwin-6.0-overview-activities-shortcuts.py b/kconf_update/kwin-6.0-overview-activities-shortcuts.py deleted file mode 100644 index e350a4be93..0000000000 --- a/kconf_update/kwin-6.0-overview-activities-shortcuts.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: 2023 Niccolò Venerandi -# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only - -import fileinput - -for line in fileinput.input(): - if line.startswith('next activity'): - print(line.replace('Meta+Tab', 'Meta+A')) - elif line.startswith('previous activity'): - print(line.replace('Meta+Shift+Tab', 'Meta+Shift+A')) - elif line.startswith('ShowDesktopGrid'): - pass - elif line.startswith('Overview'): - print('Overview=Meta+W,Meta+W,Toggle Overview') - print('Cycle Overview=Meta+Tab,Meta+Tab,Cycle through Overview and Grid View') - print('Cycle Overview Opposite=Meta+Shift+Tab,Meta+Shift+Tab,Cycle through Grid View and Overview') - print('Grid View=Meta+G,Meta+G,Toggle Grid View') - else: - print(line) diff --git a/kconf_update/kwin-6.0-reset-active-mouse-screen.cpp b/kconf_update/kwin-6.0-reset-active-mouse-screen.cpp new file mode 100644 index 0000000000..0efcc1dd60 --- /dev/null +++ b/kconf_update/kwin-6.0-reset-active-mouse-screen.cpp @@ -0,0 +1,26 @@ +/* + SPDX-FileCopyrightText: 2024 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include +#include + +int main() +{ + auto config = KSharedConfig::openConfig(QStringLiteral("kwinrc")); + + KConfigGroup windows = config->group(QStringLiteral("Windows")); + if (!windows.exists()) { + return EXIT_SUCCESS; + } + + if (!windows.hasKey(QStringLiteral("ActiveMouseScreen"))) { + return EXIT_SUCCESS; + } + + windows.deleteEntry(QStringLiteral("ActiveMouseScreen")); + + return windows.sync() ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/kconf_update/kwin.upd b/kconf_update/kwin.upd index e726be9cb8..d3ef84271c 100644 --- a/kconf_update/kwin.upd +++ b/kconf_update/kwin.upd @@ -1,11 +1,12 @@ # SPDX-FileCopyrightText: 2023 Niccolò Venerandi # SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only -Version=5 +Version=6 -# Changes the default Activities shortcut from Meta+Tab to Meta+A, -# so that the Overview can take its place -Id=change-activities-overview-shortcuts -File=kglobalshortcutsrc -Group=plasmashell,kwin -Script=kwin-6.0-overview-activities-shortcuts.py,python3 +# Reset ActiveMouseScreen config option. +Id=kwin-6.0-reset-active-mouse-screen +Script=kwin-6.0-reset-active-mouse-screen + +# Delete old desktop switching shortcuts. +Id=kwin-6.0-delete-desktop-switching-shortcuts +Script=kwin-6.0-delete-desktop-switching-shortcuts diff --git a/plugins/effects/fallapart/fallapart.cpp b/plugins/effects/fallapart/fallapart.cpp index 5e38c88411..c0ca56c5d6 100644 --- a/plugins/effects/fallapart/fallapart.cpp +++ b/plugins/effects/fallapart/fallapart.cpp @@ -13,7 +13,9 @@ SPDX-License-Identifier: GPL-2.0-or-later #include #include +#include #include + #include Q_LOGGING_CATEGORY(KWIN_FALLAPART, "kwin_effect_fallapart", QtWarningMsg) @@ -76,7 +78,8 @@ void FallApartEffect::apply(effect::window_paint_data& data, WindowQuadList& qua { auto animationIt = windows.constFind(&data.window); if (animationIt != windows.constEnd() && isRealWindow(&data.window)) { - const qreal t = animationIt->progress; + QEasingCurve easing(QEasingCurve::InCubic); + auto const t = easing.valueForProgress(animationIt->progress); // Request the window to be divided into cells quads = quads.makeGrid(blockSize); @@ -101,8 +104,7 @@ void FallApartEffect::apply(effect::window_paint_data& data, WindowQuadList& qua if (p1.y() > data.window.height() / 2) { ydiff = (p1.y() - data.window.height() / 2) / data.window.height() * 100; } - - double modif = t * t * 64; + double modif = t * 64; srandom(cnt); // change direction randomly but consistently xdiff += (rand() % 21 - 10); ydiff += (rand() % 21 - 10); diff --git a/plugins/effects/fullscreen/package/contents/code/main.js b/plugins/effects/fullscreen/package/contents/code/main.js index 7baae3d771..25099fb42d 100644 --- a/plugins/effects/fullscreen/package/contents/code/main.js +++ b/plugins/effects/fullscreen/package/contents/code/main.js @@ -41,33 +41,55 @@ class FullScreenEffect { oldGeometry = window.olderGeometry; window.olderGeometry = Object.assign({}, window.oldGeometry); window.oldGeometry = Object.assign({}, newGeometry); - window.fullScreenAnimation1 = animate({ - window: window, - duration: this.duration, - animations: [{ - type: Effect.Size, - to: { + + let couldRetarget = false; + if (window.fullScreenAnimation1) { + if (window.fullScreenAnimation1[0]) { + couldRetarget = retarget(window.fullScreenAnimation1[0], { value1: newGeometry.width, value2: newGeometry.height - }, - from: { - value1: oldGeometry.width, - value2: oldGeometry.height - }, - curve: QEasingCurve.OutCubic - }, { - type: Effect.Translation, - to: { - value1: 0, - value2: 0 - }, - from: { - value1: oldGeometry.x - newGeometry.x - (newGeometry.width / 2 - oldGeometry.width / 2), - value2: oldGeometry.y - newGeometry.y - (newGeometry.height / 2 - oldGeometry.height / 2) - }, - curve: QEasingCurve.OutCubic - }] - }); + }, this.duration); + } + if (window.fullScreenAnimation1[1]) { + couldRetarget = retarget(window.fullScreenAnimation1[1], { + value1: newGeometry.x + newGeometry.width / 2, + value2: newGeometry.y + newGeometry.height / 2 + }, this.duration); + } + } + if (!couldRetarget) { + if (window.fullScreenAnimation1) { + cancel(window.fullScreenAnimation1); + delete window.fullScreenAnimation1; + } + window.fullScreenAnimation1 = animate({ + window: window, + duration: this.duration, + animations: [{ + type: Effect.Size, + to: { + value1: newGeometry.width, + value2: newGeometry.height + }, + from: { + value1: oldGeometry.width, + value2: oldGeometry.height + }, + curve: QEasingCurve.OutCubic + }, { + type: Effect.Position, + to: { + value1: newGeometry.x + newGeometry.width / 2, + value2: newGeometry.y + newGeometry.height / 2 + }, + from: { + value1: oldGeometry.x + oldGeometry.width / 2, + value2: oldGeometry.y + oldGeometry.height / 2 + }, + curve: QEasingCurve.OutCubic + }] + }); + } if (!window.resize) { window.fullScreenAnimation2 =animate({ window: window, @@ -90,8 +112,6 @@ class FullScreenEffect { if (window.fullScreenAnimation1) { if (window.geometry.width != window.oldGeometry.width || window.geometry.height != window.oldGeometry.height) { - cancel(window.fullScreenAnimation1); - delete window.fullScreenAnimation1; if (window.fullScreenAnimation2) { cancel(window.fullScreenAnimation2); delete window.fullScreenAnimation2; diff --git a/plugins/effects/magiclamp/magiclamp_config.ui b/plugins/effects/magiclamp/magiclamp_config.ui index a142acb33d..d73c582001 100644 --- a/plugins/effects/magiclamp/magiclamp_config.ui +++ b/plugins/effects/magiclamp/magiclamp_config.ui @@ -43,7 +43,7 @@ SPDX-License-Identifier: GPL-2.0-or-later Default - milliseconds + milliseconds 5000 diff --git a/plugins/effects/magnifier/magnifier.cpp b/plugins/effects/magnifier/magnifier.cpp index 31de2d2a51..a3353d9bf7 100644 --- a/plugins/effects/magnifier/magnifier.cpp +++ b/plugins/effects/magnifier/magnifier.cpp @@ -221,7 +221,7 @@ void MagnifierEffect::zoomIn() if (effects->isOpenGLCompositing() && !m_texture) { effects->makeOpenGLContextCurrent(); m_texture = std::make_unique( - GL_RGBA8, m_magnifierSize.width(), m_magnifierSize.height()); + GL_RGBA16F, m_magnifierSize.width(), m_magnifierSize.height()); m_texture->set_content_transform(effect::transform_type::normal); m_fbo = std::make_unique(m_texture.get()); } @@ -261,7 +261,7 @@ void MagnifierEffect::toggle() if (effects->isOpenGLCompositing() && !m_texture) { effects->makeOpenGLContextCurrent(); m_texture = std::make_unique( - GL_RGBA8, m_magnifierSize.width(), m_magnifierSize.height()); + GL_RGBA16F, m_magnifierSize.width(), m_magnifierSize.height()); m_texture->set_content_transform(effect::transform_type::normal); m_fbo = std::make_unique(m_texture.get()); } diff --git a/plugins/effects/mouseclick/mouseclick.cpp b/plugins/effects/mouseclick/mouseclick.cpp index 46b9aae574..0a8bd2150d 100644 --- a/plugins/effects/mouseclick/mouseclick.cpp +++ b/plugins/effects/mouseclick/mouseclick.cpp @@ -200,8 +200,7 @@ void MouseClickEffect::repaint() dirtyRegion |= QRect( click->m_pos.x() - radius, click->m_pos.y() - radius, 2 * radius, 2 * radius); if (click->m_frame) { - // we grant the plasma style 32px padding for stuff like shadows... - dirtyRegion |= click->m_frame->geometry().adjusted(-32, -32, 32, 32); + dirtyRegion |= click->m_frame->geometry(); } } effects->addRepaint(dirtyRegion); diff --git a/plugins/effects/overview/kcm/overvieweffectkcm.cpp b/plugins/effects/overview/kcm/overvieweffectkcm.cpp index d2e59f8d61..8f703ba8d6 100644 --- a/plugins/effects/overview/kcm/overvieweffectkcm.cpp +++ b/plugins/effects/overview/kcm/overvieweffectkcm.cpp @@ -35,22 +35,20 @@ OverviewEffectConfig::OverviewEffectConfig(QObject* parent, const KPluginMetaDat actionCollection->setConfigGroup(QStringLiteral("Overview")); actionCollection->setConfigGlobal(true); - const QKeySequence defaultCycleShortcut = Qt::META | Qt::Key_Tab; QAction* cycleAction = actionCollection->addAction(QStringLiteral("Cycle Overview")); cycleAction->setText(i18nc("@action Overview and Grid View are the name of KWin effects", "Cycle through Overview and Grid View")); cycleAction->setProperty("isConfigurationAction", true); - KGlobalAccel::self()->setDefaultShortcut(cycleAction, {defaultCycleShortcut}); - KGlobalAccel::self()->setShortcut(cycleAction, {defaultCycleShortcut}); + KGlobalAccel::self()->setDefaultShortcut(cycleAction, {}); + KGlobalAccel::self()->setShortcut(cycleAction, {}); - const QKeySequence defaultUncycleShortcut = Qt::META | Qt::SHIFT | Qt::Key_Tab; QAction* reverseCycleAction = actionCollection->addAction(QStringLiteral("Cycle Overview Opposite")); reverseCycleAction->setText(i18nc("@action Grid View and Overview are the name of KWin effects", "Cycle through Grid View and Overview")); reverseCycleAction->setProperty("isConfigurationAction", true); - KGlobalAccel::self()->setDefaultShortcut(reverseCycleAction, {defaultUncycleShortcut}); - KGlobalAccel::self()->setShortcut(reverseCycleAction, {defaultUncycleShortcut}); + KGlobalAccel::self()->setDefaultShortcut(reverseCycleAction, {}); + KGlobalAccel::self()->setShortcut(reverseCycleAction, {}); const QKeySequence defaultOverviewShortcut = Qt::META | Qt::Key_W; QAction* overviewAction = actionCollection->addAction(QStringLiteral("Overview")); diff --git a/plugins/effects/overview/metadata.json b/plugins/effects/overview/metadata.json index 45b2d37488..22382f927c 100644 --- a/plugins/effects/overview/metadata.json +++ b/plugins/effects/overview/metadata.json @@ -7,7 +7,6 @@ "Name": "Overview" }, "X-KDE-ConfigModule": "kwin_overview_config", - "X-KWin-Border-Activate": true, "org.kde.kwin.effect": { "video": "https://files.kde.org/plasma/kwin/effect-videos/present_windows.mp4" } diff --git a/plugins/effects/overview/overviewconfig.kcfg b/plugins/effects/overview/overviewconfig.kcfg index 13be0341b1..db6bebbdb6 100644 --- a/plugins/effects/overview/overviewconfig.kcfg +++ b/plugins/effects/overview/overviewconfig.kcfg @@ -23,7 +23,9 @@ true - + + ElectricTopLeft + diff --git a/plugins/effects/overview/overviewconfig.kcfgc b/plugins/effects/overview/overviewconfig.kcfgc index 120a52b48f..fd1666b146 100644 --- a/plugins/effects/overview/overviewconfig.kcfgc +++ b/plugins/effects/overview/overviewconfig.kcfgc @@ -7,3 +7,4 @@ ClassName=OverviewConfig NameSpace=como Singleton=true Mutators=true +IncludeFiles=como/render/effect/interface/types.h diff --git a/plugins/effects/overview/overvieweffect.cpp b/plugins/effects/overview/overvieweffect.cpp index 631f9a5f5b..9e6197a273 100644 --- a/plugins/effects/overview/overvieweffect.cpp +++ b/plugins/effects/overview/overvieweffect.cpp @@ -156,24 +156,22 @@ OverviewEffect::OverviewEffect() m_shutdownTimer->setSingleShot(true); connect(m_shutdownTimer, &QTimer::timeout, this, &OverviewEffect::realDeactivate); - const QKeySequence defaultCycleShortcut = Qt::META | Qt::Key_Tab; auto cycleAction = new QAction(this); connect(cycleAction, &QAction::triggered, this, &OverviewEffect::cycle); cycleAction->setObjectName(QStringLiteral("Cycle Overview")); cycleAction->setText(i18nc("@action Grid View and Overview are the name of KWin effects", "Cycle through Overview and Grid View")); - KGlobalAccel::self()->setDefaultShortcut(cycleAction, {defaultCycleShortcut}); - KGlobalAccel::self()->setShortcut(cycleAction, {defaultCycleShortcut}); + KGlobalAccel::self()->setDefaultShortcut(cycleAction, {}); + KGlobalAccel::self()->setShortcut(cycleAction, {}); m_cycleShortcut = KGlobalAccel::self()->shortcut(cycleAction); - const QKeySequence defaultUncycleShortcut = Qt::META | Qt::SHIFT | Qt::Key_Tab; auto reverseCycleAction = new QAction(this); connect(reverseCycleAction, &QAction::triggered, this, &OverviewEffect::reverseCycle); reverseCycleAction->setObjectName(QStringLiteral("Cycle Overview Opposite")); reverseCycleAction->setText(i18nc("@action Grid View and Overview are the name of KWin effects", "Cycle through Grid View and Overview")); - KGlobalAccel::self()->setDefaultShortcut(reverseCycleAction, {defaultUncycleShortcut}); - KGlobalAccel::self()->setShortcut(reverseCycleAction, {defaultUncycleShortcut}); + KGlobalAccel::self()->setDefaultShortcut(reverseCycleAction, {}); + KGlobalAccel::self()->setShortcut(reverseCycleAction, {}); m_reverseCycleShortcut = KGlobalAccel::self()->shortcut(reverseCycleAction); const QKeySequence defaultOverviewShortcut = Qt::META | Qt::Key_W; @@ -199,7 +197,15 @@ OverviewEffect::OverviewEffect() OverviewConfig::instance(effects->config()); reconfigure(ReconfigureAll); - setSource(QUrl(QStringLiteral("qrc:/overview/qml/main.qml"))); + auto delegate = new QQmlComponent(effects->qmlEngine()); + connect(delegate, &QQmlComponent::statusChanged, this, [delegate]() { + if (delegate->isError()) { + qWarning() << "Failed to load overview:" << delegate->errorString(); + } + }); + delegate->loadUrl(QUrl(QStringLiteral("qrc:/overview/qml/main.qml")), + QQmlComponent::Asynchronous); + setDelegate(delegate); } OverviewEffect::~OverviewEffect() @@ -320,7 +326,7 @@ int OverviewEffect::requestedEffectChainPosition() const bool OverviewEffect::borderActivated(ElectricBorder border) { if (m_borderActivate.contains(border)) { - cycle(); + m_overviewState->toggle(); return true; } return false; diff --git a/plugins/effects/overview/qml/main.qml b/plugins/effects/overview/qml/main.qml index accf7d144a..fcc02606cf 100644 --- a/plugins/effects/overview/qml/main.qml +++ b/plugins/effects/overview/qml/main.qml @@ -118,7 +118,7 @@ FocusScope { transitions: Transition { to: "initial, grid, overview" NumberAnimation { - duration: Kirigami.Units.shortDuration + duration: effect.animationDuration properties: "gridVal, overviewVal" easing.type: Easing.OutCubic } @@ -242,14 +242,21 @@ FocusScope { } Keys.priority: Keys.AfterItem - KWinComponents.DesktopBackground { - id: backgroundItem - activity: KWinComponents.Workspace.currentActivity - desktop: KWinComponents.Workspace.currentDesktop - outputName: targetScreen.name - - layer.enabled: true - layer.effect: FastBlur {radius: 64} + Item { + width: backgroundItem.width + height: backgroundItem.height + KWinComponents.DesktopBackground { + id: backgroundItem + activity: KWinComponents.Workspace.currentActivity + desktop: KWinComponents.Workspace.currentDesktop + outputName: targetScreen.name + visible: false + } + FastBlur { + anchors.fill: parent + source: backgroundItem + radius: 64 + } } Rectangle { @@ -402,10 +409,14 @@ FocusScope { property Item currentHeap property Item currentBackgroundItem - Kirigami.ShadowedRectangle { + Item { id: mainBackground - color: Kirigami.Theme.highlightColor + + // visible when in the overview 'grid' mode, but also keep neighbouring items visible as they can appear during gestures visible: gridVal > 0 || nearCurrent + // Avoid handling drops for offscreen visible items + enabled: gridVal > 0 || current + anchors.fill: parent property bool shouldBeVisibleInOverview: !(effect.searchText.length > 0 && current) || (heap.count !== 0 && effect.filterWindows) opacity: 1 - overviewVal * (shouldBeVisibleInOverview ? 0 : 1) @@ -424,13 +435,6 @@ FocusScope { z: dragActive ? 1 : 0 readonly property bool dragActive: heap.dragActive || dragHandler.active - shadow { - size: Kirigami.Units.gridUnit * 2 - color: Qt.rgba(0, 0, 0, 0.3) - yOffset: 3 - } - radius: Kirigami.Units.largeSpacing * 2 * (overviewVal + gridVal * 2) - property int gridSize: Math.max(rows, columns) property real row: (index - column) / columns property real column: index % columns @@ -492,14 +496,6 @@ FocusScope { x: column * (width / columns) * gridVal y: row * (height / rows) * gridVal }, - // Scales down the preview slighly when in Overview mode - Scale { - origin.x: width / 2 - origin.y: height / 2 - property real scale: Math.min(maxWidth / width, maxHeight / height) - xScale: 1 + (scale - 1) * overviewVal - yScale:1 + (scale - 1) * overviewVal - }, // Initially places transition desktops in a grid around the current one, // and moves them slighly to avoid overlapping the UI Translate { @@ -508,91 +504,100 @@ FocusScope { } ] - KWinComponents.DesktopBackground { - id: desktopElement - anchors.fill: parent - anchors.margins: gridVal !== 0 ? Math.round(mainBackground.current * gridVal * (1.5 / gridScale.xScale)) : 0 - activity: KWinComponents.Workspace.currentActivity - desktop: KWinComponents.Workspace.currentDesktop - outputName: targetScreen.name - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - anchors.centerIn: parent - width: desktopElement.width - height: desktopElement.height - radius: mainBackground.radius + Item { + id: backgroundArea + property real sizeAdjust: Math.min(maxWidth / parent.width, maxHeight / parent.height) + width: parent.width * (1 + (sizeAdjust - 1) * overviewVal) + height: parent.height * (1 + (sizeAdjust - 1) * overviewVal) + x: parent.width / 2 - width / 2 + y: parent.height / 2 - height / 2 + KWinComponents.DesktopBackground { + id: desktopElement + anchors.fill: parent + anchors.margins: gridVal !== 0 ? Math.round(mainBackground.current * gridVal * (1.5 / gridScale.xScale)) : 0 + activity: KWinComponents.Workspace.currentActivity + desktop: KWinComponents.Workspace.currentDesktop + outputName: targetScreen.name + visible: false + } + + Kirigami.ShadowedTexture { + anchors.fill: parent + + color: Kirigami.Theme.highlightColor + source: desktopElement + + radius: Kirigami.Units.largeSpacing * 2 * (overviewVal + gridVal * 2) + + shadow { + size: Kirigami.Units.gridUnit * 2 + color: Qt.rgba(0, 0, 0, 0.3) + yOffset: 3 } } - } - DropArea { - anchors.fill: parent - onEntered: (drag) => { - drag.accepted = true; + DropArea { + anchors.fill: parent + onEntered: (drag) => { + drag.accepted = true; + } + onDropped: (drop) => { + drop.accepted = true; + if (drop.keys.includes("kwin-desktop")) { + // dragging a desktop as a whole + if (drag.source === mainBackground) { + drop.action = Qt.IgnoreAction; + return; + } + effect.swapDesktops(drag.source.desktop, desktop); + } else { + // dragging a KWin::Window + if (drag.source.desktops.length === 0 || drag.source.desktops.indexOf(mainBackground.desktop) !== -1) { + drop.action = Qt.IgnoreAction; + return; + } + drag.source.desktops = [mainBackground.desktop]; + } + } } - onDropped: (drop) => { - drop.accepted = true; - if (drag.source instanceof Kirigami.ShadowedRectangle) { - // dragging a desktop as a whole - if (drag.source === mainBackground) { - drop.action = Qt.IgnoreAction; + Connections { + target: effect + function onItemDroppedOutOfScreen(globalPos, item, screen) { + if (screen !== targetScreen) { return; } - effect.swapDesktops(drag.source.desktop, desktop); - } else { - // dragging a KWin::Window - if (drag.source.desktops.length === 0 || drag.source.desktops.indexOf(mainBackground.desktop) !== -1) { - drop.action = Qt.IgnoreAction; + const pos = screen.mapFromGlobal(globalPos); + if (!mainBackground.contains(mainBackground.mapFromItem(null, pos.x, pos.y))) { return; } - drag.source.desktops = [mainBackground.desktop]; - } - } - } - Connections { - target: effect - function onItemDroppedOutOfScreen(globalPos, item, screen) { - if (screen !== targetScreen) { - return; - } - const pos = screen.mapFromGlobal(globalPos); - if (!mainBackground.contains(mainBackground.mapFromItem(null, pos.x, pos.y))) { - return; + // moving the window to the client screen is handled by WindowHeap's similiarly named function that is also run + item.client.desktops = [mainBackground.desktop]; } - item.client.desktops = [mainBackground.desktop]; } - } - DragHandler { - id: dragHandler - target: heap - enabled: gridVal !== 0 - grabPermissions: PointerHandler.ApprovesTakeOverByHandlersOfSameType - onActiveChanged: { - if (!active) { - heap.Drag.drop(); - Qt.callLater(heap.resetPosition) + DragHandler { + id: dragHandler + target: heap + enabled: gridVal !== 0 + grabPermissions: PointerHandler.ApprovesTakeOverByHandlersOfSameType + onActiveChanged: { + if (!active) { + heap.Drag.drop(); + Qt.callLater(heap.resetPosition) + } } } } - MouseArea { - anchors.fill: heap - acceptedButtons: Qt.NoButton - cursorShape: dragHandler.active ? Qt.ClosedHandCursor : Qt.ArrowCursor - } - WindowHeap { id: heap - width: parent.width - height: parent.height - x: 0 - y: 0 + width: parent.width * (1 + (backgroundArea.sizeAdjust - 1)) + height: parent.height * (1 + (backgroundArea.sizeAdjust - 1)) + x: parent.width / 2 - width / 2 + y: parent.height / 2 - height / 2 function resetPosition() { - x = 0; - y = 0; + x = parent.width / 2 - width / 2; + y = parent.height / 2 - height / 2; } z: 9999 Drag.active: dragHandler.active @@ -600,6 +605,7 @@ FocusScope { Drag.supportedActions: Qt.MoveAction Drag.source: mainBackground Drag.hotSpot: Qt.point(width * 0.5, height * 0.5) + Drag.keys: ["kwin-desktop"] layout.mode: effect.layout focus: current @@ -667,6 +673,14 @@ FocusScope { } } } + + MouseArea { + anchors.fill: parent + z: -1 + acceptedButtons: Qt.NoButton + cursorShape: dragHandler.active ? Qt.ClosedHandCursor : Qt.ArrowCursor + } + onActivated: effect.deactivate() } TapHandler { @@ -676,6 +690,7 @@ FocusScope { container.effect.deactivate(); } } + onCurrentChanged: { if (current) { allDesktopHeaps.currentHeap = heap; @@ -695,28 +710,29 @@ FocusScope { } - Column { + Loader { + id: searchResults + active: effect.searchText.length > 0 && (allDesktopHeaps.currentHeap.count === 0 || !effect.filterWindows) anchors.left: container.verticalDesktopBar ? desktopBar.right : parent.left anchors.right: parent.right anchors.bottom: parent.bottom anchors.top: topBar.bottom - Item { + sourceComponent: Item { width: parent.width height: parent.height - topBar.height - visible: effect.searchText.length > 0 && (allDesktopHeaps.currentHeap.count === 0 || !effect.filterWindows) opacity: overviewVal PlasmaExtras.PlaceholderMessage { id: placeholderMessage - visible: effect.searchText.length > 0 && allDesktopHeaps.currentHeap.count === 0 && effect.filterWindows + visible: allDesktopHeaps.currentHeap.count === 0 && effect.filterWindows anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter text: i18ndc("kwin", "@info:placeholder", "No matching windows") } Milou.ResultsView { - id: searchResults + id: milouView anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter width: parent.width / 2 @@ -727,7 +743,11 @@ FocusScope { effect.deactivate(); } } + + Keys.forwardTo: milouView } + + Keys.forwardTo: item } Repeater { diff --git a/plugins/effects/private/expolayout.cpp b/plugins/effects/private/expolayout.cpp index 49ab3f6e57..46ed230ed6 100644 --- a/plugins/effects/private/expolayout.cpp +++ b/plugins/effects/private/expolayout.cpp @@ -403,7 +403,10 @@ void ExpoLayout::calculateWindowTransformationsClosest() area.y() + (slot / columns) * slotHeight, slotWidth, slotHeight); - target.adjust(m_spacing, m_spacing, -m_spacing, -m_spacing); // Borders + QRect adjustedTarget = target.adjusted(m_spacing, m_spacing, -m_spacing, -m_spacing); + if (adjustedTarget.isValid()) { + target = adjustedTarget; // Borders + } target = target.marginsRemoved(cell->margins()); qreal scale; @@ -709,7 +712,12 @@ void ExpoLayout::calculateWindowTransformationsNatural() } for (ExpoCell* cell : std::as_const(m_cells)) { - const QRect rect = centered(cell, targets.value(cell).marginsRemoved(cell->margins())); + const QRect& cellRect = targets.value(cell); + QRect cellRectWithoutMargins = cellRect.marginsRemoved(cell->margins()); + if (!cellRectWithoutMargins.isValid()) { + cellRectWithoutMargins = cellRect; + } + const QRect rect = centered(cell, cellRectWithoutMargins); cell->setX(rect.x()); cell->setY(rect.y()); diff --git a/plugins/effects/private/qml/WindowHeap.qml b/plugins/effects/private/qml/WindowHeap.qml index 3ebedb2b41..14a9743130 100644 --- a/plugins/effects/private/qml/WindowHeap.qml +++ b/plugins/effects/private/qml/WindowHeap.qml @@ -103,7 +103,9 @@ FocusScope { otherScreenThumbnail.cloneOf = item otherScreenThumbnail.x = heapRelativePos.x; otherScreenThumbnail.y = heapRelativePos.y; - otherScreenThumbnail.visible = true; + if (item.Drag.active) { + otherScreenThumbnail.visible = true; + } } } @@ -136,6 +138,12 @@ FocusScope { windowHeap: heap } + onObjectRemoved: (index, object) => { + // undo explicitly set parent in objectAdded so it can be + // removed from the scene immediately + object.parent = null + } + onObjectAdded: (index, object) => { object.parent = expoLayout var key = object.window.internalId; diff --git a/plugins/effects/private/qml/WindowHeapDelegate.qml b/plugins/effects/private/qml/WindowHeapDelegate.qml index bdf1c3c9d7..77d6ba92ad 100644 --- a/plugins/effects/private/qml/WindowHeapDelegate.qml +++ b/plugins/effects/private/qml/WindowHeapDelegate.qml @@ -30,7 +30,9 @@ Item { readonly property bool presentOnCurrentDesktop: !window.desktops.length || window.desktops.indexOf(KWinComponents.Workspace.currentDesktop) !== -1 readonly property bool initialHidden: window.minimized || !presentOnCurrentDesktop readonly property bool activeHidden: { - if (windowHeap.showOnly === "activeClass") { + if (window.skipSwitcher) { + return true; + } else if (windowHeap.showOnly === "activeClass") { if (!KWinComponents.Workspace.activeWindow) { return true; } else { @@ -81,10 +83,10 @@ Item { } component TweenBehavior : Behavior { - enabled: thumb.state !== "partial" && thumb.windowHeap.animationEnabled && thumb.animationEnabled && !thumb.activeDragHandler.active + enabled: thumb.state === "active-normal" && thumb.windowHeap.animationEnabled && thumb.animationEnabled && !thumb.activeDragHandler.active NumberAnimation { duration: thumb.windowHeap.animationDuration - easing.type: Easing.OutCubic + easing.type: Easing.InOutCubic } } @@ -103,6 +105,7 @@ Item { Drag.hotSpot: Qt.point( thumb.activeDragHandler.centroid.pressPosition.x * thumb.targetScale, thumb.activeDragHandler.centroid.pressPosition.y * thumb.targetScale) + Drag.keys: ["kwin-window"] onXChanged: effect.checkItemDraggedOutOfScreen(thumbSource) onYChanged: effect.checkItemDraggedOutOfScreen(thumbSource) @@ -127,17 +130,14 @@ Item { thumb.windowHeap.deleteDND(thumb.window.internalId); } - KSvg.FrameSvgItem { - anchors { - fill: parent - topMargin: -Kirigami.Units.smallSpacing * 2 - leftMargin: -Kirigami.Units.smallSpacing * 2 - rightMargin: -Kirigami.Units.smallSpacing * 2 - bottomMargin: -(Math.round(icon.height / 4) + (thumb.windowTitleVisible ? caption.height : 0) + (Kirigami.Units.smallSpacing * 2)) - } - imagePath: "widgets/viewitem" - prefix: "hover" - z: -1 + // Not using FrameSvg hover element intentionally for stylistic reasons + Rectangle { + border.width: Kirigami.Units.largeSpacing + border.color: Kirigami.Theme.highlightColor + anchors.fill: parent + anchors.margins: -border.width + radius: border.width + color: "transparent" visible: !thumb.windowHeap.dragActive && (hoverHandler.hovered || (thumb.selected && Window.window.activeFocusItem)) && windowHeap.effectiveOrganized } @@ -153,8 +153,8 @@ Item { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: i18nd("kwin", "Drag Down To Close") - opacity: 1 - thumbSource.opacity - visible: !thumb.activeHidden + opacity: thumbSource.opacity + visible: !thumb.activeHidden && touchDragHandler.active } Kirigami.Icon { @@ -171,7 +171,8 @@ Item { PlasmaExtras.ShadowedLabel { id: caption visible: thumb.windowTitleVisible - width: thumb.width + width: cell.width + maximumLineCount: 1 anchors.top: parent.bottom anchors.horizontalCenter: parent.horizontalCenter elide: Text.ElideRight @@ -336,16 +337,7 @@ Item { NumberAnimation { duration: thumb.windowHeap.animationDuration properties: "x, y, width, height" - easing.type: Easing.OutCubic - } - }, - Transition { - to: "initial, initial-hidden, active-normal, active-hidden" - enabled: thumb.windowHeap.animationEnabled - NumberAnimation { - duration: thumb.windowHeap.animationDuration - properties: "x, y, width, height, opacity" - easing.type: Easing.OutCubic + easing.type: Easing.InOutCubic } } ] @@ -443,28 +435,29 @@ Item { } } - PC3.Button { + Loader { id: closeButton + LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft anchors { right: thumbSource.right top: thumbSource.top margins: Kirigami.Units.smallSpacing } + active: thumb.closeButtonVisible && (hoverHandler.hovered || Kirigami.Settings.tabletMode || Kirigami.Settings.hasTransientTouchInput) && thumb.window.closeable && !thumb.activeDragHandler.active - visible: thumb.closeButtonVisible && (hoverHandler.hovered || Kirigami.Settings.tabletMode || Kirigami.Settings.hasTransientTouchInput) && thumb.window.closeable && !thumb.activeDragHandler.active - LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft - - text: i18ndc("kwin", "@info:tooltip as in: 'close this window'", "Close window") - icon.name: "window-close" - display: PC3.AbstractButton.IconOnly + sourceComponent: PC3.Button { + text: i18ndc("kwin", "@info:tooltip as in: 'close this window'", "Close window") + icon.name: "window-close" + display: PC3.AbstractButton.IconOnly - PC3.ToolTip.text: text - PC3.ToolTip.visible: hovered && display === PC3.AbstractButton.IconOnly - PC3.ToolTip.delay: Kirigami.Units.toolTipDelay - Accessible.name: text + PC3.ToolTip.text: text + PC3.ToolTip.visible: hovered && display === PC3.AbstractButton.IconOnly + PC3.ToolTip.delay: Kirigami.Units.toolTipDelay + Accessible.name: text - onClicked: thumb.window.closeWindow(); + onClicked: thumb.window.closeWindow(); + } } Component.onDestruction: { diff --git a/plugins/effects/screenshot/screenshot.cpp b/plugins/effects/screenshot/screenshot.cpp index 1bb080f94a..b3619d150a 100644 --- a/plugins/effects/screenshot/screenshot.cpp +++ b/plugins/effects/screenshot/screenshot.cpp @@ -332,6 +332,7 @@ bool ScreenShotEffect::takeScreenShot(effect::render_data& render_data, screenshot->area.size() * screenshot->result.devicePixelRatio()); QPainter painter(&screenshot->result); + painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.setWindow(nativeArea); painter.drawImage(sourceRect, snapshot); painter.end(); diff --git a/plugins/effects/screenshot/screenshotdbusinterface2.cpp b/plugins/effects/screenshot/screenshotdbusinterface2.cpp index 1940733cfc..e81c3af185 100644 --- a/plugins/effects/screenshot/screenshotdbusinterface2.cpp +++ b/plugins/effects/screenshot/screenshotdbusinterface2.cpp @@ -186,7 +186,7 @@ class ScreenShotSourceScreen2 : public ScreenShotSource2 QVariantMap attributes() const override; private: - EffectScreen* m_screen; + QString m_name; }; class ScreenShotSourceArea2 : public ScreenShotSource2 @@ -207,7 +207,7 @@ class ScreenShotSourceWindow2 : public ScreenShotSource2 QVariantMap attributes() const override; private: - EffectWindow* m_window; + QUuid m_internalId; }; class ScreenShotSinkPipe2 : public QObject @@ -259,14 +259,14 @@ ScreenShotSourceScreen2::ScreenShotSourceScreen2(ScreenShotEffect* effect, EffectScreen* screen, ScreenShotFlags flags) : ScreenShotSource2(effect->scheduleScreenShot(screen, flags)) - , m_screen(screen) + , m_name(screen->name()) { } QVariantMap ScreenShotSourceScreen2::attributes() const { return QVariantMap{ - {QStringLiteral("screen"), m_screen->name()}, + {QStringLiteral("screen"), m_name}, }; } @@ -281,14 +281,14 @@ ScreenShotSourceWindow2::ScreenShotSourceWindow2(ScreenShotEffect* effect, EffectWindow* window, ScreenShotFlags flags) : ScreenShotSource2(effect->scheduleScreenShot(window, flags)) - , m_window(window) + , m_internalId(window->internalId()) { } QVariantMap ScreenShotSourceWindow2::attributes() const { return QVariantMap{ - {QStringLiteral("windowId"), m_window->internalId().toString()}, + {QStringLiteral("windowId"), m_internalId.toString()}, }; } diff --git a/plugins/effects/slidingpopups/slidingpopups.cpp b/plugins/effects/slidingpopups/slidingpopups.cpp index 94ae651e5b..1df067eee9 100644 --- a/plugins/effects/slidingpopups/slidingpopups.cpp +++ b/plugins/effects/slidingpopups/slidingpopups.cpp @@ -112,6 +112,8 @@ SlidingPopupsEffect::SlidingPopupsEffect() &EffectsHandler::activeFullScreenEffectChanged, this, &SlidingPopupsEffect::stopAnimations); + connect( + effects, &EffectsHandler::screenLockingChanged, this, &SlidingPopupsEffect::stopAnimations); auto const windows = effects->stackingOrder(); for (auto window : windows) { @@ -314,9 +316,7 @@ void SlidingPopupsEffect::slide_out(EffectWindow* win) auto& animation = animations[win]; - if (win->isDeleted()) { - animation.deletedRef = EffectWindowDeletedRef(win); - } + animation.deletedRef = EffectWindowDeletedRef(win); animation.visibleRef = EffectWindowVisibleRef( win, EffectWindow::PAINT_DISABLED | EffectWindow::PAINT_DISABLED_BY_DELETE); diff --git a/plugins/effects/windowview/qml/main.qml b/plugins/effects/windowview/qml/main.qml index d1ec985ea9..adacb47b59 100644 --- a/plugins/effects/windowview/qml/main.qml +++ b/plugins/effects/windowview/qml/main.qml @@ -200,8 +200,27 @@ Item { ~KWinComponents.WindowFilterModel.CriticalNotification } delegate: WindowHeapDelegate { + id: delegate windowHeap: heap partialActivationFactor: container.organized ? 1 : 0 + Behavior on partialActivationFactor { + SequentialAnimation { + PropertyAction { + target: delegate + property: "gestureInProgress" + value: true + } + NumberAnimation { + duration: container.effect.animationDuration + easing.type: Easing.OutCubic + } + PropertyAction { + target: delegate + property: "gestureInProgress" + value: false + } + } + } opacity: 1 - downGestureProgress onDownGestureTriggered: window.closeWindow() diff --git a/plugins/effects/windowview/windowviewconfig.kcfg b/plugins/effects/windowview/windowviewconfig.kcfg index ec2fc36570..90feb7de88 100644 --- a/plugins/effects/windowview/windowviewconfig.kcfg +++ b/plugins/effects/windowview/windowviewconfig.kcfg @@ -18,9 +18,7 @@ - - QList<int>() << int(win::electric_border::top_left) - + diff --git a/plugins/effects/zoom/zoom.cpp b/plugins/effects/zoom/zoom.cpp index 10b5cf3154..20f5270084 100644 --- a/plugins/effects/zoom/zoom.cpp +++ b/plugins/effects/zoom/zoom.cpp @@ -110,11 +110,14 @@ ZoomEffect::ZoomEffect() connect(effects, &EffectsHandler::screenRemoved, this, &ZoomEffect::slotScreenRemoved); #if HAVE_ACCESSIBILITY - m_accessibilityIntegration = new ZoomAccessibilityIntegration(this); - connect(m_accessibilityIntegration, - &ZoomAccessibilityIntegration::focusPointChanged, - this, - &ZoomEffect::moveFocus); + if (!effects->waylandDisplay()) { + // on Wayland, the accessibility integration can cause KWin to hang + m_accessibilityIntegration = new ZoomAccessibilityIntegration(this); + connect(m_accessibilityIntegration, + &ZoomAccessibilityIntegration::focusPointChanged, + this, + &ZoomEffect::moveFocus); + } #endif auto const windows = effects->stackingOrder(); @@ -138,7 +141,7 @@ ZoomEffect::~ZoomEffect() bool ZoomEffect::isFocusTrackingEnabled() const { #if HAVE_ACCESSIBILITY - return m_accessibilityIntegration->isFocusTrackingEnabled(); + return m_accessibilityIntegration && m_accessibilityIntegration->isFocusTrackingEnabled(); #else return false; #endif @@ -147,7 +150,7 @@ bool ZoomEffect::isFocusTrackingEnabled() const bool ZoomEffect::isTextCaretTrackingEnabled() const { #if HAVE_ACCESSIBILITY - return m_accessibilityIntegration->isTextCaretTrackingEnabled(); + return m_accessibilityIntegration && m_accessibilityIntegration->isTextCaretTrackingEnabled(); #else return false; #endif @@ -218,10 +221,13 @@ void ZoomEffect::reconfigure(ReconfigureFlags) // Track moving of the mouse. mouseTracking = MouseTrackingType(ZoomConfig::mouseTracking()); #if HAVE_ACCESSIBILITY - // Enable tracking of the focused location. - m_accessibilityIntegration->setFocusTrackingEnabled(ZoomConfig::enableFocusTracking()); - // Enable tracking of the text caret. - m_accessibilityIntegration->setTextCaretTrackingEnabled(ZoomConfig::enableTextCaretTracking()); + if (m_accessibilityIntegration) { + // Enable tracking of the focused location. + m_accessibilityIntegration->setFocusTrackingEnabled(ZoomConfig::enableFocusTracking()); + // Enable tracking of the text caret. + m_accessibilityIntegration->setTextCaretTrackingEnabled( + ZoomConfig::enableTextCaretTracking()); + } #endif // The time in milliseconds to wait before a focus-event takes away a mouse-move. focusDelay = qMax(uint(0), ZoomConfig::focusDelay()); @@ -260,7 +266,6 @@ void ZoomEffect::prePaintScreen(effect::screen_prepaint_data& data) showCursor(); } else { hideCursor(); - data.paint.mask |= PAINT_SCREEN_TRANSFORMED; } effects->prePaintScreen(data); @@ -360,25 +365,42 @@ void ZoomEffect::paintScreen(effect::screen_paint_data& data) break; case MouseTrackingPush: { // touching an edge of the screen moves the zoom-area in that direction. - int x = cursorPoint.x() * zoom - prevPoint.x() * (zoom - 1.0); - int y = cursorPoint.y() * zoom - prevPoint.y() * (zoom - 1.0); - int threshold = 4; + const int x = cursorPoint.x() * zoom - prevPoint.x() * (zoom - 1.0); + const int y = cursorPoint.y() * zoom - prevPoint.y() * (zoom - 1.0); + const int threshold = 4; + const QRectF currScreen = effects->screenAt(QPoint(x, y))->geometry(); + + // bounds of the screen the cursor's on + const int screenTop = currScreen.top(); + const int screenLeft = currScreen.left(); + const int screenRight = currScreen.right(); + const int screenBottom = currScreen.bottom(); + const int screenCenterX = currScreen.center().x(); + const int screenCenterY = currScreen.center().y(); + + // figure out whether we have adjacent displays in all 4 directions + // We pan within the screen in directions where there are no adjacent screens. + const bool adjacentLeft = screenExistsAt(QPoint(screenLeft - 1, screenCenterY)); + const bool adjacentRight = screenExistsAt(QPoint(screenRight + 1, screenCenterY)); + const bool adjacentTop = screenExistsAt(QPoint(screenCenterX, screenTop - 1)); + const bool adjacentBottom = screenExistsAt(QPoint(screenCenterX, screenBottom + 1)); + xMove = yMove = 0; - if (x < threshold) { - xMove = (x - threshold) / zoom; - } else if (x + threshold > screenSize.width()) { - xMove = (x + threshold - screenSize.width()) / zoom; + if (x < screenLeft + threshold && !adjacentLeft) { + xMove = (x - threshold - screenLeft) / zoom; + } else if (x > screenRight - threshold && !adjacentRight) { + xMove = (x + threshold - screenRight) / zoom; } - if (y < threshold) { - yMove = (y - threshold) / zoom; - } else if (y + threshold > screenSize.height()) { - yMove = (y + threshold - screenSize.height()) / zoom; + if (y < screenTop + threshold && !adjacentTop) { + yMove = (y - threshold - screenTop) / zoom; + } else if (y > screenBottom - threshold && !adjacentBottom) { + yMove = (y + threshold - screenBottom) / zoom; } if (xMove) { - prevPoint.setX(qMax(0, qMin(screenSize.width(), prevPoint.x() + xMove))); + prevPoint.setX(prevPoint.x() + xMove); } if (yMove) { - prevPoint.setY(qMax(0, qMin(screenSize.height(), prevPoint.y() + yMove))); + prevPoint.setY(prevPoint.y() + yMove); } data.paint.geo.translation.setX(-int(prevPoint.x() * (zoom - 1.0))); data.paint.geo.translation.setY(-int(prevPoint.y() * (zoom - 1.0))); @@ -661,4 +683,10 @@ qreal ZoomEffect::targetZoom() const return target_zoom; } +bool ZoomEffect::screenExistsAt(const QPoint& point) const +{ + auto output = effects->screenAt(point); + return output && output->geometry().contains(point); +} + } // namespace diff --git a/plugins/effects/zoom/zoom.h b/plugins/effects/zoom/zoom.h index 866d61faa8..146dba3594 100644 --- a/plugins/effects/zoom/zoom.h +++ b/plugins/effects/zoom/zoom.h @@ -55,6 +55,9 @@ class ZoomEffect : public Effect int configuredFocusDelay() const; qreal configuredMoveFactor() const; qreal targetZoom() const; + +private: + bool screenExistsAt(const QPoint& point) const; private Q_SLOTS: inline void zoomIn() { diff --git a/plugins/scripts/desktopchangeosd/package/contents/ui/main.qml b/plugins/scripts/desktopchangeosd/package/contents/ui/main.qml index ef5ffb5506..7301d638d5 100644 --- a/plugins/scripts/desktopchangeosd/package/contents/ui/main.qml +++ b/plugins/scripts/desktopchangeosd/package/contents/ui/main.qml @@ -15,10 +15,11 @@ Item { Connections { target: Workspace - function onCurrentDesktopChanged() { + function onCurrentDesktopChanged(previous) { if (!mainItemLoader.item) { mainItemLoader.source = "osd.qml"; } + mainItemLoader.item.show(previous); } } } diff --git a/plugins/scripts/desktopchangeosd/package/contents/ui/osd.qml b/plugins/scripts/desktopchangeosd/package/contents/ui/osd.qml index a5dfb368bc..f3eaef917f 100644 --- a/plugins/scripts/desktopchangeosd/package/contents/ui/osd.qml +++ b/plugins/scripts/desktopchangeosd/package/contents/ui/osd.qml @@ -27,31 +27,6 @@ PlasmaCore.Dialog { } } - function show() { - const index = Workspace.desktops.indexOf(Workspace.currentDesktop); - if (dialogItem.currentIndex === index) { - return; - } - dialogItem.previousIndex = dialogItem.currentIndex; - timer.stop(); - dialogItem.currentIndex = index; - // screen geometry might have changed - var screen = Workspace.clientArea(KWin.FullScreenArea, Workspace.activeScreen, Workspace.currentDesktop); - dialogItem.screenWidth = screen.width; - dialogItem.screenHeight = screen.height; - if (dialogItem.showGrid) { - // non dependable properties might have changed - view.columns = Workspace.desktopGridWidth; - view.rows = Workspace.desktopGridHeight; - } - dialog.visible = true; - // position might have changed - dialog.x = screen.x + screen.width/2 - dialogItem.width/2; - dialog.y = screen.y + screen.height/2 - dialogItem.height/2; - // start the hide timer - timer.start(); - } - id: dialogItem property int screenWidth: 0 property int screenHeight: 0 @@ -269,12 +244,6 @@ PlasmaCore.Dialog { onTriggered: dialog.visible = false } - Connections { - target: Workspace - function onCurrentDesktopChanged() { - dialogItem.show() - } - } Connections { target: Options function onConfigChanged() { @@ -285,7 +254,29 @@ PlasmaCore.Dialog { view.columns = Workspace.desktopGridWidth; view.rows = Workspace.desktopGridHeight; dialogItem.loadConfig(); - dialogItem.show(); } } + + function show(previous) { + if (Workspace.isEffectActive("overview")) { + return; + } + dialogItem.previousIndex = Workspace.desktops.indexOf(previous); + dialogItem.currentIndex = Workspace.desktops.indexOf(Workspace.currentDesktop); + // screen geometry might have changed + var screen = Workspace.clientArea(KWin.FullScreenArea, Workspace.activeScreen, Workspace.currentDesktop); + dialogItem.screenWidth = screen.width; + dialogItem.screenHeight = screen.height; + if (dialogItem.showGrid) { + // non dependable properties might have changed + view.columns = Workspace.desktopGridWidth; + view.rows = Workspace.desktopGridHeight; + } + dialog.visible = true; + // position might have changed + dialog.x = screen.x + screen.width/2 - dialogItem.width/2; + dialog.y = screen.y + screen.height/2 - dialogItem.height/2; + // start the hide timer + timer.restart(); + } }