diff --git a/src/server/src/actions/wm/window-actions.hpp b/src/server/src/actions/wm/window-actions.hpp index 153c616b9..441dfc70d 100644 --- a/src/server/src/actions/wm/window-actions.hpp +++ b/src/server/src/actions/wm/window-actions.hpp @@ -8,15 +8,17 @@ class FocusWindowAction : public AbstractAction { std::shared_ptr m_window; void execute(ApplicationContext *ctx) override { - auto wm = ctx->services->windowManager(); - wm->provider()->focusWindowSync(*m_window.get()); + auto window = m_window; + ctx->navigation->deferUntilWindowHidden([ctx, window]() { + auto wm = ctx->services->windowManager(); + wm->provider()->focusWindowSync(*window.get()); + }); + ctx->navigation->closeWindow({.clearRootSearch = true}); } public: FocusWindowAction(const std::shared_ptr &window) - : AbstractAction("Focus window", ImageURL::builtin("app-window")), m_window(window) { - setAutoClose(); - } + : AbstractAction("Focus window", ImageURL::builtin("app-window")), m_window(window) {} }; class CloseWindowAction : public AbstractAction { diff --git a/src/server/src/navigation-controller.cpp b/src/server/src/navigation-controller.cpp index ab36ffba8..173b030a0 100644 --- a/src/server/src/navigation-controller.cpp +++ b/src/server/src/navigation-controller.cpp @@ -34,6 +34,16 @@ bool NavigationController::hasCompleter() const { return false; } +void NavigationController::deferUntilWindowHidden(std::function fn) { + m_deferredWindowHiddenAction = std::move(fn); +} + +std::function NavigationController::takeDeferredWindowHiddenAction() { + auto fn = std::move(m_deferredWindowHiddenAction); + m_deferredWindowHiddenAction = nullptr; + return fn; +} + void NavigationController::setInstantDismiss(bool value) { m_instantDismiss = value; } void NavigationController::goBack(const GoBackOptions &opts) { diff --git a/src/server/src/navigation-controller.hpp b/src/server/src/navigation-controller.hpp index ec6247fa9..c2252edfb 100644 --- a/src/server/src/navigation-controller.hpp +++ b/src/server/src/navigation-controller.hpp @@ -8,6 +8,7 @@ #include "ui/action-pannel/action.hpp" #include "ui/dialog/dialog.hpp" #include "ui/image/url.hpp" +#include #include #include #include @@ -263,6 +264,8 @@ class NavigationController : public QObject, NonCopyable { Q_INVOKABLE void toggleWindow(); bool isWindowOpened() const; void requestWindowSize(QSize size); + void deferUntilWindowHidden(std::function fn); + std::function takeDeferredWindowHiddenAction(); bool windowActivated(); void setWindowActivated(bool value = true); @@ -398,6 +401,7 @@ class NavigationController : public QObject, NonCopyable { bool m_popToRootOnClose = false; bool m_instantDismiss = false; bool m_closeOnFocusLoss = false; + std::function m_deferredWindowHiddenAction; std::vector> m_views; std::optional m_pendingPopToRoot; }; diff --git a/src/server/src/qml/launcher-window.cpp b/src/server/src/qml/launcher-window.cpp index 569271a14..42c3dffcf 100644 --- a/src/server/src/qml/launcher-window.cpp +++ b/src/server/src/qml/launcher-window.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #ifdef WAYLAND_LAYER_SHELL #include @@ -309,6 +310,10 @@ void LauncherWindow::handleVisibilityChanged(bool visible) { m_window->requestActivate(); } else { m_window->hide(); + + if (auto deferred = m_ctx.navigation->takeDeferredWindowHiddenAction()) { + QTimer::singleShot(0, this, [deferred = std::move(deferred)]() mutable { deferred(); }); + } } } diff --git a/src/server/src/services/window-manager/hyprland/hyprland.cpp b/src/server/src/services/window-manager/hyprland/hyprland.cpp index dcb31aeb4..63ece3822 100644 --- a/src/server/src/services/window-manager/hyprland/hyprland.cpp +++ b/src/server/src/services/window-manager/hyprland/hyprland.cpp @@ -1,4 +1,5 @@ #include +#include #include "hyprland.hpp" #include "services/window-manager/abstract-window-manager.hpp" #include "services/window-manager/hyprland/hypr-workspace.hpp" @@ -7,6 +8,32 @@ using Hyprctl = Hyprland::Controller; +namespace { +bool isScrollingLayout() { + auto response = Hyprctl::oneshot("-j/getoption general:layout"); + auto json = QJsonDocument::fromJson(response); + + if (!json.isObject()) return false; + + return json.object().value("str").toString() == "scrolling"; +} + +bool waitUntilFocused(QStringView address, int timeoutMs = 200) { + constexpr int pollIntervalMs = 10; + + for (int elapsed = 0; elapsed <= timeoutMs; elapsed += pollIntervalMs) { + auto response = Hyprctl::oneshot("-j/activewindow"); + auto json = QJsonDocument::fromJson(response); + + if (json.isObject() && json.object().value("address").toString() == address) return true; + + QThread::msleep(pollIntervalMs); + } + + return false; +} +} // namespace + HyprlandWindowManager::HyprlandWindowManager() { connect(&m_ev, &Hyprland::EventListener::openwindow, this, [this]() { emit windowsChanged(); }); connect(&m_ev, &Hyprland::EventListener::closewindow, this, [this]() { emit windowsChanged(); }); @@ -51,7 +78,17 @@ AbstractWindowManager::WindowPtr HyprlandWindowManager::getFocusedWindowSync() c } void HyprlandWindowManager::focusWindowSync(const AbstractWindow &window) const { + const auto activeWorkspace = getActiveWorkspace(); + const auto targetWorkspace = window.workspace(); + const bool sameWorkspace = activeWorkspace && targetWorkspace && activeWorkspace->id() == *targetWorkspace; + Hyprctl::oneshot(std::format("dispatch focuswindow address:{}", window.id().toStdString())); + + if (!sameWorkspace || !isScrollingLayout()) return; + + if (!waitUntilFocused(window.id())) return; + + Hyprctl::oneshot("dispatch layoutmsg center"); } bool HyprlandWindowManager::closeWindow(const AbstractWindow &window) const {