| | |
| | | |
| | | #include <QtCore/QHash> |
| | | #include <QtCore/QScopeGuard> |
| | | #include <QtCore/QTimer> |
| | | #include <QtGui/QGuiApplication> |
| | | #include <QtGui/QPainter> |
| | | #include <QtGui/QPalette> |
| | |
| | | #include <dwmapi.h> |
| | | #include <timeapi.h> |
| | | |
| | | #include "nativeeventfilter.h" |
| | | #include "qwkglobal_p.h" |
| | | |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | |
| | | // ### FIXME: Tell the user to call in the documentation, instead of automatically |
| | | // calling it directly. |
| | | // ### FIXME FIXME FIXME |
| | | static struct QWK_Hook { |
| | | static const struct QWK_Hook { |
| | | QWK_Hook() { |
| | | qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); |
| | | } |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | 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 |
| | |
| | | // 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 { |
| | |
| | | case ShowSystemMenuHook: { |
| | | const auto &pos = *static_cast<const QPoint *>(data); |
| | | auto hWnd = reinterpret_cast<HWND>(m_windowHandle->winId()); |
| | | showSystemMenu2(hWnd, qpoint2point(pos), false, |
| | | #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 NeedsDrawBordersHook: { |
| | | auto &result = *static_cast<bool *>(data); |
| | | result = isWin10OrGreater() && !isWin11OrGreater(); |
| | | 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 BorderThicknessHook: { |
| | | auto args = static_cast<void **>(data); |
| | | const bool requireNative = *static_cast<const bool *>(args[0]); |
| | | quint32 &thickness = *static_cast<quint32 *>(args[1]); |
| | | const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId()); |
| | | const auto nativeThickness = getWindowFrameBorderThickness(hwnd); |
| | | thickness = requireNative |
| | | ? nativeThickness |
| | | : QHighDpi::fromNativePixels(nativeThickness, m_windowHandle); |
| | | return; |
| | | } |
| | | |
| | | case BorderColorsHook: { |
| | | auto arr = *reinterpret_cast<QList<QColor> *>(data); |
| | | arr.clear(); |
| | | arr.push_back(kWindowsColorSet.activeLight); |
| | | arr.push_back(kWindowsColorSet.activeDark); |
| | | arr.push_back(kWindowsColorSet.inactiveLight); |
| | | arr.push_back(kWindowsColorSet.inactiveDark); |
| | | return; |
| | | } |
| | | |
| | | case DrawBordersHook: { |
| | | 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 hwnd = reinterpret_cast<HWND>(m_windowHandle->winId()); |
| | | |
| | | QPen pen; |
| | | const auto borderThickness = int(QHighDpi::fromNativePixels( |
| | | getWindowFrameBorderThickness(hwnd), m_windowHandle)); |
| | | pen.setWidth(borderThickness * 2); |
| | | const bool active = m_delegate->isWindowActive(m_host); |
| | | const bool dark = isDarkThemeActive() && isDarkWindowFrameEnabled(hwnd); |
| | | pen.setWidth(getWindowFrameBorderThickness(hwnd) * 2); |
| | | |
| | | if (active) { |
| | | const bool dark = isDarkThemeActive() && isDarkWindowFrameEnabled(hwnd); |
| | | if (m_delegate->isWindowActive(m_host)) { |
| | | if (isWindowFrameBorderColorized()) { |
| | | pen.setColor(getAccentColor()); |
| | | } else { |
| | |
| | | } |
| | | painter.save(); |
| | | |
| | | // ### TODO: do we need to enable or disable it? |
| | | // We needs anti-aliasing to give us better result. |
| | | painter.setRenderHint(QPainter::Antialiasing); |
| | | |
| | | painter.setPen(pen); |
| | | painter.drawLine(QLine{ |
| | | QPoint{0, 0}, |
| | | QPoint{rect.width(), 0} |
| | | QPoint{0, 0}, |
| | | QPoint{m_windowHandle->width(), 0} |
| | | }); |
| | | painter.restore(); |
| | | return; |
| | |
| | | AbstractWindowContext::virtual_hook(id, data); |
| | | } |
| | | |
| | | bool Win32WindowContext::needBorderPainter() const { |
| | | return isWin10OrGreater() && !isWin11OrGreater(); |
| | | } |
| | | |
| | | int Win32WindowContext::borderThickness() const { |
| | | return getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId)); |
| | | } |
| | | |
| | | bool Win32WindowContext::setupHost() { |
| | | // 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); |
| | |
| | | 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 |
| | | } |
| | | |
| | | QWK_USED static constexpr const struct { |
| | | const WPARAM wParam = 0xF1C9ADD4; |
| | | const LPARAM lParam = 0xAFB6F4C6; |
| | | const WPARAM wParam = MAKEWPARAM(44500, 61897); |
| | | const LPARAM lParam = MAKELPARAM(62662, 44982); // Not used. Reserve for future use. |
| | | } kMessageTag; |
| | | |
| | | static inline quint64 getKeyState() { |
| | |
| | | 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); |
| | | } |