| | |
| | | #include "qwkglobal_p.h" |
| | | #include "qwkwindowsextra_p.h" |
| | | |
| | | static QHash<int, const wchar_t *> xmsgHash = // |
| | | { |
| | | // |
| | | {0, L"WM_NULL" }, |
| | | {1, L"WM_CREATE" }, |
| | | {2, L"WM_DESTROY" }, |
| | | {3, L"WM_MOVE" }, |
| | | {5, L"WM_SIZE" }, |
| | | {6, L"WM_ACTIVATE" }, |
| | | {7, L"WM_SETFOCUS" }, |
| | | {8, L"WM_KILLFOCUS" }, |
| | | {10, L"WM_ENABLE" }, |
| | | {11, L"WM_SETREDRAW" }, |
| | | {12, L"WM_SETTEXT" }, |
| | | {13, L"WM_GETTEXT" }, |
| | | {14, L"WM_GETTEXTLENGTH" }, |
| | | {15, L"WM_PAINT" }, |
| | | {16, L"WM_CLOSE" }, |
| | | {17, L"WM_QUERYENDSESSION" }, |
| | | {18, L"WM_QUIT" }, |
| | | {19, L"WM_QUERYOPEN" }, |
| | | {20, L"WM_ERASEBKGND" }, |
| | | {21, L"WM_SYSCOLORCHANGE" }, |
| | | {22, L"WM_ENDSESSION" }, |
| | | {24, L"WM_SHOWWINDOW" }, |
| | | {25, L"WM_CTLCOLOR" }, |
| | | {26, L"WM_WININICHANGE" }, |
| | | {27, L"WM_DEVMODECHANGE" }, |
| | | {28, L"WM_ACTIVATEAPP" }, |
| | | {29, L"WM_FONTCHANGE" }, |
| | | {30, L"WM_TIMECHANGE" }, |
| | | {31, L"WM_CANCELMODE" }, |
| | | {32, L"WM_SETCURSOR" }, |
| | | {33, L"WM_MOUSEACTIVATE" }, |
| | | {34, L"WM_CHILDACTIVATE" }, |
| | | {35, L"WM_QUEUESYNC" }, |
| | | {36, L"WM_GETMINMAXINFO" }, |
| | | {38, L"WM_PAINTICON" }, |
| | | {39, L"WM_ICONERASEBKGND" }, |
| | | {40, L"WM_NEXTDLGCTL" }, |
| | | {42, L"WM_SPOOLERSTATUS" }, |
| | | {43, L"WM_DRAWITEM" }, |
| | | {44, L"WM_MEASUREITEM" }, |
| | | {45, L"WM_DELETEITEM" }, |
| | | {46, L"WM_VKEYTOITEM" }, |
| | | {47, L"WM_CHARTOITEM" }, |
| | | {48, L"WM_SETFONT" }, |
| | | {49, L"WM_GETFONT" }, |
| | | {50, L"WM_SETHOTKEY" }, |
| | | {51, L"WM_GETHOTKEY" }, |
| | | {55, L"WM_QUERYDRAGICON" }, |
| | | {57, L"WM_COMPAREITEM" }, |
| | | {61, L"WM_GETOBJECT" }, |
| | | {65, L"WM_COMPACTING" }, |
| | | {68, L"WM_COMMNOTIFY" }, |
| | | {70, L"WM_WINDOWPOSCHANGING" }, |
| | | {71, L"WM_WINDOWPOSCHANGED" }, |
| | | {72, L"WM_POWER" }, |
| | | {73, L"WM_COPYGLOBALDATA" }, |
| | | {74, L"WM_COPYDATA" }, |
| | | {75, L"WM_CANCELJOURNAL" }, |
| | | {78, L"WM_NOTIFY" }, |
| | | {80, L"WM_INPUTLANGCHANGEREQUEST"}, |
| | | {81, L"WM_INPUTLANGCHANGE" }, |
| | | {82, L"WM_TCARD" }, |
| | | {83, L"WM_HELP" }, |
| | | {84, L"WM_USERCHANGED" }, |
| | | {85, L"WM_NOTIFYFORMAT" }, |
| | | {123, L"WM_CONTEXTMENU" }, |
| | | {124, L"WM_STYLECHANGING" }, |
| | | {125, L"WM_STYLECHANGED" }, |
| | | {126, L"WM_DISPLAYCHANGE" }, |
| | | {127, L"WM_GETICON" }, |
| | | {128, L"WM_SETICON" }, |
| | | {129, L"WM_NCCREATE" }, |
| | | {130, L"WM_NCDESTROY" }, |
| | | {131, L"WM_NCCALCSIZE" }, |
| | | {132, L"WM_NCHITTEST" }, |
| | | {133, L"WM_NCPAINT" }, |
| | | {134, L"WM_NCACTIVATE" }, |
| | | {135, L"WM_GETDLGCODE" }, |
| | | {136, L"WM_SYNCPAINT" }, |
| | | {160, L"WM_NCMOUSEMOVE" }, |
| | | {161, L"WM_NCLBUTTONDOWN" }, |
| | | {162, L"WM_NCLBUTTONUP" }, |
| | | {163, L"WM_NCLBUTTONDBLCLK" }, |
| | | {164, L"WM_NCRBUTTONDOWN" }, |
| | | {165, L"WM_NCRBUTTONUP" }, |
| | | {166, L"WM_NCRBUTTONDBLCLK" }, |
| | | {167, L"WM_NCMBUTTONDOWN" }, |
| | | {168, L"WM_NCMBUTTONUP" }, |
| | | {169, L"WM_NCMBUTTONDBLCLK" }, |
| | | {171, L"WM_NCXBUTTONDOWN" }, |
| | | {172, L"WM_NCXBUTTONUP" }, |
| | | {173, L"WM_NCXBUTTONDBLCLK" }, |
| | | {176, L"EM_GETSEL" }, |
| | | {177, L"EM_SETSEL" }, |
| | | {178, L"EM_GETRECT" }, |
| | | {179, L"EM_SETRECT" }, |
| | | {180, L"EM_SETRECTNP" }, |
| | | {181, L"EM_SCROLL" }, |
| | | {182, L"EM_LINESCROLL" }, |
| | | {183, L"EM_SCROLLCARET" }, |
| | | {185, L"EM_GETMODIFY" }, |
| | | {187, L"EM_SETMODIFY" }, |
| | | {188, L"EM_GETLINECOUNT" }, |
| | | {189, L"EM_LINEINDEX" }, |
| | | {190, L"EM_SETHANDLE" }, |
| | | {191, L"EM_GETHANDLE" }, |
| | | {192, L"EM_GETTHUMB" }, |
| | | {193, L"EM_LINELENGTH" }, |
| | | {194, L"EM_REPLACESEL" }, |
| | | {195, L"EM_SETFONT" }, |
| | | {196, L"EM_GETLINE" }, |
| | | {197, L"EM_LIMITTEXT" }, |
| | | {197, L"EM_SETLIMITTEXT" }, |
| | | {198, L"EM_CANUNDO" }, |
| | | {199, L"EM_UNDO" }, |
| | | {200, L"EM_FMTLINES" }, |
| | | {201, L"EM_LINEFROMCHAR" }, |
| | | {202, L"EM_SETWORDBREAK" }, |
| | | {203, L"EM_SETTABSTOPS" }, |
| | | {204, L"EM_SETPASSWORDCHAR" }, |
| | | {205, L"EM_EMPTYUNDOBUFFER" }, |
| | | {206, L"EM_GETFIRSTVISIBLELINE" }, |
| | | {207, L"EM_SETREADONLY" }, |
| | | {209, L"EM_SETWORDBREAKPROC" }, |
| | | {209, L"EM_GETWORDBREAKPROC" }, |
| | | {210, L"EM_GETPASSWORDCHAR" }, |
| | | {211, L"EM_SETMARGINS" }, |
| | | {212, L"EM_GETMARGINS" }, |
| | | {213, L"EM_GETLIMITTEXT" }, |
| | | {214, L"EM_POSFROMCHAR" }, |
| | | {215, L"EM_CHARFROMPOS" }, |
| | | {216, L"EM_SETIMESTATUS" }, |
| | | {217, L"EM_GETIMESTATUS" }, |
| | | {224, L"SBM_SETPOS" }, |
| | | {225, L"SBM_GETPOS" }, |
| | | {226, L"SBM_SETRANGE" }, |
| | | {227, L"SBM_GETRANGE" }, |
| | | {228, L"SBM_ENABLE_ARROWS" }, |
| | | {230, L"SBM_SETRANGEREDRAW" }, |
| | | {233, L"SBM_SETSCROLLINFO" }, |
| | | {234, L"SBM_GETSCROLLINFO" }, |
| | | {235, L"SBM_GETSCROLLBARINFO" }, |
| | | {240, L"BM_GETCHECK" }, |
| | | {241, L"BM_SETCHECK" }, |
| | | {242, L"BM_GETSTATE" }, |
| | | {243, L"BM_SETSTATE" }, |
| | | {244, L"BM_SETSTYLE" }, |
| | | {245, L"BM_CLICK" }, |
| | | {246, L"BM_GETIMAGE" }, |
| | | {247, L"BM_SETIMAGE" }, |
| | | {248, L"BM_SETDONTCLICK" }, |
| | | {255, L"WM_INPUT" }, |
| | | {256, L"WM_KEYDOWN" }, |
| | | {256, L"WM_KEYFIRST" }, |
| | | {257, L"WM_KEYUP" }, |
| | | {258, L"WM_CHAR" }, |
| | | {259, L"WM_DEADCHAR" }, |
| | | {260, L"WM_SYSKEYDOWN" }, |
| | | {261, L"WM_SYSKEYUP" }, |
| | | {262, L"WM_SYSCHAR" }, |
| | | {263, L"WM_SYSDEADCHAR" }, |
| | | {264, L"WM_KEYLAST" }, |
| | | {265, L"WM_UNICHAR" }, |
| | | {265, L"WM_WNT_CONVERTREQUESTEX" }, |
| | | {266, L"WM_CONVERTREQUEST" }, |
| | | {267, L"WM_CONVERTRESULT" }, |
| | | {268, L"WM_INTERIM" }, |
| | | {269, L"WM_IME_STARTCOMPOSITION" }, |
| | | {270, L"WM_IME_ENDCOMPOSITION" }, |
| | | {271, L"WM_IME_COMPOSITION" }, |
| | | {271, L"WM_IME_KEYLAST" }, |
| | | {272, L"WM_INITDIALOG" }, |
| | | {273, L"WM_COMMAND" }, |
| | | {274, L"WM_SYSCOMMAND" }, |
| | | {275, L"WM_TIMER" }, |
| | | {276, L"WM_HSCROLL" }, |
| | | {277, L"WM_VSCROLL" }, |
| | | {278, L"WM_INITMENU" }, |
| | | {279, L"WM_INITMENUPOPUP" }, |
| | | {280, L"WM_SYSTIMER" }, |
| | | {287, L"WM_MENUSELECT" }, |
| | | {288, L"WM_MENUCHAR" }, |
| | | {289, L"WM_ENTERIDLE" }, |
| | | {290, L"WM_MENURBUTTONUP" }, |
| | | {291, L"WM_MENUDRAG" }, |
| | | {292, L"WM_MENUGETOBJECT" }, |
| | | {293, L"WM_UNINITMENUPOPUP" }, |
| | | {294, L"WM_MENUCOMMAND" }, |
| | | {295, L"WM_CHANGEUISTATE" }, |
| | | {296, L"WM_UPDATEUISTATE" }, |
| | | {297, L"WM_QUERYUISTATE" }, |
| | | {306, L"WM_CTLCOLORMSGBOX" }, |
| | | {307, L"WM_CTLCOLOREDIT" }, |
| | | {308, L"WM_CTLCOLORLISTBOX" }, |
| | | {309, L"WM_CTLCOLORBTN" }, |
| | | {310, L"WM_CTLCOLORDLG" }, |
| | | {311, L"WM_CTLCOLORSCROLLBAR" }, |
| | | {312, L"WM_CTLCOLORSTATIC" }, |
| | | {512, L"WM_MOUSEFIRST" }, |
| | | {512, L"WM_MOUSEMOVE" }, |
| | | {513, L"WM_LBUTTONDOWN" }, |
| | | {514, L"WM_LBUTTONUP" }, |
| | | {515, L"WM_LBUTTONDBLCLK" }, |
| | | {516, L"WM_RBUTTONDOWN" }, |
| | | {517, L"WM_RBUTTONUP" }, |
| | | {518, L"WM_RBUTTONDBLCLK" }, |
| | | {519, L"WM_MBUTTONDOWN" }, |
| | | {520, L"WM_MBUTTONUP" }, |
| | | {521, L"WM_MBUTTONDBLCLK" }, |
| | | {521, L"WM_MOUSELAST" }, |
| | | {522, L"WM_MOUSEWHEEL" }, |
| | | {523, L"WM_XBUTTONDOWN" }, |
| | | {524, L"WM_XBUTTONUP" }, |
| | | {525, L"WM_XBUTTONDBLCLK" }, |
| | | {528, L"WM_PARENTNOTIFY" }, |
| | | {529, L"WM_ENTERMENULOOP" }, |
| | | {530, L"WM_EXITMENULOOP" }, |
| | | {531, L"WM_NEXTMENU" }, |
| | | {532, L"WM_SIZING" }, |
| | | {533, L"WM_CAPTURECHANGED" }, |
| | | {534, L"WM_MOVING" }, |
| | | {536, L"WM_POWERBROADCAST" }, |
| | | {537, L"WM_DEVICECHANGE" }, |
| | | {544, L"WM_MDICREATE" }, |
| | | {545, L"WM_MDIDESTROY" }, |
| | | {546, L"WM_MDIACTIVATE" }, |
| | | {547, L"WM_MDIRESTORE" }, |
| | | {548, L"WM_MDINEXT" }, |
| | | {549, L"WM_MDIMAXIMIZE" }, |
| | | {550, L"WM_MDITILE" }, |
| | | {551, L"WM_MDICASCADE" }, |
| | | {552, L"WM_MDIICONARRANGE" }, |
| | | {553, L"WM_MDIGETACTIVE" }, |
| | | {560, L"WM_MDISETMENU" }, |
| | | {561, L"WM_ENTERSIZEMOVE" }, |
| | | {562, L"WM_EXITSIZEMOVE" }, |
| | | {563, L"WM_DROPFILES" }, |
| | | {564, L"WM_MDIREFRESHMENU" }, |
| | | {640, L"WM_IME_REPORT" }, |
| | | {641, L"WM_IME_SETCONTEXT" }, |
| | | {642, L"WM_IME_NOTIFY" }, |
| | | {643, L"WM_IME_CONTROL" }, |
| | | {644, L"WM_IME_COMPOSITIONFULL" }, |
| | | {645, L"WM_IME_SELECT" }, |
| | | {646, L"WM_IME_CHAR" }, |
| | | {648, L"WM_IME_REQUEST" }, |
| | | {656, L"WM_IMEKEYDOWN" }, |
| | | {656, L"WM_IME_KEYDOWN" }, |
| | | {657, L"WM_IMEKEYUP" }, |
| | | {657, L"WM_IME_KEYUP" }, |
| | | {672, L"WM_NCMOUSEHOVER" }, |
| | | {673, L"WM_MOUSEHOVER" }, |
| | | {674, L"WM_NCMOUSELEAVE" }, |
| | | {675, L"WM_MOUSELEAVE" }, |
| | | {768, L"WM_CUT" }, |
| | | {769, L"WM_COPY" }, |
| | | {770, L"WM_PASTE" }, |
| | | {771, L"WM_CLEAR" }, |
| | | {772, L"WM_UNDO" }, |
| | | {773, L"WM_RENDERFORMAT" }, |
| | | {774, L"WM_RENDERALLFORMATS" }, |
| | | {775, L"WM_DESTROYCLIPBOARD" }, |
| | | {776, L"WM_DRAWCLIPBOARD" }, |
| | | {777, L"WM_PAINTCLIPBOARD" }, |
| | | {778, L"WM_VSCROLLCLIPBOARD" }, |
| | | {779, L"WM_SIZECLIPBOARD" }, |
| | | {780, L"WM_ASKCBFORMATNAME" }, |
| | | {781, L"WM_CHANGECBCHAIN" }, |
| | | {782, L"WM_HSCROLLCLIPBOARD" }, |
| | | {783, L"WM_QUERYNEWPALETTE" }, |
| | | {784, L"WM_PALETTEISCHANGING" }, |
| | | {785, L"WM_PALETTECHANGED" }, |
| | | {786, L"WM_HOTKEY" }, |
| | | {791, L"WM_PRINT" }, |
| | | {792, L"WM_PRINTCLIENT" }, |
| | | {793, L"WM_APPCOMMAND" }, |
| | | {856, L"WM_HANDHELDFIRST" }, |
| | | {863, L"WM_HANDHELDLAST" }, |
| | | {864, L"WM_AFXFIRST" }, |
| | | {895, L"WM_AFXLAST" }, |
| | | {896, L"WM_PENWINFIRST" }, |
| | | {897, L"WM_RCRESULT" }, |
| | | {898, L"WM_HOOKRCRESULT" }, |
| | | {899, L"WM_GLOBALRCCHANGE" }, |
| | | {899, L"WM_PENMISCINFO" }, |
| | | {900, L"WM_SKB" }, |
| | | {901, L"WM_HEDITCTL" }, |
| | | {901, L"WM_PENCTL" }, |
| | | {902, L"WM_PENMISC" }, |
| | | {903, L"WM_CTLINIT" }, |
| | | {904, L"WM_PENEVENT" }, |
| | | {911, L"WM_PENWINLAST" }, |
| | | {1024, L"WM_USER" }, |
| | | }; |
| | | |
| | | namespace QWK { |
| | | |
| | | enum IconButtonClickLevelFlag { |
| | | IconButtonClicked = 1, |
| | | IconButtonDoubleClicked = 2, |
| | | IconButtonTriggersClose = 4, |
| | | }; |
| | | |
| | | // The thickness of an auto-hide taskbar in pixels. |
| | | static constexpr const quint8 kAutoHideTaskBarThickness = 2; |
| | |
| | | |
| | | static void setInternalWindowFrameMargins(QWindow *window, const QMargins &margins) { |
| | | const QVariant marginsVar = QVariant::fromValue(margins); |
| | | |
| | | // TODO: Add comments |
| | | window->setProperty("_q_windowsCustomMargins", marginsVar); |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | | if (QPlatformWindow *platformWindow = window->handle()) { |
| | |
| | | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); |
| | | } |
| | | |
| | | static inline bool isFullScreen(HWND hwnd) { |
| | | RECT windowRect{}; |
| | | ::GetWindowRect(hwnd, &windowRect); |
| | | // Compare to the full area of the screen, not the work area. |
| | | return (windowRect == getMonitorForWindow(hwnd).rcMonitor); |
| | | } |
| | | |
| | | static inline bool isMaximized(HWND hwnd) { |
| | | return ::IsZoomed(hwnd); |
| | | } |
| | | |
| | | static inline bool isMinimized(HWND hwnd) { |
| | | return ::IsIconic(hwnd); |
| | | } |
| | | |
| | | static inline bool isWindowNoState(HWND hwnd) { |
| | | #if 0 |
| | | WINDOWPLACEMENT wp{}; |
| | | wp.length = sizeof(wp); |
| | | ::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 bringWindowToFront(HWND hwnd) { |
| | | HWND oldForegroundWindow = ::GetForegroundWindow(); |
| | | if (!oldForegroundWindow) { |
| | |
| | | if (!::IsWindowVisible(hwnd)) { |
| | | ::ShowWindow(hwnd, SW_SHOW); |
| | | } |
| | | if (IsMinimized(hwnd)) { |
| | | 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); |
| | | } |
| | | |
| | | static inline bool isFullScreen(HWND hwnd) { |
| | | RECT windowRect{}; |
| | | ::GetWindowRect(hwnd, &windowRect); |
| | | // Compare to the full area of the screen, not the work area. |
| | | return (windowRect == getMonitorForWindow(hwnd).rcMonitor); |
| | | } |
| | | |
| | | static inline bool isWindowNoState(HWND hwnd) { |
| | | #if 0 |
| | | WINDOWPLACEMENT wp{}; |
| | | wp.length = sizeof(wp); |
| | | ::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 void syncPaintEventWithDwm() { |
| | | // No need to sync with DWM if DWM composition is disabled. |
| | | if (!isDwmCompositionEnabled()) { |
| | | return; |
| | | } |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | // Dirty hack to workaround the resize flicker caused by DWM. |
| | | LARGE_INTEGER freq{}; |
| | | ::QueryPerformanceFrequency(&freq); |
| | | TIMECAPS tc{}; |
| | | apis.ptimeGetDevCaps(&tc, sizeof(tc)); |
| | | const UINT ms_granularity = tc.wPeriodMin; |
| | | apis.ptimeBeginPeriod(ms_granularity); |
| | | LARGE_INTEGER now0{}; |
| | | ::QueryPerformanceCounter(&now0); |
| | | // ask DWM where the vertical blank falls |
| | | DWM_TIMING_INFO dti{}; |
| | | dti.cbSize = sizeof(dti); |
| | | apis.pDwmGetCompositionTimingInfo(nullptr, &dti); |
| | | LARGE_INTEGER now1{}; |
| | | ::QueryPerformanceCounter(&now1); |
| | | // - DWM told us about SOME vertical blank |
| | | // - past or future, possibly many frames away |
| | | // - convert that into the NEXT vertical blank |
| | | const auto period = qreal(dti.qpcRefreshPeriod); |
| | | const auto dt = qreal(dti.qpcVBlank - now1.QuadPart); |
| | | const qreal ratio = (dt / period); |
| | | auto w = qreal(0); |
| | | auto m = qreal(0); |
| | | if ((dt > qreal(0)) || qFuzzyIsNull(dt)) { |
| | | w = ratio; |
| | | } else { |
| | | // reach back to previous period |
| | | // - so m represents consistent position within phase |
| | | w = (ratio - qreal(1)); |
| | | } |
| | | m = (dt - (period * w)); |
| | | if ((m < qreal(0)) || qFuzzyCompare(m, period) || (m > period)) { |
| | | return; |
| | | } |
| | | const qreal m_ms = (qreal(1000) * m / qreal(freq.QuadPart)); |
| | | ::Sleep(static_cast<DWORD>(std::round(m_ms))); |
| | | apis.ptimeEndPeriod(ms_granularity); |
| | | } |
| | | |
| | | // Returns false if the menu is canceled |
| | | static bool showSystemMenu_sys(HWND hWnd, const POINT &pos, const bool selectFirstEntry, |
| | | const bool fixedSize) { |
| | |
| | | return true; |
| | | } |
| | | |
| | | const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); |
| | | const bool maxOrFull = isMaximized(hWnd) || isFullScreen(hWnd); |
| | | ::EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | MFS_ENABLED)); |
| | | ::EnableMenuItem(hMenu, SC_MAXIMIZE, |
| | | (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? MFS_DISABLED : MFS_ENABLED))); |
| | |
| | | case HTBORDER: |
| | | return Win32WindowContext::FixedBorder; |
| | | default: |
| | | Q_UNREACHABLE(); |
| | | break; |
| | | } |
| | | return Win32WindowContext::Outside; |
| | |
| | | // Try hooked procedure and save result |
| | | LRESULT result; |
| | | if (ctx->windowProc(hWnd, message, wParam, lParam, &result)) { |
| | | // https://github.com/stdware/qwindowkit/issues/45 |
| | | // Forward the event to user-defined native event filters, there may be some messages |
| | | // that need to be processed by the user. |
| | | std::ignore = |
| | |
| | | auto hWnd = reinterpret_cast<HWND>(m_windowId); |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| | | const QPoint nativeGlobalPos = |
| | | QHighDpi::toNativeGlobalPosition(pos, m_windowHandle); |
| | | QHighDpi::toNativeGlobalPosition(pos, m_windowHandle.data()); |
| | | #else |
| | | const QPoint nativeGlobalPos = QHighDpi::toNativePixels(pos, m_windowHandle); |
| | | const QPoint nativeGlobalPos = QHighDpi::toNativePixels(pos, m_windowHandle.data()); |
| | | #endif |
| | | std::ignore = showSystemMenu_sys(hWnd, qpoint2point(nativeGlobalPos), false, |
| | | m_delegate->isHostSizeFixed(m_host)); |
| | | isHostSizeFixed()); |
| | | return; |
| | | } |
| | | |
| | |
| | | return isSystemBorderEnabled() && !isWin11OrGreater(); |
| | | } |
| | | |
| | | if (key == QStringLiteral("windows-system-border-enabled")) { |
| | | return isSystemBorderEnabled(); |
| | | } |
| | | |
| | | if (key == QStringLiteral("border-thickness")) { |
| | | return m_windowId |
| | | ? int(getWindowFrameBorderThickness(reinterpret_cast<HWND>(m_windowId))) |
| | | : 0; |
| | | } |
| | | |
| | | if (key == QStringLiteral("title-bar-height")) { |
| | | return m_windowId ? int(getTitleBarHeight(reinterpret_cast<HWND>(m_windowId))) : 0; |
| | | } |
| | | return AbstractWindowContext::windowAttribute(key); |
| | | } |
| | |
| | | lastHitTestResult = WindowPart::Outside; |
| | | lastHitTestResultRaw = HTNOWHERE; |
| | | |
| | | if (!isSystemBorderEnabled()) { |
| | | m_delegate->setWindowFlags(m_host, m_delegate->getWindowFlags(m_host) | |
| | | Qt::FramelessWindowHint); |
| | | } |
| | | |
| | | // If the original window id is valid, remove all resources related |
| | | if (oldWinId) { |
| | | removeManagedWindow(reinterpret_cast<HWND>(oldWinId)); |
| | | } |
| | | |
| | | if (!winId) { |
| | | return; |
| | | } |
| | |
| | | return false; |
| | | } |
| | | |
| | | // qDebug().noquote() << QDateTime::currentDateTime() |
| | | // << QString::fromStdWString(xmsgHash.value(message, L"")) << Qt::hex |
| | | // << message; |
| | | |
| | | // Test snap layout |
| | | if (snapLayoutHandler(hWnd, message, wParam, lParam, result)) { |
| | | return true; |
| | |
| | | Q_UNUSED(oldAttribute) |
| | | |
| | | const auto hwnd = reinterpret_cast<HWND>(m_windowId); |
| | | Q_ASSERT(hwnd); |
| | | if (!hwnd) { |
| | | return false; |
| | | } |
| | | |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | static constexpr const MARGINS extendedMargins = {-1, -1, -1, -1}; |
| | | const auto &extendMargins = [this, &apis, hwnd]() { |
| | | // For some unknown reason, the window background is totally black when the host object |
| | | // is a QWidget. And extending the window frame into the client area seems to fix it |
| | | // magically. |
| | | // We don't need the following *HACK* for QtQuick windows. |
| | | if (!m_host->isWidgetType()) { |
| | | return; |
| | | } |
| | | // After many times of trying, we found that the Acrylic/Mica/Mica Alt background |
| | | // only appears on the native Win32 window's background, so naturally we want to |
| | | // extend the window frame into the whole client area to be able to let the special |
| | | // material fill the whole window. Previously we are using negative margins because |
| | | // it's widely known that using negative margins will let the window frame fill |
| | | // the whole window and that's indeed what we wanted to do, however, later we found |
| | | // that doing so is causing issues. When the user enabled the "show accent color on |
| | | // window title bar and borders" option on system personalize settings, a 30px bar |
| | | // would appear on window top. It has the same color with the system accent color. |
| | | // Actually it's the original title bar we've already hidden, and it magically |
| | | // appears again when we use negative margins to extend the window frame. And again |
| | | // after some experiments, I found that the title bar won't appear if we don't extend |
| | | // from the top side. In the end I found that we only need to extend from the left |
| | | // side if we extend long enough. In this way we can see the special material even |
| | | // when the host object is a QWidget and the title bar still remain hidden. But even |
| | | // though this solution seems perfect, I really don't know why it works. |
| | | static constexpr const MARGINS margins = {65536, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | }; |
| | | const auto &restoreMargins = [this, &apis, hwnd]() { |
| | | auto margins = qmargins2margins( |
| | | m_windowAttributes.value(QStringLiteral("extra-margins")).value<QMargins>()); |
| | |
| | | |
| | | if (key == QStringLiteral("extra-margins")) { |
| | | auto margins = qmargins2margins(attribute.value<QMargins>()); |
| | | return apis.pDwmExtendFrameIntoClientArea(hwnd, &margins) == S_OK; |
| | | return SUCCEEDED(apis.pDwmExtendFrameIntoClientArea(hwnd, &margins)); |
| | | } |
| | | |
| | | if (key == QStringLiteral("dark-mode")) { |
| | |
| | | } else { |
| | | apis.pAllowDarkModeForApp(enable); |
| | | } |
| | | for (const auto attr : { |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE, |
| | | }) { |
| | | apis.pDwmSetWindowAttribute(hwnd, attr, &enable, sizeof(enable)); |
| | | } |
| | | const auto attr = isWin1020H1OrGreater() ? _DWMWA_USE_IMMERSIVE_DARK_MODE : _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1; |
| | | apis.pDwmSetWindowAttribute(hwnd, attr, &enable, sizeof(enable)); |
| | | |
| | | apis.pFlushMenuThemes(); |
| | | return true; |
| | |
| | | return false; |
| | | } |
| | | if (attribute.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); |
| | | extendMargins(); |
| | | if (isWin1122H2OrGreater()) { |
| | | // Use official DWM API to enable Mica, available since Windows 11 22H2 |
| | | // (10.0.22621). |
| | |
| | | return false; |
| | | } |
| | | if (attribute.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); |
| | | extendMargins(); |
| | | // Use official DWM API to enable Mica Alt, available since Windows 11 22H2 |
| | | // (10.0.22621). |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TABBEDWINDOW; |
| | |
| | | return false; |
| | | } |
| | | if (attribute.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); |
| | | extendMargins(); |
| | | |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TRANSIENTWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | |
| | | } |
| | | |
| | | if (key == QStringLiteral("dwm-blur")) { |
| | | // Extending window frame would break this effect for some unknown reason. |
| | | restoreMargins(); |
| | | if (attribute.toBool()) { |
| | | // We can't extend the window frame for this effect. |
| | | restoreMargins(); |
| | | if (isWin8OrGreater()) { |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_ENABLE_BLURBEHIND; |
| | |
| | | POINT screenPoint{GET_X_LPARAM(dwScreenPos), GET_Y_LPARAM(dwScreenPos)}; |
| | | ::ScreenToClient(hWnd, &screenPoint); |
| | | QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(screenPoint), |
| | | m_windowHandle); |
| | | m_windowHandle.data()); |
| | | auto dummy = WindowAgentBase::Unknown; |
| | | if (isInSystemButtons(qtScenePos, &dummy)) { |
| | | // We must record whether the last WM_MOUSELEAVE was filtered, because if |
| | |
| | | if (lastHitTestResultRaw == HTSYSMENU) { |
| | | switch (message) { |
| | | case WM_NCLBUTTONDOWN: |
| | | if (iconButtonClickTime == 0) { |
| | | if (iconButtonClickLevel == 0) { |
| | | // A message of WM_SYSCOMMAND with SC_MOUSEMENU will be sent by |
| | | // Windows, and the current control flow will be blocked by the |
| | | // menu while Windows will create and execute a new event loop |
| | | // until the menu returns |
| | | iconButtonClickTime = ::GetTickCount64(); |
| | | *result = |
| | | ::DefWindowProcW(hWnd, WM_NCLBUTTONDOWN, wParam, lParam); |
| | | *result = ::DefWindowProcW(hWnd, message, wParam, lParam); |
| | | iconButtonClickTime = 0; |
| | | if (iconButtonClickLevel & IconButtonTriggersClose) { |
| | | ::PostMessageW(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0); |
| | | } |
| | | if (iconButtonClickLevel & IconButtonDoubleClicked) { |
| | | iconButtonClickLevel = 0; |
| | | } |
| | | // Otherwise, no need to reset `iconButtonClickLevel` if not to |
| | | // close, if it has value, there must be another incoming |
| | | // WM_NCLBUTTONDOWN |
| | | } else { |
| | | *result = FALSE; |
| | | emulateClientAreaMessage(hWnd, message, wParam, lParam); |
| | | iconButtonClickLevel = 0; |
| | | } |
| | | break; |
| | | case WM_NCLBUTTONDBLCLK: |
| | |
| | | auto clientWidth = RECT_WIDTH(clientRect); |
| | | auto clientHeight = RECT_HEIGHT(clientRect); |
| | | |
| | | QPoint qtScenePos = |
| | | QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle); |
| | | QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), |
| | | m_windowHandle.data()); |
| | | |
| | | bool isFixedSize = m_delegate->isHostSizeFixed(m_host); |
| | | bool isTitleBar = isInTitleBarDraggableArea(qtScenePos); |
| | | int frameSize = getResizeBorderThickness(hWnd); |
| | | |
| | | bool isFixedWidth = isHostWidthFixed(); |
| | | bool isFixedHeight = isHostHeightFixed(); |
| | | bool isFixedSize = isHostSizeFixed(); |
| | | bool isInLeftBorder = nativeLocalPos.x <= frameSize; |
| | | bool isInTopBorder = nativeLocalPos.y <= frameSize; |
| | | bool isInRightBorder = nativeLocalPos.x > clientWidth - frameSize; |
| | | bool isInBottomBorder = nativeLocalPos.y > clientHeight - frameSize; |
| | | bool isInTitleBar = isInTitleBarDraggableArea(qtScenePos); |
| | | WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; |
| | | bool isInCaptionButtons = isInSystemButtons(qtScenePos, &sysButtonType); |
| | | bool dontOverrideCursor = false; // ### TODO |
| | | |
| | | WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; |
| | | if (!isFixedSize && isInSystemButtons(qtScenePos, &sysButtonType)) { |
| | | if (isInCaptionButtons) { |
| | | // Firstly, we set the hit test result to a default value to be able to detect |
| | | // whether we have changed it or not afterwards. |
| | | *result = HTNOWHERE; |
| | |
| | | // window is not maximized/fullscreen/minimized, of course). |
| | | if (isWindowNoState(hWnd)) { |
| | | static constexpr const quint8 kBorderSize = 2; |
| | | bool isTop = (nativeLocalPos.y <= kBorderSize); |
| | | bool isTop = nativeLocalPos.y <= kBorderSize; |
| | | bool isLeft = nativeLocalPos.x <= kBorderSize; |
| | | bool isRight = (nativeLocalPos.x >= (clientWidth - kBorderSize)); |
| | | bool isRight = nativeLocalPos.x > clientWidth - kBorderSize; |
| | | if (isTop || isLeft || isRight) { |
| | | if (dontOverrideCursor) { |
| | | if (isFixedSize || dontOverrideCursor) { |
| | | // The user doesn't want the window to be resized, so we tell |
| | | // Windows we are in the client area so that the controls beneath |
| | | // the mouse cursor can still be hovered or clicked. |
| | | *result = (isTitleBar ? HTCAPTION : HTCLIENT); |
| | | *result = isInTitleBar ? HTCAPTION : HTCLIENT; |
| | | } else { |
| | | if (isTop) { |
| | | if (isLeft) { |
| | | *result = HTTOPLEFT; |
| | | if (isFixedWidth) { |
| | | *result = HTTOP; |
| | | } else if (isFixedHeight) { |
| | | *result = HTLEFT; |
| | | } else { |
| | | *result = HTTOPLEFT; |
| | | } |
| | | } else if (isRight) { |
| | | *result = HTTOPRIGHT; |
| | | if (isFixedWidth) { |
| | | *result = HTTOP; |
| | | } else if (isFixedHeight) { |
| | | *result = HTRIGHT; |
| | | } else { |
| | | *result = HTTOPRIGHT; |
| | | } |
| | | } else { |
| | | *result = HTTOP; |
| | | *result = isFixedHeight ? HTBORDER : HTTOP; |
| | | } |
| | | } else { |
| | | if (isLeft) { |
| | | *result = HTLEFT; |
| | | if (isFixedWidth) { |
| | | *result = HTBORDER; |
| | | } else { |
| | | *result = HTRIGHT; |
| | | *result = isLeft ? HTLEFT : HTRIGHT; |
| | | } |
| | | } |
| | | } |
| | |
| | | // OK, we are not inside any chrome buttons, try to find out which part of the |
| | | // window are we hitting. |
| | | |
| | | bool max = IsMaximized(hWnd); |
| | | bool max = isMaximized(hWnd); |
| | | bool full = isFullScreen(hWnd); |
| | | int frameSize = getResizeBorderThickness(hWnd); |
| | | bool isTop = (nativeLocalPos.y < frameSize); |
| | | |
| | | if (isSystemBorderEnabled()) { |
| | | // This will handle the left, right and bottom parts of the frame |
| | |
| | | // outside the window, that is, the three transparent window resize area. |
| | | // Returning HTCLIENT will confuse Windows, we can't put our controls there |
| | | // anyway. |
| | | *result = ((isFixedSize || dontOverrideCursor) ? HTBORDER |
| | | : originalHitTestResult); |
| | | *result = HTNOWHERE; // Make sure we can know we don't set any value |
| | | // explicitly later. |
| | | if (originalHitTestResult == HTCAPTION) { |
| | | } else if (isFixedSize || dontOverrideCursor) { |
| | | *result = HTBORDER; |
| | | } else if (isFixedWidth || isFixedHeight) { |
| | | if (originalHitTestResult == HTTOPLEFT) { |
| | | if (isFixedWidth) { |
| | | *result = HTTOP; |
| | | } else { |
| | | *result = HTLEFT; |
| | | } |
| | | } else if (originalHitTestResult == HTTOPRIGHT) { |
| | | if (isFixedWidth) { |
| | | *result = HTTOP; |
| | | } else { |
| | | *result = HTRIGHT; |
| | | } |
| | | } else if (originalHitTestResult == HTBOTTOMRIGHT) { |
| | | if (isFixedWidth) { |
| | | *result = HTBOTTOM; |
| | | } else { |
| | | *result = HTRIGHT; |
| | | } |
| | | } else if (originalHitTestResult == HTBOTTOMLEFT) { |
| | | if (isFixedWidth) { |
| | | *result = HTBOTTOM; |
| | | } else { |
| | | *result = HTLEFT; |
| | | } |
| | | } else if (originalHitTestResult == HTLEFT || |
| | | originalHitTestResult == HTRIGHT) { |
| | | if (isFixedWidth) { |
| | | *result = HTBORDER; |
| | | } |
| | | } else if (originalHitTestResult == HTTOP || |
| | | originalHitTestResult == HTBOTTOM) { |
| | | if (isFixedHeight) { |
| | | *result = HTBORDER; |
| | | } |
| | | } |
| | | } |
| | | if (*result == HTNOWHERE) { |
| | | *result = originalHitTestResult; |
| | | } |
| | | return true; |
| | | } |
| | | if (full) { |
| | |
| | | return true; |
| | | } |
| | | if (max) { |
| | | *result = (isTitleBar ? HTCAPTION : HTCLIENT); |
| | | *result = isInTitleBar ? HTCAPTION : HTCLIENT; |
| | | return true; |
| | | } |
| | | // At this point, we know that the cursor is inside the client area, |
| | |
| | | // title bar or the drag bar. Apparently, it must be the drag bar or |
| | | // the little border at the top which the user can use to move or |
| | | // resize the window. |
| | | if (isTop) { |
| | | if (isInTopBorder) { |
| | | // Return HTCLIENT instead of HTBORDER here, because the mouse is |
| | | // inside our homemade title bar now, return HTCLIENT to let our |
| | | // title bar can still capture mouse events. |
| | | *result = ((isFixedSize || dontOverrideCursor) |
| | | ? (isTitleBar ? HTCAPTION : HTCLIENT) |
| | | : HTTOP); |
| | | *result = [&]() { |
| | | if (isFixedSize || isFixedHeight || dontOverrideCursor || |
| | | (isFixedWidth && (isInLeftBorder || isInRightBorder))) { |
| | | if (isInTitleBar) { |
| | | return HTCAPTION; |
| | | } else { |
| | | return HTCLIENT; |
| | | } |
| | | } else { |
| | | return HTTOP; |
| | | } |
| | | }(); |
| | | return true; |
| | | } |
| | | if (isTitleBar) { |
| | | if (isInTitleBar) { |
| | | *result = HTCAPTION; |
| | | return true; |
| | | } |
| | |
| | | *result = HTCLIENT; |
| | | return true; |
| | | } |
| | | if (max) { |
| | | *result = (isTitleBar ? HTCAPTION : HTCLIENT); |
| | | if (max || isFixedSize || dontOverrideCursor) { |
| | | *result = isInTitleBar ? HTCAPTION : HTCLIENT; |
| | | return true; |
| | | } |
| | | if (!isFixedSize) { |
| | | const bool isBottom = (nativeLocalPos.y >= (clientHeight - frameSize)); |
| | | // Make the border a little wider to let the user easy to resize on corners. |
| | | const auto scaleFactor = ((isTop || isBottom) ? qreal(2) : qreal(1)); |
| | | const int scaledFrameSize = std::round(qreal(frameSize) * scaleFactor); |
| | | const bool isLeft = (nativeLocalPos.x < scaledFrameSize); |
| | | const bool isRight = (nativeLocalPos.x >= (clientWidth - scaledFrameSize)); |
| | | if (dontOverrideCursor && (isTop || isBottom || isLeft || isRight)) { |
| | | // Return HTCLIENT instead of HTBORDER here, because the mouse is |
| | | // inside the window now, return HTCLIENT to let the controls |
| | | // inside our window can still capture mouse events. |
| | | *result = (isTitleBar ? HTCAPTION : HTCLIENT); |
| | | return true; |
| | | if (isFixedWidth || isFixedHeight) { |
| | | if (isInLeftBorder && isInTopBorder) { |
| | | if (isFixedWidth) { |
| | | *result = HTTOP; |
| | | } else { |
| | | *result = HTLEFT; |
| | | } |
| | | } else if (isInRightBorder && isInTopBorder) { |
| | | if (isFixedWidth) { |
| | | *result = HTTOP; |
| | | } else { |
| | | *result = HTRIGHT; |
| | | } |
| | | } else if (isInRightBorder && isInBottomBorder) { |
| | | if (isFixedWidth) { |
| | | *result = HTBOTTOM; |
| | | } else { |
| | | *result = HTRIGHT; |
| | | } |
| | | } else if (isInLeftBorder && isInBottomBorder) { |
| | | if (isFixedWidth) { |
| | | *result = HTBOTTOM; |
| | | } else { |
| | | *result = HTLEFT; |
| | | } |
| | | } else if (isInLeftBorder || isInRightBorder) { |
| | | if (isFixedWidth) { |
| | | *result = HTCLIENT; |
| | | } else { |
| | | *result = isInLeftBorder ? HTLEFT : HTRIGHT; |
| | | } |
| | | } else if (isInTopBorder || isInBottomBorder) { |
| | | if (isFixedHeight) { |
| | | *result = HTCLIENT; |
| | | } else { |
| | | *result = isInTopBorder ? HTTOP : HTBOTTOM; |
| | | } |
| | | } else { |
| | | *result = HTCLIENT; |
| | | } |
| | | if (isTop) { |
| | | if (isLeft) { |
| | | return true; |
| | | } else { |
| | | if (isInTopBorder) { |
| | | if (isInLeftBorder) { |
| | | *result = HTTOPLEFT; |
| | | return true; |
| | | } |
| | | if (isRight) { |
| | | if (isInRightBorder) { |
| | | *result = HTTOPRIGHT; |
| | | return true; |
| | | } |
| | | *result = HTTOP; |
| | | return true; |
| | | } |
| | | if (isBottom) { |
| | | if (isLeft) { |
| | | if (isInBottomBorder) { |
| | | if (isInLeftBorder) { |
| | | *result = HTBOTTOMLEFT; |
| | | return true; |
| | | } |
| | | if (isRight) { |
| | | if (isInRightBorder) { |
| | | *result = HTBOTTOMRIGHT; |
| | | return true; |
| | | } |
| | | *result = HTBOTTOM; |
| | | return true; |
| | | } |
| | | if (isLeft) { |
| | | if (isInLeftBorder) { |
| | | *result = HTLEFT; |
| | | return true; |
| | | } |
| | | if (isRight) { |
| | | if (isInRightBorder) { |
| | | *result = HTRIGHT; |
| | | return true; |
| | | } |
| | | } |
| | | if (isTitleBar) { |
| | | if (isInTitleBar) { |
| | | *result = HTCAPTION; |
| | | return true; |
| | | } |
| | |
| | | // that's also how most applications customize their title bars on Windows. It's |
| | | // totally OK but since we want to preserve as much original frame as possible, |
| | | // we can't use that solution. |
| | | const LRESULT hitTestResult = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam); |
| | | if ((hitTestResult != HTERROR) && (hitTestResult != HTNOWHERE)) { |
| | | *result = hitTestResult; |
| | | const LRESULT originalResult = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam); |
| | | if (originalResult != 0) { |
| | | *result = originalResult; |
| | | return true; |
| | | } |
| | | // Re-apply the original top from before the size of the default frame was |
| | |
| | | clientRect->top = originalTop; |
| | | } |
| | | |
| | | const bool max = IsMaximized(hWnd); |
| | | const bool max = isMaximized(hWnd); |
| | | const bool full = isFullScreen(hWnd); |
| | | // We don't need this correction when we're fullscreen. We will |
| | | // have the WS_POPUP size, so we don't have to worry about |
| | |
| | | } |
| | | } |
| | | } |
| | | // We should call this function only before the function returns. |
| | | syncPaintEventWithDwm(); |
| | | // By returning WVR_REDRAW we can make the window resizing look |
| | | // less broken. But we must return 0 if wParam is FALSE, according to Microsoft |
| | | // Docs. |
| | |
| | | // of the upper-left non-client area. It's confirmed that this issue exists |
| | | // from Windows 7 to Windows 10. Not tested on Windows 11 yet. Don't know |
| | | // whether it exists on Windows XP to Windows Vista or not. |
| | | *result = wParam ? WVR_REDRAW : FALSE; |
| | | |
| | | // https://github.com/chromium/chromium/blob/5d297da3cf2a642e9ace2b23fed097370bc70814/ui/views/win/hwnd_message_handler.cc#L2330 |
| | | // Do not return WVR_REDRAW otherwise child HWNDs will be mispositioned. |
| | | *result = FALSE; |
| | | return true; |
| | | } |
| | | |
| | |
| | | return {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; |
| | | }; |
| | | const auto getNativeGlobalPosFromKeyboard = [hWnd]() -> POINT { |
| | | const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); |
| | | const bool maxOrFull = isMaximized(hWnd) || isFullScreen(hWnd); |
| | | const quint32 frameSize = getResizeBorderThickness(hWnd); |
| | | const quint32 horizontalOffset = |
| | | ((maxOrFull || !isSystemBorderEnabled()) ? 0 : frameSize); |
| | |
| | | switch (message) { |
| | | case WM_RBUTTONUP: { |
| | | const POINT nativeLocalPos = getNativePosFromMouse(); |
| | | const QPoint qtScenePos = |
| | | QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle); |
| | | if (isInTitleBarDraggableArea(qtScenePos)) { |
| | | const QPoint qtScenePos = QHighDpi::fromNativeLocalPosition( |
| | | point2qpoint(nativeLocalPos), m_windowHandle.data()); |
| | | WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; |
| | | if (isInTitleBarDraggableArea(qtScenePos) || |
| | | (isInSystemButtons(qtScenePos, &sysButtonType) && |
| | | sysButtonType == WindowAgentBase::WindowIcon)) { |
| | | shouldShowSystemMenu = true; |
| | | nativeGlobalPos = nativeLocalPos; |
| | | ::ClientToScreen(hWnd, &nativeGlobalPos); |
| | |
| | | if (shouldShowSystemMenu) { |
| | | static HHOOK mouseHook = nullptr; |
| | | static std::optional<POINT> mouseClickPos; |
| | | static bool mouseDoubleClicked = false; |
| | | bool mouseHookedLocal = false; |
| | | |
| | | // The menu is triggered by a click on icon button |
| | | if (iconButtonClickTime > 0) { |
| | | POINT menuPos{0, static_cast<LONG>(getTitleBarHeight(hWnd))}; |
| | | if (const auto tb = titleBar()) { |
| | |
| | | WH_MOUSE, |
| | | [](int nCode, WPARAM wParam, LPARAM lParam) { |
| | | if (nCode >= 0) { |
| | | if (wParam == WM_LBUTTONDOWN || wParam == WM_LBUTTONDBLCLK) { |
| | | auto pMouseStruct = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam); |
| | | if (pMouseStruct) { |
| | | mouseClickPos = pMouseStruct->pt; |
| | | switch (wParam) { |
| | | case WM_LBUTTONDBLCLK: |
| | | mouseDoubleClicked = true; |
| | | Q_FALLTHROUGH(); |
| | | |
| | | // case WM_POINTERDOWN: |
| | | |
| | | case WM_LBUTTONDOWN: { |
| | | auto pMouseStruct = |
| | | reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam); |
| | | if (pMouseStruct) { |
| | | mouseClickPos = pMouseStruct->pt; |
| | | } |
| | | break; |
| | | } |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | return ::CallNextHookEx(nullptr, nCode, wParam, lParam); |
| | |
| | | mouseHookedLocal = true; |
| | | } |
| | | } |
| | | bool res = showSystemMenu_sys(hWnd, nativeGlobalPos, broughtByKeyboard, |
| | | m_delegate->isHostSizeFixed(m_host)); |
| | | |
| | | bool res = |
| | | showSystemMenu_sys(hWnd, nativeGlobalPos, broughtByKeyboard, isHostSizeFixed()); |
| | | |
| | | // Uninstall mouse hook and check if it's a double-click |
| | | if (mouseHookedLocal) { |
| | | ::UnhookWindowsHookEx(mouseHook); |
| | | |
| | | // Emulate the Windows icon button's behavior |
| | | static uint32_t doubleClickTime = ::GetDoubleClickTime(); |
| | | if (!res && mouseClickPos.has_value() && |
| | | ::GetTickCount64() - iconButtonClickTime <= doubleClickTime) { |
| | | POINT nativeGlobalPos; |
| | | ::GetCursorPos(&nativeGlobalPos); |
| | | if (!res && mouseClickPos.has_value()) { |
| | | POINT nativeLocalPos = mouseClickPos.value(); |
| | | // qDebug() << point2qpoint(nativeLocalPos) << point2qpoint(nativeGlobalPos); |
| | | ::ScreenToClient(hWnd, &nativeLocalPos); |
| | | QPoint qtScenePos = QHighDpi::fromNativeLocalPosition( |
| | | point2qpoint(nativeLocalPos), m_windowHandle); |
| | | point2qpoint(nativeLocalPos), m_windowHandle.data()); |
| | | WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; |
| | | if (isInSystemButtons(qtScenePos, &sysButtonType) && |
| | | sysButtonType == WindowAgentBase::WindowIcon) { |
| | | ::PostMessageW(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0); |
| | | iconButtonClickLevel |= IconButtonClicked; |
| | | if (::GetTickCount64() - iconButtonClickTime <= ::GetDoubleClickTime()) { |
| | | iconButtonClickLevel |= IconButtonTriggersClose; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (mouseDoubleClicked) { |
| | | iconButtonClickLevel |= IconButtonDoubleClicked; |
| | | } |
| | | |
| | | mouseHook = nullptr; |
| | | mouseClickPos.reset(); |
| | | mouseDoubleClicked = false; |
| | | } |
| | | |
| | | // QPA's internal code will handle system menu events separately, and its |