forked from shelllet/cpp-example
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdelegatewindow.cpp
More file actions
299 lines (265 loc) · 10.7 KB
/
Copy pathdelegatewindow.cpp
File metadata and controls
299 lines (265 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#include "qt_windows.h"
#include "delegatewindow.h"
#include "titlebarwidget.h"
#include "qapplication.h"
#include "qlibrary.h"
#include "wrl.h"
#include "d2d1.h"
#include "qoperatingsystemversion.h"
#include "qwindow.h"
#include "qdebug.h"
#include "common/logs.h"
namespace shelllet {
using namespace Microsoft::WRL;
namespace frameless {
#ifdef _DEBUG
#define JWF_RESOLVE_ERROR(funcName) Q_ASSERT(m_lp##funcName);
#else
#define JWF_RESOLVE_ERROR(funcName) \
if (!m_lp##funcName) { \
LOG_FATAL("frameless") << "Failed to resolve symbol: " #funcName << std::endl; \
}
#endif
#define JWF_GENERATE_WINAPI(funcName, retType, ...) \
using WINAPI_##funcName = retType(WINAPI *)(__VA_ARGS__); \
static WINAPI_##funcName m_lp##funcName = nullptr;
#define JWF_RESOLVE_WINAPI(libName, funcName) \
if (!m_lp##funcName) { \
m_lp##funcName = reinterpret_cast<WINAPI_##funcName>( \
QLibrary::resolve(QString::fromUtf8(#libName), #funcName)); \
JWF_RESOLVE_ERROR(funcName) \
}
JWF_GENERATE_WINAPI(GetSystemMetricsForDpi, int, int, UINT)
JWF_GENERATE_WINAPI(GetDpiForWindow, UINT, HWND)
static ComPtr<ID2D1Factory> m_pDirect2dFactory;
enum AppbarAutohideEdge {
EDGE_TOP = 1 << 0,
EDGE_LEFT = 1 << 1,
EDGE_BOTTOM = 1 << 2,
EDGE_RIGHT = 1 << 3,
};
class DelegateWindowPrivate : public ObjectPrivate {
public:
TitleBarWidget* titleBar;
};
bool __MonitorHasAutohideTaskbarForEdge(UINT edge, HMONITOR monitor) {
APPBARDATA taskbarData = { sizeof(APPBARDATA), NULL, 0, edge };
taskbarData.hWnd = ::GetForegroundWindow();
// MSDN documents an ABM_GETAUTOHIDEBAREX, which supposedly takes a monitor
// rect and returns autohide bars on that monitor. This sounds like a good
// idea for multi-monitor systems. Unfortunately, it appears to not work at
// least some of the time (erroneously returning NULL) and there's almost no
// online documentation or other sample code using it that suggests ways to
// address this problem. We do the following:-
// 1. Use the ABM_GETAUTOHIDEBAR message. If it works, i.e. returns a valid
// window we are done.
// 2. If the ABM_GETAUTOHIDEBAR message does not work we query the auto hide
// state of the taskbar and then retrieve its position. That call returns
// the edge on which the taskbar is present. If it matches the edge we
// are looking for, we are done.
// NOTE: This call spins a nested run loop.
HWND taskbar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAR, &taskbarData));
if (!::IsWindow(taskbar)) {
APPBARDATA taskbarData = { sizeof(APPBARDATA), 0, 0, 0 };
unsigned int taskbar_state = SHAppBarMessage(ABM_GETSTATE, &taskbarData);
if (!(taskbar_state & ABS_AUTOHIDE))
return false;
taskbarData.hWnd = ::FindWindowW(L"Shell_TrayWnd", NULL);
if (!::IsWindow(taskbarData.hWnd))
return false;
SHAppBarMessage(ABM_GETTASKBARPOS, &taskbarData);
if (taskbarData.uEdge == edge)
taskbar = taskbarData.hWnd;
}
// There is a potential race condition here:
// 1. A maximized chrome window is fullscreened.
// 2. It is switched back to maximized.
// 3. In the process the window gets a WM_NCCACLSIZE message which calls us to
// get the autohide state.
// 4. The worker thread is invoked. It calls the API to get the autohide
// state. On Windows versions earlier than Windows 7, taskbars could
// easily be always on top or not.
// This meant that we only want to look for taskbars which have the topmost
// bit set. However this causes problems in cases where the window on the
// main thread is still in the process of switching away from fullscreen.
// In this case the taskbar might not yet have the topmost bit set.
// 5. The main thread resumes and does not leave space for the taskbar and
// hence it does not pop when hovered.
//
// To address point 4 above, it is best to not check for the WS_EX_TOPMOST
// window style on the taskbar, as starting from Windows 7, the topmost
// style is always set. We don't support XP and Vista anymore.
if (::IsWindow(taskbar)) {
if (MonitorFromWindow(taskbar, MONITOR_DEFAULTTONEAREST) == monitor)
return true;
// In some cases like when the autohide taskbar is on the left of the
// secondary monitor, the MonitorFromWindow call above fails to return the
// correct monitor the taskbar is on. We fallback to MonitorFromPoint for
// the cursor position in that case, which seems to work well.
POINT cursorPos = { 0 };
GetCursorPos(&cursorPos);
if (MonitorFromPoint(cursorPos, MONITOR_DEFAULTTONEAREST) == monitor)
return true;
}
return false;
}
int __GetAppbarAutohideEdges(HMONITOR monitor) {
int edges = 0;
if (__MonitorHasAutohideTaskbarForEdge(ABE_LEFT, monitor))
edges |= EDGE_LEFT;
if (__MonitorHasAutohideTaskbarForEdge(ABE_TOP, monitor))
edges |= EDGE_TOP;
if (__MonitorHasAutohideTaskbarForEdge(ABE_RIGHT, monitor))
edges |= EDGE_RIGHT;
if (__MonitorHasAutohideTaskbarForEdge(ABE_BOTTOM, monitor))
edges |= EDGE_BOTTOM;
return edges;
}
int __GetSystemMetricsForDpi(int nIndex, UINT dpi) {
if (m_lpGetSystemMetricsForDpi)
return m_lpGetSystemMetricsForDpi(nIndex, dpi);
return GetSystemMetrics(nIndex);
}
UINT __GetDpiForWindow(HWND hWnd) {
if (m_lpGetDpiForWindow)
return m_lpGetDpiForWindow(hWnd);
FLOAT dpiX = 96.0, dpiY = 96.0;
if (m_pDirect2dFactory)
m_pDirect2dFactory->GetDesktopDpi(&dpiX, &dpiY);
return qRound(dpiX);
}
MONITORINFO __GetMonitorInfoForWindow(HWND hWnd)
{
HMONITOR mon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = { .cbSize = sizeof mi };
GetMonitorInfoW(mon, &mi);
return mi;
}
int __GetBorderWidthForWindow(HWND hWnd) {
uint dpi = __GetDpiForWindow(hWnd);
return __GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + __GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
}
}
}
shelllet::frameless::DelegateWindow::DelegateWindow(TitleBarWidget* widget)
: Object(*new DelegateWindowPrivate, nullptr)
{
Q_D(DelegateWindow);
d->titleBar = widget;
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10) {
JWF_RESOLVE_WINAPI(User32, GetSystemMetricsForDpi)
JWF_RESOLVE_WINAPI(User32, GetDpiForWindow)
}
if (!m_pDirect2dFactory) {
HRESULT hResult = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, m_pDirect2dFactory.GetAddressOf());
if (FAILED(hResult)) {
LOG_FATAL("frameless") << "Failed to create d2d factory: " << std::system_category().message(hResult) << std::endl;
}
}
}
bool shelllet::frameless::DelegateWindow::calcNcSize(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
Q_D(DelegateWindow);
union {
LPARAM lParam;
RECT* rect;
} params = { .lParam = lParam };
RECT ncclient = *params.rect;
DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, params.lParam);
RECT client = *params.rect;
if (::IsZoomed(hWnd))
{
int borderWidth = __GetBorderWidthForWindow(hWnd);
*params.rect = {
.left = client.left,
.top = ncclient.top + static_cast<LONG>(borderWidth), // Maximized window always have a border around.
.right = client.right,
.bottom = client.bottom,
};
HMONITOR mon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = __GetMonitorInfoForWindow(hWnd);
// reduce size when same monitor's rectangle.
if (EqualRect(params.rect, &mi.rcMonitor)) {
if (__GetAppbarAutohideEdges(mon) == EDGE_TOP) {
params.rect->top++;
}
else if (__GetAppbarAutohideEdges(mon) == EDGE_LEFT) {
params.rect->left++;
}
else if (__GetAppbarAutohideEdges(mon) == EDGE_BOTTOM) {
params.rect->bottom--;
}
else if (__GetAppbarAutohideEdges(mon) == EDGE_RIGHT) {
params.rect->right--;
}
}
QWindow* window = QWindow::fromWinId(reinterpret_cast<WId>(hWnd));
d->titleBar->setMaximumWidth(window->size().width());
}
else {
*params.rect = ncclient;
d->titleBar->setMaximumWidth(QWIDGETSIZE_MAX);
}
return true;
}
void shelllet::frameless::DelegateWindow::updateWindow(HWND hWnd, bool frameChange, bool redraw)
{
if (frameChange) {
SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER);
}
if (redraw) {
//RedrawWindow(hWnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOCHILDREN);
RedrawWindow(hWnd, NULL, NULL, RDW_UPDATENOW | RDW_ERASENOW | RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_NOCHILDREN);
}
}
int shelllet::frameless::DelegateWindow::getSystemTitleBarHeight(HWND hWnd)
{
const UINT dpi = 96;
if (IsZoomed(hWnd)) {
return qRound(__GetSystemMetricsForDpi(SM_CYCAPTION, dpi) * 0.75) + 5;
}
return __GetSystemMetricsForDpi(SM_CYCAPTION, dpi) +5;
}
long shelllet::frameless::DelegateWindow::hitNcTest(HWND hWnd, const QPoint& pos, const QRect& geometry, const SizePolicyFlags& flags)
{
Q_D(DelegateWindow);
int borderWidth = __GetBorderWidthForWindow(hWnd);
QRect rcMax = geometry;
QRect rcMin = geometry.marginsRemoved({ borderWidth, borderWidth, borderWidth, borderWidth });
QRect rcLeft = { QPoint{rcMax.left(), rcMin.top()}, rcMin.bottomLeft() };
QRect rcTop = { QPoint{rcMin.left(), rcMax.top()}, rcMin.topRight() };
QRect rctRight = { rcMin.topRight(),QPoint(rcMax.right(), rcMin.bottom()) };
QRect rcBottom = { rcMin.bottomLeft(), QPoint(rcMin.right(), rcMax.bottom()) };
QRect rcTopLeft = { rcMax.topLeft(), rcMin.topLeft() };
QRect rcTopRight = { QPoint{rcMin.right(), rcMax.top()}, QPoint(rcMax.right(), rcMin.top()) };
QRect rcBottomLeft = { QPoint(rcMax.left(), rcMin.bottom()),QPoint(rcMin.left(), rcMax.bottom()) };
QRect rcBottomRight = { rcMin.bottomRight(), rcMax.bottomRight() };
if (rcLeft.contains(pos)) {
return flags.testFlag(SizePolicy::FixedWidth) ? HTCLIENT : HTLEFT;
}
else if (rctRight.contains(pos)) {
return flags.testFlag(SizePolicy::FixedWidth) ? HTCLIENT : HTRIGHT;
}
else if (rcBottom.contains(pos)) {
return flags.testFlag(SizePolicy::FixedHeigth) ? HTCLIENT : HTBOTTOM;
}
else if (rcTop.contains(pos)) {
return flags.testFlag(SizePolicy::FixedHeigth) ? HTCLIENT : HTTOP;
}
else if (rcBottomLeft.contains(pos)) {
return (flags.testFlag(SizePolicy::FixedHeigth) || flags.testFlag(SizePolicy::FixedWidth)) ? HTCLIENT : HTBOTTOMLEFT;
}
else if (rcBottomRight.contains(pos)) {
return (flags.testFlag(SizePolicy::FixedHeigth) || flags.testFlag(SizePolicy::FixedWidth)) ? HTCLIENT : HTBOTTOMRIGHT;
}
else if (rcTopRight.contains(pos)) {
return (flags.testFlag(SizePolicy::FixedHeigth) || flags.testFlag(SizePolicy::FixedWidth)) ? HTCLIENT : HTTOPRIGHT;
}
else if (rcTopLeft.contains(pos)) {
return (flags.testFlag(SizePolicy::FixedHeigth) || flags.testFlag(SizePolicy::FixedWidth)) ? HTCLIENT : HTTOPLEFT;
}
else if (d->titleBar->underMouse()) {
return HTCAPTION;
}
return HTCLIENT;
}