-
Notifications
You must be signed in to change notification settings - Fork 8.3k
/
windowproc.cpp
949 lines (803 loc) · 32.7 KB
/
windowproc.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
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "Clipboard.hpp"
#include "ConsoleControl.hpp"
#include "find.h"
#include "menu.hpp"
#include "window.hpp"
#include "windowdpiapi.hpp"
#include "windowime.hpp"
#include "windowio.hpp"
#include "windowmetrics.hpp"
#include "..\..\host\_output.h"
#include "..\..\host\output.h"
#include "..\..\host\dbcs.h"
#include "..\..\host\handle.h"
#include "..\..\host\input.h"
#include "..\..\host\misc.h"
#include "..\..\host\registry.hpp"
#include "..\..\host\scrolling.hpp"
#include "..\..\host\srvinit.h"
#include "..\inc\ServiceLocator.hpp"
#include "..\interactivity\win32\windowtheme.hpp"
#include "..\interactivity\win32\CustomWindowMessages.h"
#include "..\interactivity\win32\windowUiaProvider.hpp"
#include <iomanip>
#include <sstream>
using namespace Microsoft::Console::Interactivity::Win32;
using namespace Microsoft::Console::Types;
// The static and specific window procedures for this class are contained here
#pragma region Window Procedure
[[nodiscard]] LRESULT CALLBACK Window::s_ConsoleWindowProc(_In_ HWND hWnd, _In_ UINT Message, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
// Save the pointer here to the specific window instance when one is created
if (Message == WM_CREATE)
{
const CREATESTRUCT* const pCreateStruct = reinterpret_cast<CREATESTRUCT*>(lParam);
Window* const pWindow = reinterpret_cast<Window*>(pCreateStruct->lpCreateParams);
SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pWindow));
}
// Dispatch the message to the specific class instance
Window* const pWindow = reinterpret_cast<Window*>(GetWindowLongPtrW(hWnd, GWLP_USERDATA));
if (pWindow != nullptr)
{
return pWindow->ConsoleWindowProc(hWnd, Message, wParam, lParam);
}
// If we get this far, call the default window proc
return DefWindowProcW(hWnd, Message, wParam, lParam);
}
[[nodiscard]] LRESULT CALLBACK Window::ConsoleWindowProc(_In_ HWND hWnd, _In_ UINT Message, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
Globals& g = ServiceLocator::LocateGlobals();
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
LRESULT Status = 0;
BOOL Unlock = TRUE;
LockConsole();
SCREEN_INFORMATION& ScreenInfo = GetScreenInfo();
if (hWnd == nullptr) // TODO: this might not be possible anymore
{
if (Message == WM_CLOSE)
{
_CloseWindow();
Status = 0;
}
else
{
Status = DefWindowProcW(hWnd, Message, wParam, lParam);
}
UnlockConsole();
return Status;
}
switch (Message)
{
case WM_CREATE:
{
// Load all metrics we'll need.
_UpdateSystemMetrics();
// The system is not great and the window rect is wrong the first time for High DPI (WM_DPICHANGED scales strangely.)
// So here we have to grab the DPI of the current window (now that we have a window).
// Then we have to re-propose a window size for our window that is scaled to DPI and SetWindowPos.
// First get the new DPI and update all the scaling factors in the console that are affected.
// NOTE: GetWindowDpi and/or GetDpiForWindow can be *WRONG* at this point in time depending on monitor configuration.
// They won't be correct until the window is actually shown. So instead of using those APIs, figure out the DPI
// based on the rectangle that is about to be shown using the nearest monitor.
// Get proposed window rect from create structure
CREATESTRUCTW* pcs = (CREATESTRUCTW*)lParam;
RECT rc;
rc.left = pcs->x;
rc.top = pcs->y;
rc.right = rc.left + pcs->cx;
rc.bottom = rc.top + pcs->cy;
// Find nearest montitor.
HMONITOR hmon = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST);
// This API guarantees that dpix and dpiy will be equal, but neither is an optional parameter so give two UINTs.
UINT dpix = USER_DEFAULT_SCREEN_DPI;
UINT dpiy = USER_DEFAULT_SCREEN_DPI;
GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, &dpix, &dpiy); // If this fails, we'll use the default of 96.
// Pick one and set it to the global DPI.
ServiceLocator::LocateGlobals().dpi = (int)dpix;
_UpdateSystemMetrics(); // scroll bars and cursors and such.
s_ReinitializeFontsForDPIChange(); // font sizes.
// Now re-propose the window size with the same origin.
RECT rectProposed = { rc.left, rc.top, 0, 0 };
_CalculateWindowRect(_pSettings->GetWindowSize(), &rectProposed);
SetWindowPos(hWnd, NULL, rectProposed.left, rectProposed.top, RECT_WIDTH(&rectProposed), RECT_HEIGHT(&rectProposed), SWP_NOACTIVATE | SWP_NOZORDER);
// Save the proposed window rect dimensions here so we can adjust if the system comes back and changes them on what we asked for.
ServiceLocator::LocateWindowMetrics<WindowMetrics>()->ConvertWindowRectToClientRect(&rectProposed);
break;
}
case WM_DROPFILES:
{
_HandleDrop(wParam);
break;
}
case WM_GETOBJECT:
{
Status = _HandleGetObject(hWnd, wParam, lParam);
break;
}
case WM_DESTROY:
{
// signal to uia that they can disconnect our uia provider
if (_pUiaProvider)
{
UiaReturnRawElementProvider(hWnd, 0, 0, NULL);
}
break;
}
case WM_SIZING:
{
// Signal that the user changed the window size, so we can return the value later for telemetry. By only
// sending the data back if the size has changed, helps reduce the amount of telemetry being sent back.
// WM_SIZING doesn't fire if they resize the window using Win-UpArrow, so we'll miss that scenario. We could
// listen to the WM_SIZE message instead, but they can fire when the window is being restored from being
// minimized, and not only when they resize the window.
Telemetry::Instance().SetWindowSizeChanged();
goto CallDefWin;
break;
}
case WM_GETDPISCALEDSIZE:
{
// This message will send us the DPI we're about to be changed to.
// Our goal is to use it to try to figure out the Window Rect that we'll need at that DPI to maintain
// the same client rendering that we have now.
// First retrieve the new DPI and the current DPI.
DWORD const dpiProposed = (WORD)wParam;
DWORD const dpiCurrent = g.dpi;
// Now we need to get what the font size *would be* if we had this new DPI. We need to ask the renderer about that.
const FontInfo& fiCurrent = ScreenInfo.GetCurrentFont();
FontInfoDesired fiDesired(fiCurrent);
FontInfo fiProposed(L"", 0, 0, { 0, 0 }, 0);
const HRESULT hr = g.pRender->GetProposedFont(dpiProposed, fiDesired, fiProposed);
// fiProposal will be updated by the renderer for this new font.
// GetProposedFont can fail if there's no render engine yet.
// This can happen if we're headless.
// Just assume that the font is 1x1 in that case.
const COORD coordFontProposed = SUCCEEDED(hr) ? fiProposed.GetSize() : COORD({ 1, 1 });
// Then from that font size, we need to calculate the client area.
// Then from the client area we need to calculate the window area (using the proposed DPI scalar here as well.)
// Retrieve the additional parameters we need for the math call based on the current window & buffer properties.
const Viewport viewport = ScreenInfo.GetViewport();
COORD coordWindowInChars = viewport.Dimensions();
const COORD coordBufferSize = ScreenInfo.GetTextBuffer().GetSize().Dimensions();
// Now call the math calculation for our proposed size.
RECT rectProposed = { 0 };
s_CalculateWindowRect(coordWindowInChars, dpiProposed, coordFontProposed, coordBufferSize, hWnd, &rectProposed);
// Prepare where we're going to keep our final suggestion.
SIZE* const pSuggestionSize = (SIZE*)lParam;
pSuggestionSize->cx = RECT_WIDTH(&rectProposed);
pSuggestionSize->cy = RECT_HEIGHT(&rectProposed);
// Format our final suggestion for consumption.
UnlockConsole();
return TRUE;
}
case WM_DPICHANGED:
{
_fInDPIChange = true;
ServiceLocator::LocateGlobals().dpi = HIWORD(wParam);
_UpdateSystemMetrics();
s_ReinitializeFontsForDPIChange();
if (IsInFullscreen())
{
// If we're a full screen window, completely ignore what the DPICHANGED says as it will be bigger than the monitor and
// instead just ensure that the window is still taking up the full screen.
SetIsFullscreen(true);
}
else
{
// this is the RECT that the system suggests.
RECT* const prcNewScale = (RECT*)lParam;
SetWindowPos(hWnd, HWND_TOP, prcNewScale->left, prcNewScale->top, RECT_WIDTH(prcNewScale), RECT_HEIGHT(prcNewScale), SWP_NOZORDER | SWP_NOACTIVATE);
}
_fInDPIChange = false;
break;
}
case WM_ACTIVATE:
{
// if we're activated by a mouse click, remember it so
// we don't pass the click on to the app.
if (LOWORD(wParam) == WA_CLICKACTIVE)
{
gci.Flags |= CONSOLE_IGNORE_NEXT_MOUSE_INPUT;
}
goto CallDefWin;
break;
}
case WM_SETFOCUS:
{
gci.ProcessHandleList.ModifyConsoleProcessFocus(TRUE);
gci.Flags |= CONSOLE_HAS_FOCUS;
gci.GetCursorBlinker().FocusStart();
HandleFocusEvent(TRUE);
// ActivateTextServices does nothing if already active so this is OK to be called every focus.
ActivateTextServices(ServiceLocator::LocateConsoleWindow()->GetWindowHandle(), GetImeSuggestionWindowPos);
// set the text area to have focus for accessibility consumers
if (_pUiaProvider)
{
LOG_IF_FAILED(_pUiaProvider->SetTextAreaFocus());
}
break;
}
case WM_KILLFOCUS:
{
gci.ProcessHandleList.ModifyConsoleProcessFocus(FALSE);
gci.Flags &= ~CONSOLE_HAS_FOCUS;
// turn it off when we lose focus.
gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().SetIsOn(false);
gci.GetCursorBlinker().FocusEnd();
HandleFocusEvent(FALSE);
break;
}
case WM_PAINT:
{
// Since we handle our own minimized window state, we need to
// check if we're minimized (iconic) and set our internal state flags accordingly.
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162483(v=vs.85).aspx
// NOTE: We will not get called to paint ourselves when minimized because we set an icon when registering the window class.
// That means this CONSOLE_IS_ICONIC is unnnecessary when/if we can decouple the drawing with D2D.
if (IsIconic(hWnd))
{
WI_SetFlag(gci.Flags, CONSOLE_IS_ICONIC);
}
else
{
WI_ClearFlag(gci.Flags, CONSOLE_IS_ICONIC);
}
LOG_IF_FAILED(_HandlePaint());
// NOTE: We cannot let the OS handle this message (meaning do NOT pass to DefWindowProc)
// or it will cause missing painted regions in scenarios without a DWM (like Core Server SKU).
// Ensure it is re-validated in this handler so we don't receive infinite WM_PAINTs after
// we have stored the invalid region data for the next trip around the renderer thread.
break;
}
case WM_ERASEBKGND:
{
break;
}
case WM_CLOSE:
{
// Write the final trace log during the WM_CLOSE message while the console process is still fully alive.
// This gives us time to query the process for information. We shouldn't really miss any useful
// telemetry between now and when the process terminates.
Telemetry::Instance().WriteFinalTraceLog();
_CloseWindow();
break;
}
case WM_SETTINGCHANGE:
{
try
{
WindowTheme theme;
LOG_IF_FAILED(theme.TrySetDarkMode(hWnd));
}
CATCH_LOG();
gci.GetCursorBlinker().SettingsChanged();
}
__fallthrough;
case WM_DISPLAYCHANGE:
{
_UpdateSystemMetrics();
break;
}
case WM_WINDOWPOSCHANGING:
{
// Enforce maximum size here instead of WM_GETMINMAXINFO.
// If we return it in WM_GETMINMAXINFO, then it will be enforced when snapping across DPI boundaries (bad.)
// Retrieve the suggested dimensions and make a rect and size.
LPWINDOWPOS lpwpos = (LPWINDOWPOS)lParam;
// We only need to apply restrictions if the size is changing.
if (!WI_IsFlagSet(lpwpos->flags, SWP_NOSIZE))
{
// Figure out the suggested dimensions
RECT rcSuggested;
rcSuggested.left = lpwpos->x;
rcSuggested.top = lpwpos->y;
rcSuggested.right = rcSuggested.left + lpwpos->cx;
rcSuggested.bottom = rcSuggested.top + lpwpos->cy;
SIZE szSuggested;
szSuggested.cx = RECT_WIDTH(&rcSuggested);
szSuggested.cy = RECT_HEIGHT(&rcSuggested);
// Figure out the current dimensions for comparison.
RECT rcCurrent = GetWindowRect();
// Determine whether we're being resized by someone dragging the edge or completely moved around.
bool fIsEdgeResize = false;
{
// We can only be edge resizing if our existing rectangle wasn't empty. If it was empty, we're doing the initial create.
if (!IsRectEmpty(&rcCurrent))
{
// If one or two sides are changing, we're being edge resized.
unsigned int cSidesChanging = 0;
if (rcCurrent.left != rcSuggested.left)
{
cSidesChanging++;
}
if (rcCurrent.right != rcSuggested.right)
{
cSidesChanging++;
}
if (rcCurrent.top != rcSuggested.top)
{
cSidesChanging++;
}
if (rcCurrent.bottom != rcSuggested.bottom)
{
cSidesChanging++;
}
if (cSidesChanging == 1 || cSidesChanging == 2)
{
fIsEdgeResize = true;
}
}
}
// If the window is maximized, let it do whatever it wants to do.
// If not, then restrict it to our maximum possible window.
if (!WI_IsFlagSet(GetWindowStyle(hWnd), WS_MAXIMIZE))
{
// Find the related monitor, the maximum pixel size,
// and the dpi for the suggested rect.
UINT dpiOfMaximum;
RECT rcMaximum;
if (fIsEdgeResize)
{
// If someone's dragging from the edge to resize in one direction, we want to make sure we never grow past the current monitor.
rcMaximum = ServiceLocator::LocateWindowMetrics<WindowMetrics>()->GetMaxWindowRectInPixels(&rcCurrent, &dpiOfMaximum);
}
else
{
// In other circumstances, assume we're snapping around or some other jump (TS).
// Just do whatever we're told using the new suggestion as the restriction monitor.
rcMaximum = ServiceLocator::LocateWindowMetrics<WindowMetrics>()->GetMaxWindowRectInPixels(&rcSuggested, &dpiOfMaximum);
}
// Only apply the maximum size restriction if the current DPI matches the DPI of the
// maximum rect. This keeps us from applying the wrong restriction if the monitor
// we're moving to has a different DPI but we've yet to get notified of that DPI
// change. If we do apply it, then we'll restrict the console window BEFORE its
// been resized for the DPI change, so we're likely to shrink the window too much
// or worse yet, keep it from moving entirely. We'll get a WM_DPICHANGED,
// resize the window, and then process the restriction in a few window messages.
if (((int)dpiOfMaximum == g.dpi) &&
((szSuggested.cx > RECT_WIDTH(&rcMaximum)) || (szSuggested.cy > RECT_HEIGHT(&rcMaximum))))
{
lpwpos->cx = std::min(RECT_WIDTH(&rcMaximum), szSuggested.cx);
lpwpos->cy = std::min(RECT_HEIGHT(&rcMaximum), szSuggested.cy);
// We usually add SWP_NOMOVE so that if the user is dragging the left or top edge
// and hits the restriction, then the window just stops growing, it doesn't
// move with the mouse. However during DPI changes, we need to allow a move
// because the RECT from WM_DPICHANGED has been specially crafted by win32k
// to keep the mouse cursor from jumping away from the caption bar.
if (!_fInDPIChange)
{
lpwpos->flags |= SWP_NOMOVE;
}
}
}
break;
}
else
{
goto CallDefWin;
}
}
case WM_WINDOWPOSCHANGED:
{
// Only handle this if the DPI is the same as last time.
// If the DPI is different, assume we're about to get a DPICHANGED notification
// which will have a better suggested rectangle than this one.
// NOTE: This stopped being possible in RS4 as the DPI now changes when and only when
// we receive WM_DPICHANGED. We keep this check around so that we perform better downlevel.
int const dpi = ServiceLocator::LocateHighDpiApi<WindowDpiApi>()->GetWindowDPI(hWnd);
if (dpi == ServiceLocator::LocateGlobals().dpi)
{
_HandleWindowPosChanged(lParam);
}
break;
}
case WM_CONTEXTMENU:
{
Telemetry::Instance().SetContextMenuUsed();
if (DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam) == HTCLIENT)
{
HMENU hHeirMenu = Menu::s_GetHeirMenuHandle();
Unlock = FALSE;
UnlockConsole();
TrackPopupMenuEx(hHeirMenu,
TPM_RIGHTBUTTON | (GetSystemMetrics(SM_MENUDROPALIGNMENT) == 0 ? TPM_LEFTALIGN : TPM_RIGHTALIGN),
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
hWnd,
nullptr);
}
else
{
goto CallDefWin;
}
break;
}
case WM_NCLBUTTONDOWN:
{
// allow user to move window even when bigger than the screen
switch (wParam & 0x00FF)
{
case HTCAPTION:
UnlockConsole();
Unlock = FALSE;
SetActiveWindow(hWnd);
SendMessageTimeoutW(hWnd, WM_SYSCOMMAND, SC_MOVE | wParam, lParam, SMTO_NORMAL, INFINITE, nullptr);
break;
default:
goto CallDefWin;
}
break;
}
case WM_KEYDOWN:
case WM_KEYUP:
case WM_CHAR:
case WM_DEADCHAR:
{
HandleKeyEvent(hWnd, Message, wParam, lParam, &Unlock);
break;
}
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_SYSCHAR:
case WM_SYSDEADCHAR:
{
if (HandleSysKeyEvent(hWnd, Message, wParam, lParam, &Unlock))
{
goto CallDefWin;
}
break;
}
case WM_COMMAND:
// If this is an edit command from the context menu, treat it like a sys command.
if ((wParam < ID_CONSOLE_COPY) || (wParam > ID_CONSOLE_SELECTALL))
{
break;
}
__fallthrough;
case WM_SYSCOMMAND:
if (wParam == ID_CONSOLE_MARK)
{
Selection::Instance().InitializeMarkSelection();
}
else if (wParam == ID_CONSOLE_COPY)
{
Clipboard::Instance().Copy();
}
else if (wParam == ID_CONSOLE_PASTE)
{
Clipboard::Instance().Paste();
}
else if (wParam == ID_CONSOLE_SCROLL)
{
Scrolling::s_DoScroll();
}
else if (wParam == ID_CONSOLE_FIND)
{
DoFind();
Unlock = FALSE;
}
else if (wParam == ID_CONSOLE_SELECTALL)
{
Selection::Instance().SelectAll();
}
else if (wParam == ID_CONSOLE_CONTROL)
{
Menu::s_ShowPropertiesDialog(hWnd, FALSE);
}
else if (wParam == ID_CONSOLE_DEFAULTS)
{
Menu::s_ShowPropertiesDialog(hWnd, TRUE);
}
else
{
goto CallDefWin;
}
break;
case WM_HSCROLL:
{
HorizontalScroll(LOWORD(wParam), HIWORD(wParam));
break;
}
case WM_VSCROLL:
{
VerticalScroll(LOWORD(wParam), HIWORD(wParam));
break;
}
case WM_INITMENU:
{
HandleMenuEvent(WM_INITMENU);
Menu::Instance()->Initialize();
break;
}
case WM_MENUSELECT:
{
if (HIWORD(wParam) == 0xffff)
{
HandleMenuEvent(WM_MENUSELECT);
}
break;
}
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_LBUTTONDBLCLK:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_RBUTTONDBLCLK:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_MBUTTONDBLCLK:
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
{
if (HandleMouseEvent(ScreenInfo, Message, wParam, lParam))
{
if (Message != WM_MOUSEWHEEL && Message != WM_MOUSEHWHEEL)
{
goto CallDefWin;
}
}
else
{
break;
}
// Don't handle zoom.
if (wParam & MK_CONTROL)
{
goto CallDefWin;
}
Status = 1;
bool isMouseWheel = Message == WM_MOUSEWHEEL;
bool isMouseHWheel = Message == WM_MOUSEHWHEEL;
if (isMouseWheel || isMouseHWheel)
{
short wheelDelta = (short)HIWORD(wParam);
bool hasShift = (wParam & MK_SHIFT) ? true : false;
Scrolling::s_HandleMouseWheel(isMouseWheel,
isMouseHWheel,
wheelDelta,
hasShift,
ScreenInfo);
}
break;
}
case CM_SET_WINDOW_SIZE:
{
Status = _InternalSetWindowSize();
break;
}
case CM_BEEP:
{
UnlockConsole();
Unlock = FALSE;
// Don't fall back to Beep() on win32 systems -- if the user configures their system for no sound, we should
// respect that.
PlaySoundW((LPCWSTR)SND_ALIAS_SYSTEMHAND, nullptr, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
break;
}
case CM_UPDATE_SCROLL_BARS:
{
ScreenInfo.InternalUpdateScrollBars();
break;
}
case CM_UPDATE_TITLE:
{
SetWindowTextW(hWnd, gci.GetTitleAndPrefix().c_str());
break;
}
case CM_UPDATE_EDITKEYS:
{
// Re-read the edit key settings from registry.
Registry reg(&gci);
reg.GetEditKeys(NULL);
break;
}
#ifdef DBG
case CM_SET_KEY_STATE:
{
const int keyboardInputTableStateSize = 256;
if (wParam < keyboardInputTableStateSize)
{
BYTE keyState[keyboardInputTableStateSize];
GetKeyboardState(keyState);
keyState[wParam] = static_cast<BYTE>(lParam);
SetKeyboardState(keyState);
}
else
{
LOG_HR_MSG(E_INVALIDARG, "CM_SET_KEY_STATE invalid wParam");
}
break;
}
case CM_SET_KEYBOARD_LAYOUT:
{
try
{
std::wstringstream wss;
wss << std::setfill(L'0') << std::setw(8) << wParam;
std::wstring wstr(wss.str());
LoadKeyboardLayout(wstr.c_str(), KLF_ACTIVATE);
}
catch (...)
{
LOG_HR_MSG(wil::ResultFromCaughtException(), "CM_SET_KEYBOARD_LAYOUT exception");
}
break;
}
#endif DBG
case EVENT_CONSOLE_CARET:
case EVENT_CONSOLE_UPDATE_REGION:
case EVENT_CONSOLE_UPDATE_SIMPLE:
case EVENT_CONSOLE_UPDATE_SCROLL:
case EVENT_CONSOLE_LAYOUT:
case EVENT_CONSOLE_START_APPLICATION:
case EVENT_CONSOLE_END_APPLICATION:
{
NotifyWinEvent(Message, hWnd, (LONG)wParam, (LONG)lParam);
break;
}
default:
CallDefWin:
{
if (Unlock)
{
UnlockConsole();
Unlock = FALSE;
}
Status = DefWindowProcW(hWnd, Message, wParam, lParam);
break;
}
}
if (Unlock)
{
UnlockConsole();
}
return Status;
}
#pragma endregion
// Helper handler methods for specific cases within the large window procedure are in this section
#pragma region Message Handlers
void Window::_HandleWindowPosChanged(const LPARAM lParam)
{
HWND hWnd = GetWindowHandle();
SCREEN_INFORMATION& ScreenInfo = GetScreenInfo();
LPWINDOWPOS const lpWindowPos = (LPWINDOWPOS)lParam;
// If the frame changed, update the system metrics.
if (WI_IsFlagSet(lpWindowPos->flags, SWP_FRAMECHANGED))
{
_UpdateSystemMetrics();
}
// This message is sent as the result of someone calling SetWindowPos(). We use it here to set/clear the
// CONSOLE_IS_ICONIC bit appropriately. doing so in the WM_SIZE handler is incorrect because the WM_SIZE
// comes after the WM_ERASEBKGND during SetWindowPos() processing, and the WM_ERASEBKGND needs to know if
// the console window is iconic or not.
if (!ScreenInfo.ResizingWindow && (lpWindowPos->cx || lpWindowPos->cy) && !IsIconic(hWnd))
{
// calculate the dimensions for the newly proposed window rectangle
RECT rcNew;
s_ConvertWindowPosToWindowRect(lpWindowPos, &rcNew);
ServiceLocator::LocateWindowMetrics<WindowMetrics>()->ConvertWindowRectToClientRect(&rcNew);
// If the window is not being resized, including a DPI change, then
// don't do anything except update our windowrect
if (!WI_IsFlagSet(lpWindowPos->flags, SWP_NOSIZE) || _fInDPIChange)
{
ScreenInfo.ProcessResizeWindow(&rcNew, &_rcClientLast);
}
// now that operations are complete, save the new rectangle size as the last seen value
_rcClientLast = rcNew;
}
}
// Routine Description:
// - This helper method for the window procedure will handle the WM_PAINT message
// - It will retrieve the invalid rectangle and dispatch that information to the attached renderer
// (if available). It will then attempt to validate/finalize the paint to appease the system
// and prevent more WM_PAINTs from coming back (until of course something else causes an invalidation).
// Arguments:
// - <none>
// Return Value:
// - S_OK if we succeeded. ERROR_INVALID_HANDLE if there is no HWND. E_FAIL if GDI failed for some reason.
[[nodiscard]] HRESULT Window::_HandlePaint() const
{
HWND const hwnd = GetWindowHandle();
RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE), hwnd);
// We have to call BeginPaint to retrieve the invalid rectangle state
// BeginPaint/EndPaint does a bunch of other magic in the system level
// that we can't sufficiently replicate with GetInvalidRect/ValidateRect.
// ---
// We've tried in the past to not call BeginPaint/EndPaint
// and under certain circumstances (windows with SW_HIDE, SKUs without DWM, etc.)
// the system either sends WM_PAINT messages ad nauseum or fails to redraw everything correctly.
PAINTSTRUCT ps;
HDC const hdc = BeginPaint(hwnd, &ps);
RETURN_HR_IF_NULL(E_FAIL, hdc);
if (ServiceLocator::LocateGlobals().pRender != nullptr)
{
// In lieu of actually painting right now, we're just going to aggregate this information in the renderer
// and let it paint whenever it feels appropriate.
RECT const rcUpdate = ps.rcPaint;
ServiceLocator::LocateGlobals().pRender->TriggerSystemRedraw(&rcUpdate);
}
LOG_IF_WIN32_BOOL_FALSE(EndPaint(hwnd, &ps));
return S_OK;
}
// Routine Description:
// - This routine is called when ConsoleWindowProc receives a WM_DROPFILES message.
// - It initially calls DragQueryFile() to calculate the number of files dropped and then DragQueryFile() is called to retrieve the filename.
// - DoStringPaste() pastes the filename to the console window
// Arguments:
// - wParam - Identifies the structure containing the filenames of the dropped files.
// - Console - Pointer to CONSOLE_INFORMATION structure
// Return Value:
// - <none>
void Window::_HandleDrop(const WPARAM wParam) const
{
WCHAR szPath[MAX_PATH];
BOOL fAddQuotes;
if (DragQueryFile((HDROP)wParam, 0, szPath, ARRAYSIZE(szPath)) != 0)
{
// Log a telemetry flag saying the user interacted with the Console
// Only log when DragQueryFile succeeds, because if we don't when the console starts up, we're seeing
// _HandleDrop get called multiple times (and DragQueryFile fail),
// which can incorrectly mark this console session as interactive.
Telemetry::Instance().SetUserInteractive();
fAddQuotes = (wcschr(szPath, L' ') != nullptr);
if (fAddQuotes)
{
Clipboard::Instance().StringPaste(L"\"", 1);
}
Clipboard::Instance().StringPaste(szPath, wcslen(szPath));
if (fAddQuotes)
{
Clipboard::Instance().StringPaste(L"\"", 1);
}
}
}
[[nodiscard]] LRESULT Window::_HandleGetObject(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
{
LRESULT retVal = 0;
// If we are receiving a request from Microsoft UI Automation framework, then return the basic UIA COM interface.
if (static_cast<long>(lParam) == static_cast<long>(UiaRootObjectId))
{
// NOTE: Deliverable MSFT: 10881045 is required before this will work properly.
// The UIAutomationCore.dll cannot currently handle the fact that our HWND is assigned to the child PID.
// It will attempt to set up events/pipes on the wrong PID/HWND combination when called here.
// A temporary workaround until that is delivered is to disable window handle reparenting using
// ConsoleControl's ConsoleSetWindowOwner call.
retVal = UiaReturnRawElementProvider(hwnd, wParam, lParam, _GetUiaProvider());
}
// Otherwise, return 0. We don't implement MS Active Accessibility (the other framework that calls WM_GETOBJECT).
return retVal;
}
#pragma endregion
// Dispatchers are used to post or send a window message into the queue from other portions of the codebase without accessing internal properties directly
#pragma region Dispatchers
BOOL Window::PostUpdateWindowSize() const
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const SCREEN_INFORMATION& ScreenInfo = GetScreenInfo();
if (ScreenInfo.ConvScreenInfo != nullptr)
{
return FALSE;
}
if (gci.Flags & CONSOLE_SETTING_WINDOW_SIZE)
{
return FALSE;
}
gci.Flags |= CONSOLE_SETTING_WINDOW_SIZE;
return PostMessageW(GetWindowHandle(), CM_SET_WINDOW_SIZE, (WPARAM)&ScreenInfo, 0);
}
BOOL Window::SendNotifyBeep() const
{
return SendNotifyMessageW(GetWindowHandle(), CM_BEEP, 0, 0);
}
BOOL Window::PostUpdateScrollBars() const
{
return PostMessageW(GetWindowHandle(), CM_UPDATE_SCROLL_BARS, (WPARAM)&GetScreenInfo(), 0);
}
BOOL Window::PostUpdateExtendedEditKeys() const
{
return PostMessageW(GetWindowHandle(), CM_UPDATE_EDITKEYS, 0, 0);
}
#pragma endregion