| | |
| | | |
| | | #include <QtCore/QHash> |
| | | #include <QtCore/QScopeGuard> |
| | | #include <QtCore/QTimer> |
| | | #include <QtGui/QGuiApplication> |
| | | #include <QtGui/QPainter> |
| | | #include <QtGui/QPalette> |
| | | #include <QtGui/QStyleHints> |
| | | |
| | | #include <QtCore/private/qwinregistry_p.h> |
| | | #include <QtCore/private/qsystemlibrary_p.h> |
| | | #include <QtGui/private/qhighdpiscaling_p.h> |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| | |
| | | #include <shellscalingapi.h> |
| | | #include <dwmapi.h> |
| | | #include <timeapi.h> |
| | | #include <versionhelpers.h> |
| | | |
| | | #include "nativeeventfilter.h" |
| | | #include "qwkglobal_p.h" |
| | | |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | | Q_DECLARE_METATYPE(QMargins) |
| | |
| | | |
| | | namespace QWK { |
| | | |
| | | enum _DWMWINDOWATTRIBUTE { |
| | | // [set] BOOL, Allows the use of host backdrop brushes for the window. |
| | | _DWMWA_USE_HOSTBACKDROPBRUSH = 17, |
| | | |
| | | // Undocumented, the same with DWMWA_USE_IMMERSIVE_DARK_MODE, but available on systems |
| | | // before Win10 20H1. |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19, |
| | | |
| | | // [set] BOOL, Allows a window to either use the accent color, or dark, according to the |
| | | // user Color Mode preferences. |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE = 20, |
| | | |
| | | // [set] WINDOW_CORNER_PREFERENCE, Controls the policy that rounds top-level window corners |
| | | _DWMWA_WINDOW_CORNER_PREFERENCE = 33, |
| | | |
| | | // [get] UINT, width of the visible border around a thick frame window |
| | | _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37, |
| | | |
| | | // [get, set] SYSTEMBACKDROP_TYPE, Controls the system-drawn backdrop material of a window, |
| | | // including behind the non-client area. |
| | | _DWMWA_SYSTEMBACKDROP_TYPE = 38, |
| | | |
| | | // Undocumented, use this value to enable Mica material on Win11 21H2. You should use |
| | | // DWMWA_SYSTEMBACKDROP_TYPE instead on Win11 22H2 and newer. |
| | | _DWMWA_MICA_EFFECT = 1029 |
| | | }; |
| | | |
| | | // The thickness of an auto-hide taskbar in pixels. |
| | | static constexpr const auto kAutoHideTaskBarThickness = quint8{2}; |
| | | static constexpr const quint8 kAutoHideTaskBarThickness = 2; |
| | | |
| | | QWK_USED static constexpr const struct { |
| | | const uint32_t activeLight = MAKE_RGBA_COLOR(110, 110, 110, 255); // #6E6E6E |
| | | const uint32_t activeDark = MAKE_RGBA_COLOR(51, 51, 51, 255); // #333333 |
| | | const uint32_t inactiveLight = MAKE_RGBA_COLOR(167, 167, 167, 255); // #A7A7A7 |
| | | const uint32_t inactiveDark = MAKE_RGBA_COLOR(61, 61, 62, 255); // #3D3D3E |
| | | } kWindowsColorSet; |
| | | |
| | | // hWnd -> context |
| | | using WndProcHash = QHash<HWND, Win32WindowContext *>; |
| | |
| | | // Original Qt window proc function |
| | | static WNDPROC g_qtWindowProc = nullptr; |
| | | |
| | | // ### FIXME FIXME FIXME |
| | | // ### FIXME: Tell the user to call in the documentation, instead of automatically |
| | | // calling it directly. |
| | | // ### FIXME FIXME FIXME |
| | | static struct QWK_Hook { |
| | | QWK_Hook() { |
| | | qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); |
| | | } |
| | | } g_hook{}; |
| | | |
| | | struct DynamicApis { |
| | | // template <typename T> |
| | | // struct DefaultFunc; |
| | | // |
| | | // template <typename Return, typename... Args> |
| | | // struct DefaultFunc<Return(QT_WIN_CALLBACK *)(Args...)> { |
| | | // static Return STDAPICALLTYPE func(Args...) { |
| | | // return Return{}; |
| | | // } |
| | | // }; |
| | | // |
| | | // #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = |
| | | // DefaultFunc<decltype(&::NAME)>::func |
| | | static const DynamicApis &instance() { |
| | | static const DynamicApis inst{}; |
| | | return inst; |
| | | } |
| | | |
| | | // template <typename T> |
| | | // struct DefaultFunc; |
| | | // |
| | | // template <typename Return, typename... Args> |
| | | // struct DefaultFunc<Return(QT_WIN_CALLBACK *)(Args...)> { |
| | | // static Return STDAPICALLTYPE func(Args...) { |
| | | // return Return{}; |
| | | // } |
| | | // }; |
| | | // |
| | | // #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = |
| | | // DefaultFunc<decltype(&::NAME)>::func |
| | | |
| | | #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = nullptr |
| | | |
| | | DYNAMIC_API_DECLARE(DwmFlush); |
| | | DYNAMIC_API_DECLARE(DwmIsCompositionEnabled); |
| | | DYNAMIC_API_DECLARE(DwmGetCompositionTimingInfo); |
| | | DYNAMIC_API_DECLARE(DwmGetWindowAttribute); |
| | | DYNAMIC_API_DECLARE(DwmSetWindowAttribute); |
| | | DYNAMIC_API_DECLARE(GetDpiForWindow); |
| | | DYNAMIC_API_DECLARE(GetSystemMetricsForDpi); |
| | | DYNAMIC_API_DECLARE(GetDpiForMonitor); |
| | |
| | | |
| | | #undef DYNAMIC_API_DECLARE |
| | | |
| | | private: |
| | | DynamicApis() { |
| | | #define DYNAMIC_API_RESOLVE(DLL, NAME) \ |
| | | p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME)) |
| | |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmFlush); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmIsCompositionEnabled); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmGetCompositionTimingInfo); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmGetWindowAttribute); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmSetWindowAttribute); |
| | | |
| | | QSystemLibrary winmm(QStringLiteral("winmm")); |
| | | DYNAMIC_API_RESOLVE(winmm, timeGetDevCaps); |
| | |
| | | |
| | | ~DynamicApis() = default; |
| | | |
| | | static const DynamicApis &instance() { |
| | | static const DynamicApis inst{}; |
| | | return inst; |
| | | } |
| | | |
| | | private: |
| | | Q_DISABLE_COPY_MOVE(DynamicApis) |
| | | }; |
| | | |
| | |
| | | } |
| | | |
| | | static inline bool isWin8OrGreater() { |
| | | static const bool result = ::IsWindows8OrGreater(); |
| | | static const bool result = IsWindows8OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin8Point1OrGreater() { |
| | | static const bool result = ::IsWindows8Point1OrGreater(); |
| | | static const bool result = IsWindows8Point1OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin10OrGreater() { |
| | | static const bool result = ::IsWindows10OrGreater(); |
| | | static const bool result = IsWindows10OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin11OrGreater() { |
| | | static const bool result = ::IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN10), |
| | | LOBYTE(_WIN32_WINNT_WIN10), 22000); |
| | | static const bool result = IsWindows11OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | |
| | | } |
| | | BOOL enabled = FALSE; |
| | | return SUCCEEDED(apis.pDwmIsCompositionEnabled(&enabled)) && enabled; |
| | | } |
| | | |
| | | static inline bool isWindowFrameBorderColorized() { |
| | | const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); |
| | | if (!registry.isValid()) { |
| | | return false; |
| | | } |
| | | const auto value = registry.dwordValue(L"ColorPrevalence"); |
| | | if (!value.second) { |
| | | return false; |
| | | } |
| | | return value.first; |
| | | } |
| | | |
| | | static inline bool isDarkThemeActive() { |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) |
| | | return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark; |
| | | #else |
| | | const QWinRegistryKey registry( |
| | | HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)"); |
| | | if (!registry.isValid()) { |
| | | return false; |
| | | } |
| | | const auto value = registry.dwordValue(L"AppsUseLightTheme"); |
| | | if (!value.second) { |
| | | return false; |
| | | } |
| | | return !value.first; |
| | | #endif |
| | | } |
| | | |
| | | static inline bool isDarkWindowFrameEnabled(HWND hwnd) { |
| | | BOOL enabled = FALSE; |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, _DWMWA_USE_IMMERSIVE_DARK_MODE, &enabled, |
| | | sizeof(enabled)))) { |
| | | return enabled; |
| | | } else if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, |
| | | &enabled, sizeof(enabled)))) { |
| | | return enabled; |
| | | } else { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | static inline QColor getAccentColor() { |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) |
| | | return QGuiApplication::palette().color(QPalette::Accent); |
| | | #else |
| | | const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); |
| | | if (!registry.isValid()) { |
| | | return {}; |
| | | } |
| | | const auto value = registry.dwordValue(L"AccentColor"); |
| | | if (!value.second) { |
| | | return {}; |
| | | } |
| | | // The retrieved value is in the #AABBGGRR format, we need to |
| | | // convert it to the #AARRGGBB format which Qt expects. |
| | | const QColor abgr = QColor::fromRgba(value.first); |
| | | if (!abgr.isValid()) { |
| | | return {}; |
| | | } |
| | | return QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha()); |
| | | #endif |
| | | } |
| | | |
| | | static inline void triggerFrameChange(HWND hwnd) { |
| | |
| | | } else { // Win2K |
| | | HDC hdc = ::GetDC(nullptr); |
| | | const int dpiX = ::GetDeviceCaps(hdc, LOGPIXELSX); |
| | | const int dpiY = ::GetDeviceCaps(hdc, LOGPIXELSY); |
| | | // const int dpiY = ::GetDeviceCaps(hdc, LOGPIXELSY); |
| | | ::ReleaseDC(nullptr, hdc); |
| | | return quint32(dpiX); |
| | | } |
| | | } |
| | | |
| | | static inline quint32 getResizeBorderThickness(HWND hwnd) { |
| | | static inline quint32 getSystemMetricsForDpi(int index, quint32 dpi) { |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (apis.pGetSystemMetricsForDpi) { |
| | | const quint32 dpi = getDpiForWindow(hwnd); |
| | | return apis.pGetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + |
| | | apis.pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); |
| | | } else { |
| | | return ::GetSystemMetrics(SM_CXSIZEFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER); |
| | | return ::GetSystemMetricsForDpi(index, dpi); |
| | | } |
| | | return ::GetSystemMetrics(index); |
| | | } |
| | | |
| | | static inline quint32 getWindowFrameBorderThickness(HWND hwnd) { |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (UINT result = 0; SUCCEEDED(apis.pDwmGetWindowAttribute( |
| | | hwnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &result, sizeof(result)))) { |
| | | return result; |
| | | } |
| | | return getSystemMetricsForDpi(SM_CXBORDER, getDpiForWindow(hwnd)); |
| | | } |
| | | |
| | | static inline quint32 getResizeBorderThickness(HWND hwnd) { |
| | | const quint32 dpi = getDpiForWindow(hwnd); |
| | | return getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + |
| | | getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); |
| | | } |
| | | |
| | | static inline quint32 getTitleBarHeight(HWND hwnd) { |
| | | const auto captionHeight = [hwnd]() -> int { |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (apis.pGetSystemMetricsForDpi) { |
| | | const quint32 dpi = getDpiForWindow(hwnd); |
| | | return apis.pGetSystemMetricsForDpi(SM_CYCAPTION, dpi); |
| | | } else { |
| | | return ::GetSystemMetrics(SM_CYCAPTION); |
| | | } |
| | | }(); |
| | | return captionHeight + getResizeBorderThickness(hwnd); |
| | | const quint32 dpi = getDpiForWindow(hwnd); |
| | | return getSystemMetricsForDpi(SM_CYCAPTION, dpi) + |
| | | getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + |
| | | getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); |
| | | } |
| | | |
| | | static inline void updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) { |
| | | static void updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) { |
| | | const auto margins = [hwnd]() -> QMargins { |
| | | const int titleBarHeight = getTitleBarHeight(hwnd); |
| | | const auto titleBarHeight = int(getTitleBarHeight(hwnd)); |
| | | if (isWin10OrGreater()) { |
| | | return {0, -titleBarHeight, 0, 0}; |
| | | } else { |
| | | const int frameSize = getResizeBorderThickness(hwnd); |
| | | const auto frameSize = int(getResizeBorderThickness(hwnd)); |
| | | return {-frameSize, -titleBarHeight, -frameSize, -frameSize}; |
| | | } |
| | | }(); |
| | |
| | | const auto monitorInfo = getMonitorForWindow(hwnd); |
| | | RECT windowRect{}; |
| | | ::GetWindowRect(hwnd, &windowRect); |
| | | const auto newX = (RECT_WIDTH(monitorInfo.rcMonitor) - RECT_WIDTH(windowRect)) / 2; |
| | | const auto newY = (RECT_HEIGHT(monitorInfo.rcMonitor) - RECT_HEIGHT(windowRect)) / 2; |
| | | const auto newX = monitorInfo.rcMonitor.left + |
| | | (RECT_WIDTH(monitorInfo.rcMonitor) - RECT_WIDTH(windowRect)) / 2; |
| | | const auto newY = monitorInfo.rcMonitor.top + |
| | | (RECT_HEIGHT(monitorInfo.rcMonitor) - RECT_HEIGHT(windowRect)) / 2; |
| | | ::SetWindowPos(hwnd, nullptr, newX, newY, 0, 0, |
| | | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); |
| | | } |
| | |
| | | ::GetWindowPlacement(hwnd, &wp); |
| | | return ((wp.showCmd == SW_NORMAL) || (wp.showCmd == SW_RESTORE)); |
| | | #else |
| | | if (isFullScreen(hwnd)) { |
| | | return false; |
| | | } |
| | | const auto style = static_cast<DWORD>(::GetWindowLongPtrW(hwnd, GWL_STYLE)); |
| | | return (!(style & (WS_MINIMIZE | WS_MAXIMIZE))); |
| | | #endif |
| | | } |
| | | |
| | | static inline void syncPaintEventWithDwm() { |
| | | static void syncPaintEventWithDwm() { |
| | | // No need to sync with DWM if DWM composition is disabled. |
| | | if (!isDwmCompositionEnabled()) { |
| | | return; |
| | |
| | | apis.ptimeEndPeriod(ms_granularity); |
| | | } |
| | | |
| | | static inline void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry, |
| | | const bool fixedSize) { |
| | | const HMENU hMenu = ::GetSystemMenu(hWnd, FALSE); |
| | | static void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry, |
| | | const bool fixedSize) { |
| | | HMENU hMenu = ::GetSystemMenu(hWnd, FALSE); |
| | | if (!hMenu) { |
| | | // The corresponding window doesn't have a system menu, most likely due to the |
| | | // lack of the "WS_SYSMENU" window style. This situation should not be treated |
| | |
| | | case HTBORDER: |
| | | return Win32WindowContext::FixedBorder; |
| | | default: |
| | | break; // unreachable |
| | | // unreachable |
| | | break; |
| | | } |
| | | return Win32WindowContext::Outside; |
| | | } |
| | |
| | | // DefWindowProc(). Consequently, we have to add a global native filter that forwards the result |
| | | // of the hook function, telling Qt whether we have filtered the events before. Since Qt only |
| | | // handles Windows window messages in the main thread, it is safe to do so. |
| | | class WindowsNativeEventFilter : public NativeEventFilter { |
| | | class WindowsNativeEventFilter : public AppNativeEventFilter { |
| | | public: |
| | | bool nativeEventFilter(const QByteArray &eventType, void *message, |
| | | QT_NATIVE_EVENT_RESULT_TYPE *result) override { |
| | |
| | | return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam); |
| | | } |
| | | |
| | | Win32WindowContext::Win32WindowContext() : AbstractWindowContext() { |
| | | } |
| | | |
| | | Win32WindowContext::~Win32WindowContext() { |
| | | // Remove window handle mapping |
| | | if (auto hWnd = reinterpret_cast<HWND>(windowId); hWnd) { |
| | | g_wndProcHash->remove(hWnd); |
| | | |
| | | // Remove event filter if the all windows has been destroyed |
| | | if (g_wndProcHash->empty()) { |
| | | WindowsNativeEventFilter::uninstall(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | QString Win32WindowContext::key() const { |
| | | return "win32"; |
| | | } |
| | | |
| | | void Win32WindowContext::virtual_hook(int id, void *data) { |
| | | switch (id) { |
| | | case ShowSystemMenuHook: { |
| | | const auto &pos = *reinterpret_cast<const QPoint *>(data); |
| | | auto winId = m_windowHandle->winId(); |
| | | auto hWnd = reinterpret_cast<HWND>(winId); |
| | | showSystemMenu2(hWnd, {pos.x(), pos.y()}, false, |
| | | m_delegate->isHostSizeFixed(m_host)); |
| | | return; |
| | | } |
| | | default: |
| | | break; |
| | | } |
| | | AbstractWindowContext::virtual_hook(id, data); |
| | | } |
| | | |
| | | bool Win32WindowContext::setupHost() { |
| | | // Install window hook |
| | | auto winId = m_windowHandle->winId(); |
| | | auto hWnd = reinterpret_cast<HWND>(winId); |
| | | |
| | | // Inform Qt we want and have set custom margins |
| | | updateInternalWindowFrameMargins(hWnd, m_windowHandle); |
| | | |
| | | static inline void addManagedWindow(HWND hWnd, Win32WindowContext *ctx) { |
| | | // Store original window proc |
| | | if (!g_qtWindowProc) { |
| | | g_qtWindowProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hWnd, GWLP_WNDPROC)); |
| | |
| | | // Install global native event filter |
| | | WindowsNativeEventFilter::install(); |
| | | |
| | | // Cache window ID |
| | | windowId = winId; |
| | | |
| | | // Save window handle mapping |
| | | g_wndProcHash->insert(hWnd, this); |
| | | g_wndProcHash->insert(hWnd, ctx); |
| | | } |
| | | |
| | | return true; |
| | | static inline void removeManagedWindow(HWND hWnd) { |
| | | // Remove window handle mapping |
| | | if (!g_wndProcHash->remove(hWnd)) |
| | | return; |
| | | |
| | | // Remove event filter if the all windows has been destroyed |
| | | if (g_wndProcHash->empty()) { |
| | | WindowsNativeEventFilter::uninstall(); |
| | | } |
| | | } |
| | | |
| | | Win32WindowContext::Win32WindowContext() : AbstractWindowContext() { |
| | | } |
| | | |
| | | Win32WindowContext::~Win32WindowContext() { |
| | | if (windowId) { |
| | | removeManagedWindow(reinterpret_cast<HWND>(windowId)); |
| | | } |
| | | } |
| | | |
| | | QString Win32WindowContext::key() const { |
| | | return QStringLiteral("win32"); |
| | | } |
| | | |
| | | void Win32WindowContext::virtual_hook(int id, void *data) { |
| | | switch (id) { |
| | | case CentralizeHook: { |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | moveToDesktopCenter(hwnd); |
| | | return; |
| | | } |
| | | |
| | | case ShowSystemMenuHook: { |
| | | const auto &pos = *static_cast<const QPoint *>(data); |
| | | auto hWnd = reinterpret_cast<HWND>(windowId); |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| | | const QPoint nativeGlobalPos = |
| | | QHighDpi::toNativeGlobalPosition(pos, m_windowHandle); |
| | | #else |
| | | const QPoint nativeGlobalPos = QHighDpi::toNativePixels(pos, m_windowHandle); |
| | | #endif |
| | | showSystemMenu2(hWnd, qpoint2point(nativeGlobalPos), false, |
| | | m_delegate->isHostSizeFixed(m_host)); |
| | | return; |
| | | } |
| | | |
| | | case WindowAttributeChangedHook: { |
| | | auto args = static_cast<void **>(data); |
| | | const auto &key = *static_cast<const QString *>(args[0]); |
| | | const auto &newVar = *static_cast<const QVariant *>(args[1]); |
| | | const auto &oldVar = *static_cast<const QVariant *>(args[2]); |
| | | |
| | | if (key == QStringLiteral("no-frame-shadow")) { |
| | | if (newVar.toBool()) { |
| | | // TODO: set off |
| | | } else { |
| | | // TODO: set on |
| | | } |
| | | } |
| | | |
| | | break; |
| | | } |
| | | |
| | | case DefaultColorsHook: { |
| | | auto &map = *static_cast<QMap<QString, QColor> *>(data); |
| | | map.clear(); |
| | | map.insert(QStringLiteral("activeLight"), kWindowsColorSet.activeLight); |
| | | map.insert(QStringLiteral("activeDark"), kWindowsColorSet.activeDark); |
| | | map.insert(QStringLiteral("inactiveLight"), kWindowsColorSet.inactiveLight); |
| | | map.insert(QStringLiteral("inactiveDark"), kWindowsColorSet.inactiveDark); |
| | | return; |
| | | } |
| | | |
| | | case DrawWindows10BorderHook: { |
| | | auto args = static_cast<void **>(data); |
| | | auto &painter = *static_cast<QPainter *>(args[0]); |
| | | const auto &rect = *static_cast<const QRect *>(args[1]); |
| | | const auto ®ion = *static_cast<const QRegion *>(args[2]); |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | |
| | | QPen pen; |
| | | pen.setWidth(getWindowFrameBorderThickness(hwnd) * 2); |
| | | |
| | | const bool dark = isDarkThemeActive() && isDarkWindowFrameEnabled(hwnd); |
| | | if (m_delegate->isWindowActive(m_host)) { |
| | | if (isWindowFrameBorderColorized()) { |
| | | pen.setColor(getAccentColor()); |
| | | } else { |
| | | static QColor frameBorderActiveColorLight(kWindowsColorSet.activeLight); |
| | | static QColor frameBorderActiveColorDark(kWindowsColorSet.activeDark); |
| | | pen.setColor(dark ? frameBorderActiveColorDark |
| | | : frameBorderActiveColorLight); |
| | | } |
| | | } else { |
| | | static QColor frameBorderInactiveColorLight(kWindowsColorSet.inactiveLight); |
| | | static QColor frameBorderInactiveColorDark(kWindowsColorSet.inactiveDark); |
| | | pen.setColor(dark ? frameBorderInactiveColorDark |
| | | : frameBorderInactiveColorLight); |
| | | } |
| | | painter.save(); |
| | | |
| | | // We needs anti-aliasing to give us better result. |
| | | painter.setRenderHint(QPainter::Antialiasing); |
| | | |
| | | painter.setPen(pen); |
| | | painter.drawLine(QLine{ |
| | | QPoint{0, 0}, |
| | | QPoint{m_windowHandle->width(), 0} |
| | | }); |
| | | painter.restore(); |
| | | return; |
| | | } |
| | | |
| | | default: { |
| | | // unreachable |
| | | break; |
| | | } |
| | | } |
| | | AbstractWindowContext::virtual_hook(id, data); |
| | | } |
| | | |
| | | bool Win32WindowContext::needBorderPainter() const { |
| | | return isWin10OrGreater() && !isWin11OrGreater(); |
| | | } |
| | | |
| | | int Win32WindowContext::borderThickness() const { |
| | | return getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId)); |
| | | } |
| | | |
| | | void Win32WindowContext::winIdChanged(QWindow *oldWindow) { |
| | | removeManagedWindow(reinterpret_cast<HWND>(windowId)); |
| | | if (!m_windowHandle) { |
| | | return; |
| | | } |
| | | |
| | | // Install window hook |
| | | auto winId = m_windowHandle->winId(); |
| | | auto hWnd = reinterpret_cast<HWND>(winId); |
| | | |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) |
| | | for (const auto attr : { |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE, |
| | | }) { |
| | | const BOOL enable = TRUE; |
| | | DynamicApis::instance().pDwmSetWindowAttribute(hWnd, attr, &enable, sizeof(enable)); |
| | | } |
| | | #endif |
| | | |
| | | // Inform Qt we want and have set custom margins |
| | | updateInternalWindowFrameMargins(hWnd, m_windowHandle); |
| | | |
| | | // Add managed window |
| | | addManagedWindow(hWnd, this); |
| | | |
| | | // Cache win id |
| | | windowId = winId; |
| | | } |
| | | |
| | | bool Win32WindowContext::windowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, |
| | |
| | | return true; |
| | | } |
| | | |
| | | // Whether to show system menu |
| | | if (systemMenuHandler(hWnd, message, wParam, lParam, result)) { |
| | | return true; |
| | | } |
| | | |
| | | // Forward to native event filter subscribers |
| | | if (!m_nativeEventFilters.isEmpty()) { |
| | | MSG msg; |
| | | msg.hwnd = hWnd; |
| | | msg.message = message; |
| | | msg.wParam = wParam; |
| | | msg.lParam = lParam; |
| | | QT_NATIVE_EVENT_RESULT_TYPE res = 0; |
| | | if (dispatch(QByteArrayLiteral("windows_generic_MSG"), &msg, &res)) { |
| | | *result = LRESULT(res); |
| | | return true; |
| | | } |
| | | } |
| | | return false; // Not handled |
| | | } |
| | | |
| | | static constexpr const auto kMessageTag = WPARAM(0xF1C9ADD4); |
| | | |
| | | static inline constexpr bool isTaggedMessage(WPARAM wParam) { |
| | | return (wParam == kMessageTag); |
| | | } |
| | | QWK_USED static constexpr const struct { |
| | | const WPARAM wParam = MAKEWPARAM(44500, 61897); |
| | | const LPARAM lParam = MAKELPARAM(62662, 44982); // Not used. Reserve for future use. |
| | | } kMessageTag; |
| | | |
| | | static inline quint64 getKeyState() { |
| | | quint64 result = 0; |
| | |
| | | // wParam is always ignored in mouse leave messages, but here we |
| | | // give them a special tag to be able to distinguish which messages |
| | | // are sent by ourselves. |
| | | return kMessageTag; |
| | | return kMessageTag.wParam; |
| | | } |
| | | const quint64 keyState = getKeyState(); |
| | | if ((myMsg >= WM_NCXBUTTONDOWN) && (myMsg <= WM_NCXBUTTONDBLCLK)) { |
| | |
| | | SEND_MESSAGE(hWnd, WM_MOUSELEAVE, wParamNew, lParamNew); |
| | | break; |
| | | default: |
| | | // unreachable |
| | | break; |
| | | } |
| | | |
| | |
| | | LPARAM lParam, LRESULT *result) { |
| | | switch (message) { |
| | | case WM_MOUSELEAVE: { |
| | | if (!isTaggedMessage(wParam)) { |
| | | if (wParam != kMessageTag.wParam) { |
| | | // Qt will call TrackMouseEvent() to get the WM_MOUSELEAVE message when it |
| | | // receives WM_MOUSEMOVE messages, and since we are converting every |
| | | // WM_NCMOUSEMOVE message to WM_MOUSEMOVE message and send it back to the window |
| | |
| | | const WindowPart currentWindowPart = lastHitTestResult; |
| | | if (message == WM_NCMOUSEMOVE) { |
| | | if (currentWindowPart != WindowPart::ChromeButton) { |
| | | m_delegate->resetQtGrabbedControl(); |
| | | m_delegate->resetQtGrabbedControl(m_host); |
| | | if (mouseLeaveBlocked) { |
| | | emulateClientAreaMessage(hWnd, message, wParam, lParam, |
| | | WM_NCMOUSELEAVE); |
| | |
| | | // window from client area, which means we will get previous window part as |
| | | // HTCLIENT if the mouse leaves window from client area and enters window |
| | | // from non-client area, but it has no bad effect. |
| | | m_delegate->resetQtGrabbedControl(); |
| | | m_delegate->resetQtGrabbedControl(m_host); |
| | | } |
| | | } |
| | | break; |
| | |
| | | // If wParam is TRUE, the window is being shown. |
| | | // If lParam is zero, the message was sent because of a call to the ShowWindow |
| | | // function. |
| | | if (wParam && lParam == 0) { |
| | | if (wParam && !lParam) { |
| | | centered = true; |
| | | moveToDesktopCenter(hWnd); |
| | | } |
| | | } |
| | | break; |
| | | } |
| | | |
| | | case WM_NCHITTEST: { |
| | | // 原生Win32窗口只有顶边是在窗口内部resize的,其余三边都是在窗口 |
| | | // 外部进行resize的,其原理是,WS_THICKFRAME这个窗口样式会在窗 |
| | |
| | | // this is also the normal behavior of a native Win32 window (but only when the |
| | | // window is not maximized/fullscreen/minimized, of course). |
| | | if (isWindowNoState(hWnd)) { |
| | | static constexpr const auto kBorderSize = quint8{2}; |
| | | static constexpr const quint8 kBorderSize = 2; |
| | | bool isTop = (nativeLocalPos.y <= kBorderSize); |
| | | bool isLeft = nativeLocalPos.x <= kBorderSize; |
| | | bool isRight = (nativeLocalPos.x >= (clientWidth - kBorderSize)); |
| | |
| | | *result = HTCLOSE; |
| | | break; |
| | | default: |
| | | break; // unreachable |
| | | // unreachable |
| | | break; |
| | | } |
| | | } |
| | | if (*result == HTNOWHERE) { |
| | |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | case WM_WINDOWPOSCHANGING: { |
| | | // ### FIXME: How does this problem happen and why is it solved? |
| | | // When toggling the "Show theme color in title bar and window border" setting in |
| | | // Windows Settings, or calling `DrawMenuBar()`, Windows sends a message of |
| | | // WM_WINDOWPOSCHANGING with flags 0x37. If we do not process this message, |
| | | // the client area as a whole will shift to the left, which looks very abnormal if |
| | | // we don't repaint it. This exception disappears if we add SWP_NOCOPYBITS flag. |
| | | // But I don't know what caused the problem, or why this would solve it. |
| | | static constexpr const auto kBadWindowPosFlag = |
| | | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED; |
| | | const auto windowPos = reinterpret_cast<LPWINDOWPOS>(lParam); |
| | | if (windowPos->flags == kBadWindowPosFlag) { |
| | | windowPos->flags |= SWP_NOCOPYBITS; |
| | | } |
| | | break; |
| | | } |
| | | |
| | | default: |
| | | break; |
| | | } |
| | | |
| | | if (!isWin10OrGreater()) { |
| | | switch (message) { |
| | | case WM_NCUAHDRAWCAPTION: |