From 3767f9efa362cdd84e255d154f0ac241afa3f937 Mon Sep 17 00:00:00 2001 From: Nick Korotysh Date: Sun, 6 Oct 2024 00:31:41 +0200 Subject: [PATCH] implement cross-platform hacks for window flags manipulation partially reverts 7442199b8247fbf01163620d85805b90ffee08b6 --- app/core/application.cpp | 2 +- app/core/application.hpp | 4 +- app/gui/clock_window.cpp | 36 +++++++++-- app/gui/clock_window.hpp | 14 ++--- app/platform/CMakeLists.txt | 7 ++- app/platform/clock_native_window.hpp | 24 +++++++ app/platform/mac/clock_native_window.hpp | 23 +++++++ ...clock_window.mm => clock_native_window.mm} | 26 ++++---- ...ock_window.cpp => clock_native_window.cpp} | 62 ++++++++++--------- app/platform/win/clock_native_window.hpp | 22 +++++++ 10 files changed, 161 insertions(+), 59 deletions(-) create mode 100644 app/platform/clock_native_window.hpp create mode 100644 app/platform/mac/clock_native_window.hpp rename app/platform/mac/{clock_window.mm => clock_native_window.mm} (74%) rename app/platform/win/{clock_window.cpp => clock_native_window.cpp} (66%) create mode 100644 app/platform/win/clock_native_window.hpp diff --git a/app/core/application.cpp b/app/core/application.cpp index 8efc33b..86ae4e9 100644 --- a/app/core/application.cpp +++ b/app/core/application.cpp @@ -224,7 +224,7 @@ void Application::initTray() void Application::createWindows() { for (int i = 0; i < _cfg->global().getNumInstances(); i++) { - auto wnd = std::make_unique(); + auto wnd = std::make_unique(); wnd->setOriginPoint(wnd->originPoint() + QPoint(i*15, i*15)); diff --git a/app/core/application.hpp b/app/core/application.hpp index 60669b7..2fac0b6 100644 --- a/app/core/application.hpp +++ b/app/core/application.hpp @@ -18,7 +18,7 @@ #include "config/app_config.hpp" #include "gui/clock_tray_icon.hpp" -#include "gui/clock_window.hpp" +#include "platform/clock_native_window.hpp" #include "platform/mouse_tracker.hpp" #include "dialog_manager.hpp" #include "plugin_manager.hpp" @@ -97,7 +97,7 @@ private slots: ClockTrayIcon _tray_icon; std::unique_ptr _tray_menu; // QWidget seems to be non-movable type :( - std::vector> _windows; + std::vector> _windows; QTimer _time_timer; QTimer _tick_timer; SkinManager _sm; diff --git a/app/gui/clock_window.cpp b/app/gui/clock_window.cpp index 3d184e3..01339ca 100644 --- a/app/gui/clock_window.cpp +++ b/app/gui/clock_window.cpp @@ -6,21 +6,37 @@ #include "clock_window.hpp" +#include + #include #include #include #include +namespace { + +// this wrapper function allows to change window flag on runtime as it should be +// after setWindowFlag() call widget becomes hidden, and must be shown explicitly +// show() activates the widget, so previous becomes inactive, this is annoying... +// so, remember the current active widget and restore it after changing the flag +void wrap_set_window_flag(QWidget* wnd, Qt::WindowType flag, bool set) +{ + QWidget* aw = QApplication::activeWindow(); + bool last_visible = wnd->isVisible(); + wnd->setWindowFlag(flag, set); + if (last_visible != wnd->isVisible()) wnd->show(); + if (aw) aw->activateWindow(); +} + +} // namespace + ClockWindow::ClockWindow(QWidget* parent) : QWidget(parent) { setAttribute(Qt::WA_NativeWindow); setAttribute(Qt::WA_DontCreateNativeAncestors); -#if defined(Q_OS_MACOS) - setHiddenInMissionControl(true); - setVisibleOnAllDesktops(true); -#endif + _layout = new QGridLayout(this); _layout->setSizeConstraint(QLayout::SetFixedSize); _layout->setContentsMargins(0, 0, 0, 0); @@ -173,6 +189,18 @@ void ClockWindow::handleMouseMove(const QPoint& global_pos) } #endif +void ClockWindow::setStayOnTop(bool en) +{ + _should_stay_on_top = en; + wrap_set_window_flag(this, Qt::WindowStaysOnTopHint, en); + wrap_set_window_flag(this, Qt::BypassWindowManagerHint, en); +} + +void ClockWindow::setTransparentForInput(bool en) +{ + wrap_set_window_flag(this, Qt::WindowTransparentForInput, en); +} + void ClockWindow::contextMenuEvent(QContextMenuEvent* event) { _ctx_menu->popup(event->globalPos()); diff --git a/app/gui/clock_window.hpp b/app/gui/clock_window.hpp index 8605b00..789fffb 100644 --- a/app/gui/clock_window.hpp +++ b/app/gui/clock_window.hpp @@ -73,17 +73,10 @@ public slots: // handler should be public to keep only one instance of that tracker void handleMouseMove(const QPoint& global_pos); #endif -#if defined(Q_OS_WINDOWS) - void runStayOnTopHacks(); -#endif -#if defined(Q_OS_MACOS) - void setHiddenInMissionControl(bool en); - void setVisibleOnAllDesktops(bool en); -#endif - void setFullscreenDetect(bool en) { _detect_fullscreen = en; } + virtual void setFullscreenDetect(bool en) { _detect_fullscreen = en; } - void setStayOnTop(bool en); - void setTransparentForInput(bool en); + virtual void setStayOnTop(bool en); + virtual void setTransparentForInput(bool en); signals: void saveStateRequested(); @@ -129,6 +122,7 @@ public slots: bool _transparent_on_hover = true; qreal _opacity_on_hover = 0.15; +protected: // used by stay on top hacks, but types are not platform-dependent QScreen* _last_screen = nullptr; QSet _fullscreen_ignore_list; diff --git a/app/platform/CMakeLists.txt b/app/platform/CMakeLists.txt index 494cee1..246b077 100644 --- a/app/platform/CMakeLists.txt +++ b/app/platform/CMakeLists.txt @@ -6,13 +6,15 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(${PROJECT_NAME} PRIVATE autostart.h + clock_native_window.hpp ) if (WIN32) target_sources(${PROJECT_NAME} PRIVATE mouse_tracker.hpp win/autostart_win.cpp - win/clock_window.cpp + win/clock_native_window.cpp + win/clock_native_window.hpp win/fullscreen_detect.cpp win/fullscreen_detect.hpp win/mouse_tracker.cpp @@ -25,7 +27,8 @@ if (APPLE) target_sources(${PROJECT_NAME} PRIVATE mouse_tracker.hpp mac/autostart_mac.cpp - mac/clock_window.mm + mac/clock_native_window.hpp + mac/clock_native_window.mm mac/mouse_tracker.mm ) endif() diff --git a/app/platform/clock_native_window.hpp b/app/platform/clock_native_window.hpp new file mode 100644 index 0000000..eeabc22 --- /dev/null +++ b/app/platform/clock_native_window.hpp @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2024 Nick Korotysh + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#if defined(Q_OS_WINDOWS) +#define HAVE_NATIVE_WIN_IMPL +#include "win/clock_native_window.hpp" +#endif + +#if defined(Q_OS_MACOS) +#define HAVE_NATIVE_WIN_IMPL +#include "mac/clock_native_window.hpp" +#endif + +#if !defined(HAVE_NATIVE_WIN_IMPL) +#include "gui/clock_window.hpp" +using ClockNativeWindow = ClockWindow; +#endif diff --git a/app/platform/mac/clock_native_window.hpp b/app/platform/mac/clock_native_window.hpp new file mode 100644 index 0000000..25d3a63 --- /dev/null +++ b/app/platform/mac/clock_native_window.hpp @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2024 Nick Korotysh + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "gui/clock_window.hpp" + +class ClockNativeWindow : public ClockWindow +{ + Q_OBJECT + +public: + explicit ClockNativeWindow(QWidget* parent = nullptr); + +public slots: + void setStayOnTop(bool en) override; + + void setHiddenInMissionControl(bool en); + void setVisibleOnAllDesktops(bool en); +}; diff --git a/app/platform/mac/clock_window.mm b/app/platform/mac/clock_native_window.mm similarity index 74% rename from app/platform/mac/clock_window.mm rename to app/platform/mac/clock_native_window.mm index d7542d1..942b68a 100644 --- a/app/platform/mac/clock_window.mm +++ b/app/platform/mac/clock_native_window.mm @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#include "gui/clock_window.hpp" +#include "clock_native_window.hpp" #import @@ -24,29 +24,31 @@ static void SetVisibleInFullscreen(NSWindow* window, bool vis) } -void ClockWindow::setHiddenInMissionControl(bool en) +ClockNativeWindow::ClockNativeWindow(QWidget* parent) + : ClockWindow(parent) { - NSView* view = reinterpret_cast(winId()); - SetCollectionBehavior(view.window, NSWindowCollectionBehaviorManaged, !en); - SetCollectionBehavior(view.window, NSWindowCollectionBehaviorTransient, en); + setHiddenInMissionControl(true); + setVisibleOnAllDesktops(true); } -void ClockWindow::setVisibleOnAllDesktops(bool en) +void ClockNativeWindow::setStayOnTop(bool en) { + ClockWindow::setStayOnTop(en); + NSView* view = reinterpret_cast(winId()); - SetCollectionBehavior(view.window, NSWindowCollectionBehaviorCanJoinAllSpaces, en); SetVisibleInFullscreen(view.window, !_detect_fullscreen); } -void ClockWindow::setStayOnTop(bool en) +void ClockNativeWindow::setHiddenInMissionControl(bool en) { NSView* view = reinterpret_cast(winId()); - [view.window setLevel: en ? NSFloatingWindowLevel : NSNormalWindowLevel]; - SetVisibleInFullscreen(view.window, !_detect_fullscreen); + SetCollectionBehavior(view.window, NSWindowCollectionBehaviorManaged, !en); + SetCollectionBehavior(view.window, NSWindowCollectionBehaviorTransient, en); } -void ClockWindow::setTransparentForInput(bool en) +void ClockNativeWindow::setVisibleOnAllDesktops(bool en) { NSView* view = reinterpret_cast(winId()); - [view.window setIgnoresMouseEvents: en]; + SetCollectionBehavior(view.window, NSWindowCollectionBehaviorCanJoinAllSpaces, en); + SetVisibleInFullscreen(view.window, !_detect_fullscreen); } diff --git a/app/platform/win/clock_window.cpp b/app/platform/win/clock_native_window.cpp similarity index 66% rename from app/platform/win/clock_window.cpp rename to app/platform/win/clock_native_window.cpp index 64020be..1986794 100644 --- a/app/platform/win/clock_window.cpp +++ b/app/platform/win/clock_native_window.cpp @@ -1,10 +1,10 @@ /* - * SPDX-FileCopyrightText: 2017-2024 Nick Korotysh + * SPDX-FileCopyrightText: 2024 Nick Korotysh * * SPDX-License-Identifier: GPL-3.0-or-later */ -#include "gui/clock_window.hpp" +#include "clock_native_window.hpp" #ifndef NOMINMAX #define NOMINMAX @@ -38,24 +38,49 @@ static void SetSurviveWinDHackEnabled(WId winId, bool en) SetWindowLongPtr((HWND)winId, GWLP_HWNDPARENT, (LONG_PTR)0); } -void ClockWindow::runStayOnTopHacks() + +ClockNativeWindow::ClockNativeWindow(QWidget* parent) + : ClockWindow(parent) { - if (!_should_stay_on_top) return; +} + +void ClockNativeWindow::setStayOnTop(bool en) +{ + ClockWindow::setStayOnTop(en); + + SetSurviveWinDHackEnabled(winId(), !en); + runStayOnTopHacks(); +} + +void ClockNativeWindow::runStayOnTopHacks() +{ + // calling winId() for invisible window causes move event from (0,0) to (289,160) + // during startup (doesn't matter what saved coordinates were, each time the same), + // as result real saved position will be overwritten even before it will be read + // so do nothing if window is not visible + if (!isVisible()) return; // unfortunately, fullscreen invisible (transparent) windows may exist... we should ignore them // remember all fullscreen windows on app startup or window's screen change and ignore them later if (_last_screen != screen()) { - _last_screen = screen(); - _fullscreen_ignore_list = dc::GetFullscreenWindowsOnSameMonitor(winId()); - } + _last_screen = screen(); + _fullscreen_ignore_list = dc::GetFullscreenWindowsOnSameMonitor(winId()); + } + + if (!_should_stay_on_top) return; if (_detect_fullscreen && dc::IsFullscreenWndOnSameMonitor(winId(), _fullscreen_ignore_list)) { // don't stay above fullscreen windows - if ((GetWindowLongPtr((HWND)winId(), GWL_EXSTYLE) & WS_EX_TOPMOST) != 0) { - SetWindowPos((HWND)winId(), HWND_BOTTOM, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + if (windowFlags() & Qt::WindowStaysOnTopHint) { + setStayOnTop(false); + lower(); } } else { // always on top problem workaround + // sometimes Qt somehow loses Qt::WindowStaysOnTopHint window flag, so set it again + if (!(windowFlags() & Qt::WindowStaysOnTopHint)) { + setStayOnTop(true); + } // sometimes even window have Qt::WindowStaysOnTopHint window flag, it doesn't have WS_EX_TOPMOST flag, // so set it manually using WinAPI... if ((GetWindowLongPtr((HWND)winId(), GWL_EXSTYLE) & WS_EX_TOPMOST) == 0) { @@ -66,22 +91,3 @@ void ClockWindow::runStayOnTopHacks() if (!isActiveWindow()) raise(); } } - -void ClockWindow::setStayOnTop(bool en) -{ - _should_stay_on_top = en; - HWND pos = en ? HWND_TOPMOST : HWND_NOTOPMOST; - SetWindowPos((HWND)winId(), pos, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); - SetSurviveWinDHackEnabled(winId(), !en); - runStayOnTopHacks(); -} - -void ClockWindow::setTransparentForInput(bool en) -{ - LONG_PTR st = GetWindowLongPtr((HWND)winId(), GWL_EXSTYLE); - if (en) - st |= WS_EX_TRANSPARENT; - else - st &= ~WS_EX_TRANSPARENT; - SetWindowLongPtr((HWND)winId(), GWL_EXSTYLE, (LONG_PTR)st); -} diff --git a/app/platform/win/clock_native_window.hpp b/app/platform/win/clock_native_window.hpp new file mode 100644 index 0000000..228bcdf --- /dev/null +++ b/app/platform/win/clock_native_window.hpp @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024 Nick Korotysh + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "gui/clock_window.hpp" + +class ClockNativeWindow : public ClockWindow +{ + Q_OBJECT + +public: + explicit ClockNativeWindow(QWidget* parent = nullptr); + +public slots: + void setStayOnTop(bool en) override; + + void runStayOnTopHacks(); +};