| | |
| | | } |
| | | RECT currentWindowRect{}; |
| | | ::GetWindowRect(hwnd, ¤tWindowRect); |
| | | auto newWindowX = activeMonitorRect.left + (currentWindowRect.left - currentMonitorRect.left); |
| | | 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); |
| | | ::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) { |
| | |
| | | // 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)) { |
| | | SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG, 1000, |
| | | nullptr)) { |
| | | // The foreground window hangs, can't activate current window. |
| | | return; |
| | | } |
| | |
| | | // 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 }; |
| | | |
| | | [[maybe_unused]] const auto &cleaner = |
| | | qScopeGuard([windowThreadProcessId, currentThreadId]() { |
| | | ::AttachThreadInput(windowThreadProcessId, currentThreadId, FALSE); // |
| | | }); |
| | | |
| | | ::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. |
| | |
| | | } |
| | | } |
| | | |
| | | break; |
| | | return; |
| | | } |
| | | |
| | | case DefaultColorsHook: { |
| | |
| | | } |
| | | |
| | | default: { |
| | | // unreachable |
| | | break; |
| | | } |
| | | } |
| | |
| | | const WindowPart currentWindowPart = lastHitTestResult; |
| | | if (message == WM_NCMOUSEMOVE) { |
| | | if (currentWindowPart != WindowPart::ChromeButton) { |
| | | // https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/widgets/kernel/qwidgetwindow.cpp#L472 |
| | | // When the mouse press event arrives, QWidgetWindow will implicitly grab |
| | | // the top widget right under the mouse, and set `qt_button_down` to this |
| | | // widget. At this time, no other widgets will accept the mouse event until |
| | | // QWidgetWindow receives the mouse release event, then set `qt_button_down` |
| | | // to null. |
| | | |
| | | // Imagine the following situation, now the main window has a pop-up menu, |
| | | // the focus is not on the main window, if we click outside the pop-up menu, |
| | | // the menu will close, which seems to be completely fine. But if we close |
| | | // the menu by clicking on the title bar draggable area, then other widgets |
| | | // won't accept the mouse message afterwards. |
| | | |
| | | // Here's the reason. |
| | | // When the mouse is placed in the draggable area of the title bar, there |
| | | // are two situations. |
| | | |
| | | // 1. If the focus is on the main window, and the last result of |
| | | // WM_NCHITTEST is HTCAPTION, the mouse click event in the title bar is |
| | | // taken over by Windows and Qt does not receive the mouse click event. |
| | | |
| | | // 2. If the main window has a pop-up menu, it is completely different. When |
| | | // the mouse is pressed on the title bar, Windows sends the WM_LBUTTONDOWN |
| | | // message to the window plane of the pop-up menu, the menu is closed, but |
| | | // Qt will continue to forward the event to the QWidget under the mouse, and |
| | | // the event will be processed by QWidgetWindow, causing the title bar |
| | | // widget to be implicitly grabbed. After the menu is closed, Windows |
| | | // immediately sends WM_NCHITTEST, because the mouse is in the title bar |
| | | // draggable area, the result is HTCAPTION, so when the mouse is released, |
| | | // Windows sends WM_NCLBUTTONUP, which is a non-client area message, and it |
| | | // will be ignored by Qt. As a consequence, QWidgetWindow can't receive a |
| | | // mouse release messages in the client area, so the grab remains, and other |
| | | // widgets cannot receive mouse events. |
| | | |
| | | // Since we didn't watch the menu window, we cannot capture any mouse |
| | | // press events sent by Windows, so we cannot solve this problem by |
| | | // recording mouse events. Fortunately, we found that the main window will |
| | | // receive a WM_NCMOUSEMOVE message immediately after the menu is closed, so |
| | | // we just manually send a mouse release event when this message arrives and |
| | | // set qt_button_down to null. Don't worry, when receiving WM_NCMOUSEMOVE, |
| | | // there shouldn't be any control in the state of being grabbed. |
| | | |
| | | // In the native window, although QWidgetWindow handles the forwarded mouse |
| | | // press event when the menu is closed, since the native title bar is not a |
| | | // QWidget, no widget will be grabbed, and `qt_button_down` remains empty, |
| | | // the above problems would not arise. |
| | | |
| | | m_delegate->resetQtGrabbedControl(m_host); |
| | | if (mouseLeaveBlocked) { |
| | | emulateClientAreaMessage(hWnd, message, wParam, lParam, |
| | |
| | | // 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. |
| | | |
| | | // Why do we need to call this function here? |
| | | m_delegate->resetQtGrabbedControl(m_host); |
| | | } |
| | | } |
| | |
| | | // Terminal does, however, later I found that if we choose a proper |
| | | // color, our homemade top border can almost have exactly the same |
| | | // appearance with the system's one. |
| | | |
| | | [[maybe_unused]] const auto &hitTestRecorder = qScopeGuard([this, result]() { |
| | | lastHitTestResult = getHitWindowPart(int(*result)); // |
| | | }); |