Skip to content

Commit

Permalink
implement cross-platform hacks for window flags manipulation
Browse files Browse the repository at this point in the history
partially reverts 7442199
  • Loading branch information
Kolcha committed Oct 5, 2024
1 parent 141b28a commit 3767f9e
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 59 deletions.
2 changes: 1 addition & 1 deletion app/core/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ void Application::initTray()
void Application::createWindows()
{
for (int i = 0; i < _cfg->global().getNumInstances(); i++) {
auto wnd = std::make_unique<ClockWindow>();
auto wnd = std::make_unique<ClockNativeWindow>();

wnd->setOriginPoint(wnd->originPoint() + QPoint(i*15, i*15));

Expand Down
4 changes: 2 additions & 2 deletions app/core/application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -97,7 +97,7 @@ private slots:
ClockTrayIcon _tray_icon;
std::unique_ptr<QMenu> _tray_menu;
// QWidget seems to be non-movable type :(
std::vector<std::unique_ptr<ClockWindow>> _windows;
std::vector<std::unique_ptr<ClockNativeWindow>> _windows;
QTimer _time_timer;
QTimer _tick_timer;
SkinManager _sm;
Expand Down
36 changes: 32 additions & 4 deletions app/gui/clock_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,37 @@

#include "clock_window.hpp"

#include <QApplication>

#include <QMenu>
#include <QScreen>

#include <QContextMenuEvent>
#include <QMouseEvent>

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);
Expand Down Expand Up @@ -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());
Expand Down
14 changes: 4 additions & 10 deletions app/gui/clock_window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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<QString> _fullscreen_ignore_list;
Expand Down
7 changes: 5 additions & 2 deletions app/platform/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
24 changes: 24 additions & 0 deletions app/platform/clock_native_window.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2024 Nick Korotysh <[email protected]>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/

#pragma once

#include <QtGlobal>

#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
23 changes: 23 additions & 0 deletions app/platform/mac/clock_native_window.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2024 Nick Korotysh <[email protected]>
*
* 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);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/

#include "gui/clock_window.hpp"
#include "clock_native_window.hpp"

#import <AppKit/NSWindow.h>

Expand All @@ -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<NSView*>(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<NSView*>(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<NSView*>(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<NSView*>(winId());
[view.window setIgnoresMouseEvents: en];
SetCollectionBehavior(view.window, NSWindowCollectionBehaviorCanJoinAllSpaces, en);
SetVisibleInFullscreen(view.window, !_detect_fullscreen);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/*
* SPDX-FileCopyrightText: 2017-2024 Nick Korotysh <[email protected]>
* SPDX-FileCopyrightText: 2024 Nick Korotysh <[email protected]>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/

#include "gui/clock_window.hpp"
#include "clock_native_window.hpp"

#ifndef NOMINMAX
#define NOMINMAX
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
22 changes: 22 additions & 0 deletions app/platform/win/clock_native_window.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2024 Nick Korotysh <[email protected]>
*
* 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();
};

0 comments on commit 3767f9e

Please sign in to comment.