-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathThrobberBand.cpp
515 lines (427 loc) · 13.2 KB
/
ThrobberBand.cpp
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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
/*
* ThrobberBand.cpp: Implements the throbber/brand band. This displays the Windows logo
* in the upper-right corner of the Explorer window.
*/
#include "stdafx.h"
#include "framework.h"
#include "resource.h"
#include "ClassicExplorer_i.h"
#include "dllmain.h"
#include <commoncontrols.h>
#include "util.h"
#include "ThrobberBand.h"
void ThrobberBand::ClearResources()
{
DeleteObject(m_hBitmap);
m_pWebBrowser.Release();
}
/*
* OnPaint: Paint the background and the logo.
*/
LRESULT ThrobberBand::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
PAINTSTRUCT paintInfo;
RECT clientRect;
HDC dc = BeginPaint(&paintInfo);
GetClientRect(&clientRect);
// Calculate destination point:
POINT destinationPoint;
destinationPoint.x = (clientRect.right - clientRect.left - m_cxCurBmp) / 2;
destinationPoint.y = (clientRect.bottom - clientRect.top - m_cyCurBmp) / 2;
::SetBkColor(dc, RGB(255, 255, 255));
// Draw the background
HBRUSH bgBrush = CreateSolidBrush(RGB(255, 255, 255));
FillRect(dc, &clientRect, bgBrush);
DeleteObject(bgBrush);
HDC sourceDc = CreateCompatibleDC(dc);
HBITMAP oldBitmap = (HBITMAP)SelectObject(sourceDc, m_hBitmap);
BitBlt(
dc,
destinationPoint.x,
destinationPoint.y,
m_cxCurBmp,
m_cyCurBmp,
sourceDc,
0,
0,
SRCCOPY
);
SelectObject(sourceDc, oldBitmap);
DeleteDC(sourceDc);
EndPaint(&paintInfo);
return S_OK;
}
/*
* OnSize: Handle size messages and reload the desired logo bitmap for the current size.
*/
LRESULT ThrobberBand::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
LoadBitmapForSize();
return DefWindowProcW(uMsg, wParam, lParam);
}
/*
* OnEraseBackground: We always paint our own background, so there's no need to ever do this.
*/
LRESULT ThrobberBand::OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
return 1;
}
/*
* LoadBitmapForSize: Load the desired throbber icon for the current size of the band.
*/
LRESULT ThrobberBand::LoadBitmapForSize()
{
RECT curRect;
GetClientRect(&curRect);
DeleteObject(m_hBitmap);
int cySelf = curRect.bottom - curRect.top;
int resourceId = IDB_THROBBER_SIZE_SMALL;
if (cySelf >= 38)
{
resourceId = IDB_THROBBER_SIZE_LARGE;
}
else if (cySelf >= 26)
{
resourceId = IDB_THROBBER_SIZE_MID;
}
else
{
resourceId = IDB_THROBBER_SIZE_SMALL;
}
m_hBitmap = LoadBitmapW(
_AtlBaseModule.GetResourceInstance(),
MAKEINTRESOURCEW(resourceId)
);
BITMAP bmp;
GetObject(m_hBitmap, sizeof(bmp), &bmp);
m_cxCurBmp = bmp.bmWidth;
m_cyCurBmp = bmp.bmHeight;
return S_OK;
}
/*
* CorrectBandSize: Correct the height of the band to be displayed.
*
* This may seem like a hacky solution, but it is actually necessary, as in my testing, using
* the default integral scaling would only scale up and never scale back down to fit sibling
* toolbars on the same row.
*
* Using this solution, the band is resized by sending some messages to the ReBar API which is
* owned by Explorer.
*
* I spent several days straight trying to figure out how to do this; please don't make fun of
* me too much...
*/
LRESULT ThrobberBand::CorrectBandSize()
{
RECT curRect;
GetClientRect(&curRect);
UINT initialTrHeight = ::SendMessageW(m_parentRebar, RB_GETROWHEIGHT, 0, 0);
REBARBANDINFOW curBandInfo;
curBandInfo.cbSize = sizeof(REBARBANDINFOW);
curBandInfo.fMask = RBBIM_CHILD;
// This was never an officially-supported feature of shell rebars in Windows, so there's
// no function to just get the interface for our own rebar band.
for (int i = 0; i < ::SendMessageW(m_parentRebar, RB_GETBANDCOUNT, 0, 0); i++)
{
::SendMessageW(m_parentRebar, RB_GETBANDINFO, i, (LPARAM)&curBandInfo);
if (::IsWindow(curBandInfo.hwndChild) && curBandInfo.hwndChild == m_hWnd)
{
// Get the rebar info:
REBARBANDINFOW curBandInfo2;
curBandInfo2.cbSize = sizeof(REBARBANDINFOW);
curBandInfo2.fMask = RBBIM_SIZE | RBBIM_CHILDSIZE;
::SendMessageW(m_parentRebar, RB_GETBANDINFO, i, (LPARAM)&curBandInfo2);
// Set the bar to the minimum size possible.
// Yes, it was necessary to define all of these, or it would half in horizontal size
// every time this function was called.
curBandInfo2.fMask = RBBIM_CHILDSIZE;
curBandInfo2.cxMinChild = 38;
curBandInfo2.cyMinChild = 22;
curBandInfo2.cyChild = 22;
curBandInfo2.cyIntegral = 1;
::SendMessageW(m_parentRebar, RB_SETBANDINFOW, i, (LPARAM)&curBandInfo2);
// Resize the bar back up to what it should be: the size of the topmost row.
UINT topRowHeight = ::SendMessageW(m_parentRebar, RB_GETROWHEIGHT, 0, 0);
curBandInfo2.cyChild = topRowHeight;
::SendMessageW(m_parentRebar, RB_SETBANDINFOW, i, (LPARAM)&curBandInfo2);
// Explorer isn't notified of this resize, so we need to manually invalidate the
// visual or a vertical gap may be left under the rebar.
if (initialTrHeight > topRowHeight)
{
m_shouldManuallyCorrectHeight = true;
}
}
}
return S_OK;
}
/*
* ShouldRefreshVisual: Determines if the visual needs manual correction from our code.
*/
bool ThrobberBand::ShouldRefreshVisual()
{
if (::IsWindow(m_parentRebar))
{
int numRebars = ::SendMessageW(m_parentRebar, RB_GETBANDCOUNT, 0, 0);
// If there is only one rebar control for whatever reason, then we don't want to try
// redrawing or a redraw loop may occur.
if (numRebars > 1)
{
RECT rcOffset;
GetClientRect(&rcOffset);
::MapWindowPoints(m_hWnd, m_parentRebar, (LPPOINT)&rcOffset, 2);
WCHAR buf[512];
wsprintf(buf, L"%d, %d", rcOffset.left, rcOffset.top);
//MessageBoxW(buf, L"hi!", MB_OK);
if (rcOffset.left < 12)
{
return true;
}
}
}
return false;
}
void ThrobberBand::PerformRedrawCheck()
{
const int checkDuration = 500;
const int allowedRedrawsInTimeSpan = 50;
unsigned int previousRedrawTime = m_latestRedrawTime;
m_latestRedrawTime = GetTickCount();
if (m_latestRedrawTime < previousRedrawTime + checkDuration)
{
m_redrawCounter++;
if (m_redrawCounter > allowedRedrawsInTimeSpan)
{
m_disableRedraws = true;
#ifdef DEBUG
MessageBoxW(L"Redraw loop mitigated.");
#endif
}
}
else
{
m_redrawCounter = 0;
}
}
/*
* RebarParentSubclassProc: This reads notifications (not messages) of the rebar, which is done by hooking
* its parent (the shell WorkerW).
*
* This is used to initialise the size-correction routine, since it works in a predictable manner, sending
* notifications about changes to the parent before enacting those changes on the child.
*/
LRESULT CALLBACK ThrobberBand::RebarParentSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
ThrobberBand *self = (ThrobberBand *)dwRefData;
if (uMsg == WM_NOTIFY)
{
LPNMHDR hdr = (LPNMHDR)lParam;
switch (hdr->code)
{
case RBN_HEIGHTCHANGE:
case RBN_LAYOUTCHANGED:
{
self->CorrectBandSize();
break;
}
}
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
/*
* RebarSubclassProc: This reads messages sent to the rebar itself, which is used to send out the sizing
* command when it is needed.
*/
LRESULT CALLBACK ThrobberBand::RebarSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
ThrobberBand *self = (ThrobberBand *)dwRefData;
if (uMsg == WM_SIZE)
{
if (self->m_shouldManuallyCorrectHeight)
{
// Mark the correction as no longer necessary as we handle it here:
self->m_shouldManuallyCorrectHeight = false;
LRESULT result = DefSubclassProc(hWnd, uMsg, wParam, lParam);
CEUtil::FixExplorerSizesIfNecessary(self->m_parentRebar);
return result;
}
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
//================================================================================================================
// implement IDeskBand:
//
STDMETHODIMP ThrobberBand::GetBandInfo(DWORD dwBandId, DWORD dwViewMode, DESKBANDINFO *pDbi)
{
/*if (!m_subclassedRebar)
{
m_parentRebar = GetParent();
SetWindowSubclass(::GetParent(m_parentRebar), RebarParentSubclassProc, (UINT_PTR)this, (UINT_PTR)this);
SetWindowSubclass(m_parentRebar, RebarSubclassProc, (UINT_PTR)this, (UINT_PTR)this);
m_subclassedRebar = true;
}*/
if (pDbi)
{
if (pDbi->dwMask & DBIM_MINSIZE)
{
pDbi->ptMinSize.x = 38;
pDbi->ptMinSize.y = 22;
}
if (pDbi->dwMask & DBIM_MAXSIZE)
{
// The throbber should be able to adjust to the size of any sibling rebar, but
// should always be locked to a size of 38 pixels horizontally.
pDbi->ptMaxSize.x = 38;
pDbi->ptMaxSize.y = -1;
}
if (pDbi->dwMask & DBIM_INTEGRAL)
{
// We want the throbber to grow organically alongside sibling rebars, without any
// blank spaces. As such, it is to grow every 1 pixel vertically.
pDbi->ptIntegral.x = 0;
pDbi->ptIntegral.y = 1;
}
if (pDbi->dwMask & DBIM_ACTUAL)
{
pDbi->ptActual.x = 38;
pDbi->ptActual.y = -1;
}
if (pDbi->dwMask & DBIM_TITLE)
{
// No title.
wcscpy_s(pDbi->wszTitle, L"");
}
if (pDbi->dwMask & DBIM_MODEFLAGS)
{
pDbi->dwModeFlags = DBIMF_FIXED | DBIMF_TOPALIGN | DBIMF_VARIABLEHEIGHT;
}
if (pDbi->dwMask & DBIM_BKCOLOR)
{
// We draw the background ourselves (in OnPaint), so there's no need to bother
// with it here.
pDbi->crBkgnd = 0;
}
}
return S_OK;
}
//================================================================================================================
// implement IOleWindow:
//
STDMETHODIMP ThrobberBand::GetWindow(HWND *hWnd)
{
if (!hWnd)
{
return E_INVALIDARG;
}
*hWnd = m_hWnd;
return S_OK;
}
STDMETHODIMP ThrobberBand::ContextSensitiveHelp(BOOL fEnterMode)
{
return S_OK;
}
//================================================================================================================
// implement IDockingWindow:
//
STDMETHODIMP ThrobberBand::CloseDW(unsigned long dwReserved)
{
ShowDW(FALSE);
if (IsWindow())
DestroyWindow();
m_hWnd = NULL;
return S_OK;
}
STDMETHODIMP ThrobberBand::ResizeBorderDW(const RECT *pRcBorder, IUnknown *pUnkToolbarSite, BOOL fReserved)
{
return E_NOTIMPL;
}
STDMETHODIMP ThrobberBand::ShowDW(BOOL fShow)
{
if (m_hWnd)
ShowWindow(fShow ? SW_SHOW : SW_HIDE);
return S_OK;
}
//================================================================================================================
// implement IObjectWithSite:
//
/**
* SetSite: Responsible for installation or removal of the toolbar band from a location
* provided by Explorer.
*
* This function is additionally responsible for obtaining the shell control APIs and creating
* the inner toolbar window.
*/
STDMETHODIMP ThrobberBand::SetSite(IUnknown *pUnkSite)
{
IObjectWithSiteImpl<ThrobberBand>::SetSite(pUnkSite);
HRESULT hr;
CComPtr<IOleWindow> oleWindow;
if (pUnkSite == NULL)
{
ClearResources();
return S_OK;
}
HWND hWndParent = NULL;
CComQIPtr<IOleWindow> pOleWindow = pUnkSite;
if (pOleWindow)
pOleWindow->GetWindow(&hWndParent);
if (!::IsWindow(hWndParent))
{
return E_FAIL;
}
this->Create(
hWndParent,
NULL,
NULL,
WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
);
if (!IsWindow())
return E_FAIL;
CComQIPtr<IServiceProvider> pProvider = pUnkSite;
if (pProvider)
{
// No error handling; this can fail safely.
pProvider->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, (void **)&m_pWebBrowser);
if (m_pWebBrowser)
{
if (m_dwEventCookie == 0xFEFEFEFE)
{
//MessageBox(L"d");
DispEventAdvise(m_pWebBrowser, &DIID_DWebBrowserEvents2);
}
}
}
LoadBitmapForSize();
// Subclass helpers:
m_parentRebar = GetParent();
SetWindowSubclass(::GetParent(m_parentRebar), RebarParentSubclassProc, (UINT_PTR)this, (UINT_PTR)this);
SetWindowSubclass(m_parentRebar, RebarSubclassProc, (UINT_PTR)this, (UINT_PTR)this);
m_subclassedRebar = true;
// Explorer may initialise our position onto a separate rebar until the sizes are
// invalidated, so let's manually invalidate to correct the position:
CEUtil::FixExplorerSizes(this->m_hWnd);
return S_OK;
}
//================================================================================================================
// handle DWebBrowserEvents2:
//
STDMETHODIMP ThrobberBand::OnNavigateComplete(IDispatch *pDisp, VARIANT *url)
{
//MessageBox(L"fuck you");
CEUtil::FixExplorerSizes(this->m_hWnd);
//::SendMessageW(m_parentRebar, WM_SIZE, 0, 1);
return S_OK;
}
/**
* OnQuit: Called when the user attempts to quit the browser.
*
* Copied from Open-Shell implementation here:
* https://github.com/Open-Shell/Open-Shell-Menu/blob/master/Src/ClassicExplorer/ExplorerBand.cpp#L2280-L2285
*/
STDMETHODIMP ThrobberBand::OnQuit()
{
if (m_pWebBrowser && m_dwEventCookie != 0xFEFEFEFE)
{
return DispEventUnadvise(m_pWebBrowser, &DIID_DWebBrowserEvents2);
}
return S_OK;
}