| | |
| | | // 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 const struct QWK_Hook { |
| | | QWK_Hook() { |
| | | qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); |
| | | } |
| | | } g_hook{}; |
| | | |
| | | struct DynamicApis { |
| | | static const DynamicApis &instance() { |
| | | static const DynamicApis inst{}; |
| | |
| | | monitorInfo.cbSize = sizeof(monitorInfo); |
| | | ::GetMonitorInfoW(monitor, &monitorInfo); |
| | | return monitorInfo; |
| | | }; |
| | | } |
| | | |
| | | static inline void moveToDesktopCenter(HWND hwnd) { |
| | | const auto monitorInfo = getMonitorForWindow(hwnd); |
| | | static inline void moveWindowToDesktopCenter(HWND hwnd) { |
| | | MONITORINFOEXW monitorInfo = getMonitorForWindow(hwnd); |
| | | RECT windowRect{}; |
| | | ::GetWindowRect(hwnd, &windowRect); |
| | | 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; |
| | | 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); |
| | | } |
| | | |
| | | static inline void moveWindowToMonitor(HWND hwnd, const MONITORINFOEXW &activeMonitor) { |
| | | RECT currentMonitorRect = getMonitorForWindow(hwnd).rcMonitor; |
| | | RECT activeMonitorRect = activeMonitor.rcMonitor; |
| | | // We are in the same monitor, nothing to adjust here. |
| | | if (currentMonitorRect == activeMonitorRect) { |
| | | return; |
| | | } |
| | | RECT currentWindowRect{}; |
| | | ::GetWindowRect(hwnd, ¤tWindowRect); |
| | | auto newWindowX = activeMonitorRect.left + (currentWindowRect.left - currentMonitorRect.left); |
| | | auto newWindowY = activeMonitorRect.top + (currentWindowRect.top - currentMonitorRect.top); |
| | | ::SetWindowPos(hwnd, nullptr, newWindowX, newWindowY, RECT_WIDTH(currentWindowRect), RECT_HEIGHT(currentWindowRect), SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); |
| | | } |
| | | |
| | | static inline void bringWindowToFront(HWND hwnd) { |
| | | HWND oldForegroundWindow = ::GetForegroundWindow(); |
| | | if (!oldForegroundWindow) { |
| | | // The foreground window can be NULL, it's not an API error. |
| | | return; |
| | | } |
| | | MONITORINFOEXW activeMonitor = getMonitorForWindow(oldForegroundWindow); |
| | | // We need to show the window first, otherwise we won't be able to bring it to front. |
| | | if (!::IsWindowVisible(hwnd)) { |
| | | ::ShowWindow(hwnd, SW_SHOW); |
| | | } |
| | | if (IsMinimized(hwnd)) { |
| | | // Restore the window if it is minimized. |
| | | ::ShowWindow(hwnd, SW_RESTORE); |
| | | // Once we've been restored, throw us on the active monitor. |
| | | moveWindowToMonitor(hwnd, activeMonitor); |
| | | // When the window is restored, it will always become the foreground window. |
| | | // So return early here, we don't need the following code to bring it to front. |
| | | return; |
| | | } |
| | | // OK, our window is not minimized, so now we will try to bring it to front manually. |
| | | // First try to send a message to the current foreground window to check whether |
| | | // it is currently hanging or not. |
| | | if (!::SendMessageTimeoutW(oldForegroundWindow, WM_NULL, 0, 0, |
| | | SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG, 1000, nullptr)) { |
| | | // The foreground window hangs, can't activate current window. |
| | | return; |
| | | } |
| | | DWORD windowThreadProcessId = ::GetWindowThreadProcessId(oldForegroundWindow, nullptr); |
| | | DWORD currentThreadId = ::GetCurrentThreadId(); |
| | | // We won't be able to change a window's Z order if it's not our own window, |
| | | // so we use this small technique to pretend the foreground window is ours. |
| | | ::AttachThreadInput(windowThreadProcessId, currentThreadId, TRUE); |
| | | QWK_USED struct Cleaner { |
| | | Cleaner(DWORD idAttach, DWORD idAttachTo) : m_idAttach(idAttach), m_idAttachTo(idAttachTo) {} |
| | | ~Cleaner() { |
| | | ::AttachThreadInput(m_idAttach, m_idAttachTo, FALSE); |
| | | } |
| | | private: |
| | | Q_DISABLE_COPY(Cleaner) |
| | | DWORD m_idAttach; |
| | | DWORD m_idAttachTo; |
| | | } cleaner{ windowThreadProcessId, currentThreadId }; |
| | | ::BringWindowToTop(hwnd); |
| | | // Activate the window too. This will force us to the virtual desktop this |
| | | // window is on, if it's on another virtual desktop. |
| | | ::SetActiveWindow(hwnd); |
| | | // Throw us on the active monitor. |
| | | moveWindowToMonitor(hwnd, activeMonitor); |
| | | } |
| | | |
| | | static inline bool isFullScreen(HWND hwnd) { |
| | |
| | | return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam); |
| | | } |
| | | |
| | | static inline void addManagedWindow(HWND hWnd, Win32WindowContext *ctx) { |
| | | // Store original window proc |
| | | if (!g_qtWindowProc) { |
| | | g_qtWindowProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hWnd, GWLP_WNDPROC)); |
| | | } |
| | | |
| | | // Hook window proc |
| | | ::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(QWKHookedWndProc)); |
| | | |
| | | // Install global native event filter |
| | | WindowsNativeEventFilter::install(); |
| | | |
| | | // Save window handle mapping |
| | | g_wndProcHash->insert(hWnd, ctx); |
| | | } |
| | | |
| | | 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() { |
| | | // 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(); |
| | | } |
| | | if (windowId) { |
| | | removeManagedWindow(reinterpret_cast<HWND>(windowId)); |
| | | } |
| | | } |
| | | |
| | |
| | | void Win32WindowContext::virtual_hook(int id, void *data) { |
| | | switch (id) { |
| | | case CentralizeHook: { |
| | | const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId()); |
| | | moveToDesktopCenter(hwnd); |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | moveWindowToDesktopCenter(hwnd); |
| | | return; |
| | | } |
| | | |
| | | case RaiseWindowHook: { |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | bringWindowToFront(hwnd); |
| | | return; |
| | | } |
| | | |
| | | case ShowSystemMenuHook: { |
| | | const auto &pos = *static_cast<const QPoint *>(data); |
| | | auto hWnd = reinterpret_cast<HWND>(m_windowHandle->winId()); |
| | | auto hWnd = reinterpret_cast<HWND>(windowId); |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| | | const QPoint nativeGlobalPos = QHighDpi::toNativeGlobalPosition(pos, m_windowHandle); |
| | | 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 &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>(m_windowHandle->winId()); |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | |
| | | QPen pen; |
| | | pen.setWidth(getWindowFrameBorderThickness(hwnd) * 2); |
| | |
| | | return getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId)); |
| | | } |
| | | |
| | | bool Win32WindowContext::setupHost() { |
| | | 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 }) { |
| | | 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)); |
| | | } |
| | |
| | | // Inform Qt we want and have set custom margins |
| | | updateInternalWindowFrameMargins(hWnd, m_windowHandle); |
| | | |
| | | // Store original window proc |
| | | if (!g_qtWindowProc) { |
| | | g_qtWindowProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hWnd, GWLP_WNDPROC)); |
| | | } |
| | | // Add managed window |
| | | addManagedWindow(hWnd, this); |
| | | |
| | | // Hook window proc |
| | | ::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(QWKHookedWndProc)); |
| | | |
| | | // Install global native event filter |
| | | WindowsNativeEventFilter::install(); |
| | | |
| | | // Cache window ID |
| | | // Cache win id |
| | | windowId = winId; |
| | | |
| | | // Save window handle mapping |
| | | g_wndProcHash->insert(hWnd, this); |
| | | |
| | | return true; |
| | | } |
| | | |
| | | bool Win32WindowContext::windowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, |
| | |
| | | // function. |
| | | if (wParam && !lParam) { |
| | | centered = true; |
| | | moveToDesktopCenter(hWnd); |
| | | moveWindowToDesktopCenter(hWnd); |
| | | } |
| | | } |
| | | break; |
| | |
| | | // 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 == |
| | | (SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED)) { |
| | | if (windowPos->flags == kBadWindowPosFlag) { |
| | | windowPos->flags |= SWP_NOCOPYBITS; |
| | | } |
| | | break; |