-
Notifications
You must be signed in to change notification settings - Fork 8.3k
/
windowio.cpp
1118 lines (1000 loc) · 42 KB
/
windowio.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
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "windowio.hpp"
#include "ConsoleControl.hpp"
#include "find.h"
#include "clipboard.hpp"
#include "consoleKeyInfo.hpp"
#include "window.hpp"
#include "../../host/ApiRoutines.h"
#include "../../host/init.hpp"
#include "../../host/input.h"
#include "../../host/handle.h"
#include "../../host/scrolling.hpp"
#include "../../host/output.h"
#include "../inc/ServiceLocator.hpp"
#pragma hdrstop
using namespace Microsoft::Console::Interactivity::Win32;
using namespace Microsoft::Console::VirtualTerminal;
using Microsoft::Console::Interactivity::ServiceLocator;
// For usage with WM_SYSKEYDOWN message processing.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646286(v=vs.85).aspx
// Bit 29 is whether ALT was held when the message was posted.
#define WM_SYSKEYDOWN_ALT_PRESSED (0x20000000)
// This magic flag is "documented" at https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx
// "If the high-order bit is 1, the key is down; otherwise, it is up."
static constexpr short KeyPressed{ gsl::narrow_cast<short>(0x8000) };
// ----------------------------
// Helpers
// ----------------------------
ULONG ConvertMouseButtonState(_In_ ULONG Flag, _In_ ULONG State)
{
if (State & MK_LBUTTON)
{
Flag |= FROM_LEFT_1ST_BUTTON_PRESSED;
}
if (State & MK_MBUTTON)
{
Flag |= FROM_LEFT_2ND_BUTTON_PRESSED;
}
if (State & MK_RBUTTON)
{
Flag |= RIGHTMOST_BUTTON_PRESSED;
}
return Flag;
}
/*
* This routine tells win32k what process we want to use to masquerade as the
* owner of conhost's window. If ProcessData is nullptr that means the root process
* has exited so we need to find any old process to be the owner. If this console
* has no processes attached to it -- it's only being kept alive by references
* via IO handles -- then we'll just set the owner to conhost.exe itself.
*/
VOID SetConsoleWindowOwner(const HWND hwnd, _Inout_opt_ ConsoleProcessHandle* pProcessData)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
FAIL_FAST_IF(!(gci.IsConsoleLocked()));
DWORD dwProcessId;
DWORD dwThreadId;
if (nullptr != pProcessData)
{
dwProcessId = pProcessData->dwProcessId;
dwThreadId = pProcessData->dwThreadId;
}
else
{
// Find a process to own the console window. If there are none then let's use conhost's.
pProcessData = gci.ProcessHandleList.GetFirstProcess();
if (pProcessData != nullptr)
{
dwProcessId = pProcessData->dwProcessId;
dwThreadId = pProcessData->dwThreadId;
pProcessData->fRootProcess = true;
}
else
{
dwProcessId = GetCurrentProcessId();
dwThreadId = GetCurrentThreadId();
}
}
CONSOLEWINDOWOWNER ConsoleOwner;
ConsoleOwner.hwnd = hwnd;
ConsoleOwner.ProcessId = dwProcessId;
ConsoleOwner.ThreadId = dwThreadId;
// Comment out this line to enable UIA tree to be visible until UIAutomationCore.dll can support our scenario.
LOG_IF_FAILED(ServiceLocator::LocateConsoleControl<Microsoft::Console::Interactivity::Win32::ConsoleControl>()
->Control(ConsoleControl::ControlType::ConsoleSetWindowOwner,
&ConsoleOwner,
sizeof(ConsoleOwner)));
}
// ----------------------------
// Window Message Handlers
// (called by windowproc)
// ----------------------------
// Routine Description:
// - Handler for detecting whether a key-press event can be appropriately converted into a terminal sequence.
// Will only trigger when virtual terminal input mode is set via STDIN handle
// Arguments:
// - pInputRecord - Input record event from the general input event handler
// Return Value:
// - True if the modes were appropriate for converting to a terminal sequence AND there was a matching terminal sequence for this key. False otherwise.
bool HandleTerminalMouseEvent(const til::point cMousePosition,
const unsigned int uiButton,
const short sModifierKeystate,
const short sWheelDelta)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// If the modes don't align, this is unhandled by default.
auto fWasHandled = false;
// Virtual terminal input mode
if (IsInVirtualTerminalInputMode())
{
const TerminalInput::MouseButtonState state{
WI_IsFlagSet(OneCoreSafeGetKeyState(VK_LBUTTON), KeyPressed),
WI_IsFlagSet(OneCoreSafeGetKeyState(VK_MBUTTON), KeyPressed),
WI_IsFlagSet(OneCoreSafeGetKeyState(VK_RBUTTON), KeyPressed)
};
// GH#6401: VT applications should be able to receive mouse events from outside the
// terminal buffer. This is likely to happen when the user drags the cursor offscreen.
// We shouldn't throw away perfectly good events when they're offscreen, so we just
// clamp them to be within the range [(0, 0), (W, H)].
auto clampedPosition{ cMousePosition };
const auto clampViewport{ gci.GetActiveOutputBuffer().GetViewport().ToOrigin() };
clampViewport.Clamp(clampedPosition);
fWasHandled = gci.GetActiveInputBuffer()->GetTerminalInput().HandleMouse(clampedPosition, uiButton, sModifierKeystate, sWheelDelta, state);
}
return fWasHandled;
}
void HandleKeyEvent(const HWND hWnd,
const UINT Message,
const WPARAM wParam,
const LPARAM lParam,
_Inout_opt_ PBOOL pfUnlockConsole)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// BOGUS for WM_CHAR/WM_DEADCHAR, in which LOWORD(lParam) is a character
auto VirtualKeyCode = LOWORD(wParam);
WORD VirtualScanCode = LOBYTE(HIWORD(lParam));
const auto RepeatCount = LOWORD(lParam);
const auto ControlKeyState = GetControlKeyState(lParam);
const BOOL bKeyDown = WI_IsFlagClear(lParam, KEY_TRANSITION_UP);
if (bKeyDown)
{
// Log a telemetry flag saying the user interacted with the Console
// Only log when the key is a down press. Otherwise we're getting many calls with
// Message = WM_CHAR, VirtualKeyCode = VK_TAB, with bKeyDown = false
// when nothing is happening, or the user has merely clicked on the title bar, and
// this can incorrectly mark the session as being interactive.
Telemetry::Instance().SetUserInteractive();
}
// Make sure we retrieve the key info first, or we could chew up
// unneeded space in the key info table if we bail out early.
if (Message == WM_CHAR || Message == WM_SYSCHAR || Message == WM_DEADCHAR || Message == WM_SYSDEADCHAR)
{
// --- START LOAD BEARING CODE ---
// NOTE: We MUST match up the original data from the WM_KEYDOWN stroke (handled at some inexact moment in the
// past by TranslateMessageEx) with the WM_CHAR we are processing now to ensure we have the correct
// wVirtualScanCode to associate with the message and pass down into the console input queue for further
// processing.
// This is required because we cannot accurately re-synthesize (using MapVirtualKey/Ex)
// the original scan code just based on the information we have now and the scan code might be
// required by the underlying client application, processed input handler (inside the console),
// or other input channels to help portray certain key sequences.
// Most notably this affects Ctrl-C, Ctrl-Break, and Pause/Break among others.
//
RetrieveKeyInfo(hWnd,
&VirtualKeyCode,
&VirtualScanCode,
!gci.pInputBuffer->fInComposition);
// --- END LOAD BEARING CODE ---
}
KeyEvent keyEvent{ !!bKeyDown, RepeatCount, VirtualKeyCode, VirtualScanCode, UNICODE_NULL, 0 };
if (Message == WM_CHAR || Message == WM_SYSCHAR || Message == WM_DEADCHAR || Message == WM_SYSDEADCHAR)
{
// If this is a fake character, zero the scancode.
if (lParam & 0x02000000)
{
keyEvent.SetVirtualScanCode(0);
}
keyEvent.SetActiveModifierKeys(GetControlKeyState(lParam));
if (Message == WM_CHAR || Message == WM_SYSCHAR)
{
keyEvent.SetCharData(static_cast<wchar_t>(wParam));
}
else
{
keyEvent.SetCharData(0);
}
}
else
{
// if alt-gr, ignore
if (lParam & 0x02000000)
{
return;
}
keyEvent.SetActiveModifierKeys(ControlKeyState);
keyEvent.SetCharData(0);
}
const INPUT_KEY_INFO inputKeyInfo(VirtualKeyCode, ControlKeyState);
// Capture telemetry on Ctrl+Shift+ C or V commands
if (IsInProcessedInputMode())
{
// Capture telemetry data when a user presses ctrl+shift+c or v in processed mode
if (inputKeyInfo.IsShiftAndCtrlOnly())
{
if (VirtualKeyCode == 'V')
{
Telemetry::Instance().LogCtrlShiftVProcUsed();
}
else if (VirtualKeyCode == 'C')
{
Telemetry::Instance().LogCtrlShiftCProcUsed();
}
}
}
else
{
// Capture telemetry data when a user presses ctrl+shift+c or v in raw mode
if (inputKeyInfo.IsShiftAndCtrlOnly())
{
if (VirtualKeyCode == 'V')
{
Telemetry::Instance().LogCtrlShiftVRawUsed();
}
else if (VirtualKeyCode == 'C')
{
Telemetry::Instance().LogCtrlShiftCRawUsed();
}
}
}
// If this is a key up message, should we ignore it? We do this so that if a process reads a line from the input
// buffer, the key up event won't get put in the buffer after the read completes.
if (gci.Flags & CONSOLE_IGNORE_NEXT_KEYUP)
{
gci.Flags &= ~CONSOLE_IGNORE_NEXT_KEYUP;
if (!bKeyDown)
{
return;
}
}
auto pSelection = &Selection::Instance();
if (bKeyDown && gci.GetInterceptCopyPaste() && inputKeyInfo.IsShiftAndCtrlOnly())
{
// Intercept C-S-v to paste
switch (VirtualKeyCode)
{
case 'V':
// the user is attempting to paste from the clipboard
Telemetry::Instance().SetKeyboardTextEditingUsed();
Clipboard::Instance().Paste();
return;
}
}
else if (!IsInVirtualTerminalInputMode())
{
// First attempt to process simple key chords (Ctrl+Key)
if (inputKeyInfo.IsCtrlOnly() && ShouldTakeOverKeyboardShortcuts() && bKeyDown)
{
switch (VirtualKeyCode)
{
case 'A':
// Set Text Selection using keyboard to true for telemetry
Telemetry::Instance().SetKeyboardTextSelectionUsed();
// the user is asking to select all
pSelection->SelectAll();
return;
case 'F':
// the user is asking to go to the find window
DoFind();
*pfUnlockConsole = FALSE;
return;
case 'M':
// the user is asking for mark mode
Selection::Instance().InitializeMarkSelection();
return;
case 'V':
// the user is attempting to paste from the clipboard
Telemetry::Instance().SetKeyboardTextEditingUsed();
Clipboard::Instance().Paste();
return;
case VK_HOME:
case VK_END:
case VK_UP:
case VK_DOWN:
// if the user is asking for keyboard scroll, give it to them
if (Scrolling::s_HandleKeyScrollingEvent(&inputKeyInfo))
{
return;
}
break;
case VK_PRIOR:
case VK_NEXT:
Telemetry::Instance().SetCtrlPgUpPgDnUsed();
break;
}
}
// Handle F11 fullscreen toggle
if (VirtualKeyCode == VK_F11 &&
bKeyDown &&
inputKeyInfo.HasNoModifiers() &&
ShouldTakeOverKeyboardShortcuts())
{
ServiceLocator::LocateConsoleWindow<Window>()->ToggleFullscreen();
return;
}
// handle shift-ins paste
if (inputKeyInfo.IsShiftOnly() && ShouldTakeOverKeyboardShortcuts())
{
if (!bKeyDown)
{
return;
}
else if (VirtualKeyCode == VK_INSERT && !(pSelection->IsInSelectingState() && pSelection->IsKeyboardMarkSelection()))
{
Clipboard::Instance().Paste();
return;
}
}
// handle ctrl+shift+plus/minus for transparency adjustment
if (inputKeyInfo.IsShiftAndCtrlOnly() && ShouldTakeOverKeyboardShortcuts())
{
if (!bKeyDown)
{
return;
}
else
{
//This is the only place where the window opacity is changed NOT due to the props sheet.
short opacityDelta = 0;
if (VirtualKeyCode == VK_OEM_PLUS || VirtualKeyCode == VK_ADD)
{
opacityDelta = OPACITY_DELTA_INTERVAL;
}
else if (VirtualKeyCode == VK_OEM_MINUS || VirtualKeyCode == VK_SUBTRACT)
{
opacityDelta = -OPACITY_DELTA_INTERVAL;
}
if (opacityDelta != 0)
{
ServiceLocator::LocateConsoleWindow<Window>()->ChangeWindowOpacity(opacityDelta);
return;
}
}
}
}
// Then attempt to process more complicated selection/scrolling commands that require state.
// These selection and scrolling functions must go after the simple key-chord combinations
// as they have the potential to modify state in a way those functions do not expect.
if (gci.Flags & CONSOLE_SELECTING)
{
if (!bKeyDown)
{
return;
}
auto handlingResult = pSelection->HandleKeySelectionEvent(&inputKeyInfo);
if (handlingResult == Selection::KeySelectionEventResult::CopyToClipboard)
{
// If the ALT key is held, also select HTML as well as plain text.
const auto fAlsoSelectHtml = WI_IsFlagSet(OneCoreSafeGetKeyState(VK_MENU), KEY_PRESSED);
Clipboard::Instance().Copy(fAlsoSelectHtml);
return;
}
else if (handlingResult == Selection::KeySelectionEventResult::EventHandled)
{
return;
}
}
if (Scrolling::s_IsInScrollMode())
{
if (!bKeyDown || Scrolling::s_HandleKeyScrollingEvent(&inputKeyInfo))
{
return;
}
}
// we need to check if there is an active popup because otherwise they won't be able to receive shift+key events
if (pSelection->s_IsValidKeyboardLineSelection(&inputKeyInfo) && IsInProcessedInputMode() && gci.PopupCount.load() == 0)
{
if (!bKeyDown || pSelection->HandleKeyboardLineSelectionEvent(&inputKeyInfo))
{
return;
}
}
// if the user is inputting chars at an inappropriate time, beep.
if ((gci.Flags & (CONSOLE_SELECTING | CONSOLE_SCROLLING | CONSOLE_SCROLLBAR_TRACKING)) &&
bKeyDown &&
!IsSystemKey(VirtualKeyCode))
{
ServiceLocator::LocateConsoleWindow()->SendNotifyBeep();
return;
}
if (gci.pInputBuffer->fInComposition)
{
return;
}
auto generateBreak = false;
// ignore key strokes that will generate CHAR messages. this is only necessary while a dialog box is up.
if (ServiceLocator::LocateGlobals().uiDialogBoxCount != 0)
{
if (Message != WM_CHAR && Message != WM_SYSCHAR && Message != WM_DEADCHAR && Message != WM_SYSDEADCHAR)
{
WCHAR awch[MAX_CHARS_FROM_1_KEYSTROKE];
BYTE KeyState[256];
if (GetKeyboardState(KeyState))
{
auto cwch = ToUnicodeEx((UINT)wParam, HIWORD(lParam), KeyState, awch, ARRAYSIZE(awch), TM_POSTCHARBREAKS, nullptr);
if (cwch != 0)
{
return;
}
}
else
{
return;
}
}
else
{
// remember to generate break
if (Message == WM_CHAR)
{
generateBreak = true;
}
}
}
HandleGenericKeyEvent(keyEvent, generateBreak);
}
// Routine Description:
// - Returns TRUE if DefWindowProc should be called.
BOOL HandleSysKeyEvent(const HWND hWnd, const UINT Message, const WPARAM wParam, const LPARAM lParam, _Inout_opt_ PBOOL pfUnlockConsole)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
WORD VirtualKeyCode;
if (Message == WM_SYSCHAR || Message == WM_SYSDEADCHAR)
{
VirtualKeyCode = (WORD)OneCoreSafeMapVirtualKeyW(LOBYTE(HIWORD(lParam)), MAPVK_VSC_TO_VK_EX);
}
else
{
VirtualKeyCode = LOWORD(wParam);
}
// Log a telemetry flag saying the user interacted with the Console
Telemetry::Instance().SetUserInteractive();
// check for ctrl-esc
const auto bCtrlDown = OneCoreSafeGetKeyState(VK_CONTROL) & KEY_PRESSED;
if (VirtualKeyCode == VK_ESCAPE &&
bCtrlDown && !(OneCoreSafeGetKeyState(VK_MENU) & KEY_PRESSED) && !(OneCoreSafeGetKeyState(VK_SHIFT) & KEY_PRESSED))
{
return TRUE; // call DefWindowProc
}
// check for alt-f4
if (VirtualKeyCode == VK_F4 && (OneCoreSafeGetKeyState(VK_MENU) & KEY_PRESSED) && IsInProcessedInputMode() && gci.IsAltF4CloseAllowed())
{
return TRUE; // let DefWindowProc generate WM_CLOSE
}
if (WI_IsFlagClear(lParam, WM_SYSKEYDOWN_ALT_PRESSED))
{ // we're iconic
// Check for ENTER while iconic (restore accelerator).
if (VirtualKeyCode == VK_RETURN)
{
return TRUE; // call DefWindowProc
}
else
{
HandleKeyEvent(hWnd, Message, wParam, lParam, pfUnlockConsole);
return FALSE;
}
}
if (VirtualKeyCode == VK_RETURN && !bCtrlDown)
{
// only toggle on keydown
if (!(lParam & KEY_TRANSITION_UP))
{
ServiceLocator::LocateConsoleWindow<Window>()->ToggleFullscreen();
}
return FALSE;
}
// make sure alt-space gets translated so that the system menu is displayed.
if (!(OneCoreSafeGetKeyState(VK_CONTROL) & KEY_PRESSED))
{
if (VirtualKeyCode == VK_SPACE)
{
if (IsInVirtualTerminalInputMode())
{
HandleKeyEvent(hWnd, Message, wParam, lParam, pfUnlockConsole);
return FALSE;
}
return TRUE; // call DefWindowProc
}
if (VirtualKeyCode == VK_ESCAPE)
{
return TRUE; // call DefWindowProc
}
if (VirtualKeyCode == VK_TAB)
{
return TRUE; // call DefWindowProc
}
}
HandleKeyEvent(hWnd, Message, wParam, lParam, pfUnlockConsole);
return FALSE;
}
[[nodiscard]] static HRESULT _AdjustFontSize(const SHORT delta) noexcept
{
auto& globals = ServiceLocator::LocateGlobals();
auto& screenInfo = globals.getConsoleInformation().GetActiveOutputBuffer();
// Increase or decrease font by delta through the API to ensure our behavior matches public behavior.
CONSOLE_FONT_INFOEX font = { 0 };
font.cbSize = sizeof(font);
RETURN_IF_FAILED(globals.api->GetCurrentConsoleFontExImpl(screenInfo, false, font));
font.dwFontSize.Y += delta;
RETURN_IF_FAILED(globals.api->SetCurrentConsoleFontExImpl(screenInfo, false, font));
return S_OK;
}
// Routine Description:
// - Returns TRUE if DefWindowProc should be called.
BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
const UINT Message,
const WPARAM wParam,
const LPARAM lParam)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (Message != WM_MOUSEMOVE)
{
// Log a telemetry flag saying the user interacted with the Console
Telemetry::Instance().SetUserInteractive();
}
const auto pSelection = &Selection::Instance();
if (!(gci.Flags & CONSOLE_HAS_FOCUS) && !pSelection->IsMouseButtonDown())
{
return TRUE;
}
if (gci.Flags & CONSOLE_IGNORE_NEXT_MOUSE_INPUT)
{
// only reset on up transition
if (Message != WM_LBUTTONDOWN && Message != WM_MBUTTONDOWN && Message != WM_RBUTTONDOWN)
{
gci.Flags &= ~CONSOLE_IGNORE_NEXT_MOUSE_INPUT;
return FALSE;
}
return TRUE;
}
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx
// Important Do not use the LOWORD or HIWORD macros to extract the x- and y-
// coordinates of the cursor position because these macros return incorrect
// results on systems with multiple monitors. Systems with multiple monitors
// can have negative x- and y- coordinates, and LOWORD and HIWORD treat the
// coordinates as unsigned quantities.
const auto x = GET_X_LPARAM(lParam);
const auto y = GET_Y_LPARAM(lParam);
til::point MousePosition;
// If it's a *WHEEL event, it's in screen coordinates, not window
if (Message == WM_MOUSEWHEEL || Message == WM_MOUSEHWHEEL)
{
POINT coords = { x, y };
ScreenToClient(ServiceLocator::LocateConsoleWindow()->GetWindowHandle(), &coords);
MousePosition = { coords.x, coords.y };
}
else
{
MousePosition = { x, y };
}
// translate mouse position into characters, if necessary.
auto ScreenFontSize = ScreenInfo.GetScreenFontSize();
MousePosition.X /= ScreenFontSize.X;
MousePosition.Y /= ScreenFontSize.Y;
const auto fShiftPressed = WI_IsFlagSet(OneCoreSafeGetKeyState(VK_SHIFT), KEY_PRESSED);
// We need to try and have the virtual terminal handle the mouse's position in viewport coordinates,
// not in screen buffer coordinates. It expects the top left to always be 0,0
// (the TerminalMouseInput object will add (1,1) to convert to VT coords on its own.)
// Mouse events with shift pressed will ignore this and fall through to the default handler.
// This is in line with PuTTY's behavior and vim's own documentation:
// "The xterm handling of the mouse buttons can still be used by keeping the shift key pressed." - `:help 'mouse'`, vim.
// Mouse events while we're selecting or have a selection will also skip this and fall though
// (so that the VT handler doesn't eat any selection region updates)
if (!fShiftPressed && !pSelection->IsInSelectingState())
{
short sDelta = 0;
if (Message == WM_MOUSEWHEEL)
{
sDelta = GET_WHEEL_DELTA_WPARAM(wParam);
}
if (HandleTerminalMouseEvent(MousePosition, Message, LOWORD(GetControlKeyState(0)), sDelta))
{
// Use GetControlKeyState here to get the control state in console event mode.
// This will ensure that we get ALT and SHIFT, the former of which is not available
// through MK_ constants. We only care about the bottom 16 bits.
// GH#6401: Capturing the mouse ensures that we get drag/release events
// even if the user moves outside the window.
// HandleTerminalMouseEvent returns false if the terminal's not in VT mode,
// so capturing/releasing here should not impact other console mouse event
// consumers.
switch (Message)
{
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
SetCapture(ServiceLocator::LocateConsoleWindow()->GetWindowHandle());
break;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
ReleaseCapture();
break;
}
return FALSE;
}
}
MousePosition.X += ScreenInfo.GetViewport().Left();
MousePosition.Y += ScreenInfo.GetViewport().Top();
const auto coordScreenBufferSize = ScreenInfo.GetBufferSize().Dimensions();
// make sure mouse position is clipped to screen buffer
if (MousePosition.X < 0)
{
MousePosition.X = 0;
}
else if (MousePosition.X >= coordScreenBufferSize.X)
{
MousePosition.X = coordScreenBufferSize.X - 1;
}
if (MousePosition.Y < 0)
{
MousePosition.Y = 0;
}
else if (MousePosition.Y >= coordScreenBufferSize.Y)
{
MousePosition.Y = coordScreenBufferSize.Y - 1;
}
// Process the transparency mousewheel message before the others so that we can
// process all the mouse events within the Selection and QuickEdit check
if (Message == WM_MOUSEWHEEL)
{
const short sKeyState = GET_KEYSTATE_WPARAM(wParam);
if (WI_IsFlagSet(sKeyState, MK_CONTROL))
{
const short sDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
// ctrl+shift+scroll adjusts opacity of the window
if (WI_IsFlagSet(sKeyState, MK_SHIFT))
{
ServiceLocator::LocateConsoleWindow<Window>()->ChangeWindowOpacity(OPACITY_DELTA_INTERVAL * sDelta);
}
// ctrl+scroll adjusts the font size
else
{
LOG_IF_FAILED(_AdjustFontSize(sDelta));
}
}
}
if (pSelection->IsInSelectingState() || pSelection->IsInQuickEditMode())
{
if (Message == WM_LBUTTONDOWN)
{
// make sure message matches button state
if (!(OneCoreSafeGetKeyState(VK_LBUTTON) & KEY_PRESSED))
{
return FALSE;
}
if (pSelection->IsInQuickEditMode() && !pSelection->IsInSelectingState())
{
// start a mouse selection
pSelection->InitializeMouseSelection(MousePosition);
pSelection->MouseDown();
// Check for ALT-Mouse Down "use alternate selection"
// If in box mode, use line mode. If in line mode, use box mode.
// TODO: move into initialize?
pSelection->CheckAndSetAlternateSelection();
pSelection->ShowSelection();
}
else
{
auto fExtendSelection = false;
// We now capture the mouse to our Window. We do this so that the
// user can "scroll" the selection endpoint to an off screen
// position by moving the mouse off the client area.
if (pSelection->IsMouseInitiatedSelection())
{
// Check for SHIFT-Mouse Down "continue previous selection" command.
if (fShiftPressed)
{
fExtendSelection = true;
}
}
// if we chose to extend the selection, do that.
if (fExtendSelection)
{
pSelection->MouseDown();
pSelection->ExtendSelection(MousePosition);
}
else
{
// otherwise, set up a new selection from here. note that it's important to ClearSelection(true) here
// because ClearSelection() unblocks console output, causing us to have
// a line of output occur every time the user changes the selection.
pSelection->ClearSelection(true);
pSelection->InitializeMouseSelection(MousePosition);
pSelection->MouseDown();
pSelection->ShowSelection();
}
}
}
else if (Message == WM_LBUTTONUP)
{
if (pSelection->IsInSelectingState() && pSelection->IsMouseInitiatedSelection())
{
pSelection->MouseUp();
}
}
else if (Message == WM_LBUTTONDBLCLK)
{
// on double-click, attempt to select a "word" beneath the cursor
const auto selectionAnchor = pSelection->GetSelectionAnchor();
if (MousePosition == selectionAnchor)
{
try
{
const auto wordBounds = ScreenInfo.GetWordBoundary(MousePosition);
MousePosition = wordBounds.second;
// update both ends of the selection since we may have adjusted the anchor in some circumstances.
pSelection->AdjustSelection(wordBounds.first, wordBounds.second);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
}
}
pSelection->MouseDown();
}
else if ((Message == WM_RBUTTONDOWN) || (Message == WM_RBUTTONDBLCLK))
{
if (!pSelection->IsMouseButtonDown())
{
if (pSelection->IsInSelectingState())
{
// Capture data on when quick edit copy is used in proc or raw mode
if (IsInProcessedInputMode())
{
Telemetry::Instance().LogQuickEditCopyProcUsed();
}
else
{
Telemetry::Instance().LogQuickEditCopyRawUsed();
}
// If the ALT key is held, also select HTML as well as plain text.
const auto fAlsoCopyFormatting = WI_IsFlagSet(OneCoreSafeGetKeyState(VK_MENU), KEY_PRESSED);
Clipboard::Instance().Copy(fAlsoCopyFormatting);
}
else if (gci.Flags & CONSOLE_QUICK_EDIT_MODE)
{
// Capture data on when quick edit paste is used in proc or raw mode
if (IsInProcessedInputMode())
{
Telemetry::Instance().LogQuickEditPasteProcUsed();
}
else
{
Telemetry::Instance().LogQuickEditPasteRawUsed();
}
Clipboard::Instance().Paste();
}
gci.Flags |= CONSOLE_IGNORE_NEXT_MOUSE_INPUT;
}
}
else if (Message == WM_MBUTTONDOWN)
{
ServiceLocator::LocateConsoleControl<Microsoft::Console::Interactivity::Win32::ConsoleControl>()
->EnterReaderModeHelper(ServiceLocator::LocateConsoleWindow()->GetWindowHandle());
}
else if (Message == WM_MOUSEMOVE)
{
if (pSelection->IsMouseButtonDown() && pSelection->ShouldAllowMouseDragSelection(MousePosition))
{
pSelection->ExtendSelection(MousePosition);
}
}
else if (Message == WM_MOUSEWHEEL || Message == WM_MOUSEHWHEEL)
{
return TRUE;
}
// We're done processing the messages for selection. We need to return
return FALSE;
}
if (WI_IsFlagClear(gci.pInputBuffer->InputMode, ENABLE_MOUSE_INPUT))
{
ReleaseCapture();
return TRUE;
}
ULONG ButtonFlags;
ULONG EventFlags;
switch (Message)
{
case WM_LBUTTONDOWN:
SetCapture(ServiceLocator::LocateConsoleWindow()->GetWindowHandle());
ButtonFlags = FROM_LEFT_1ST_BUTTON_PRESSED;
EventFlags = 0;
break;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
ReleaseCapture();
ButtonFlags = EventFlags = 0;
break;
case WM_RBUTTONDOWN:
SetCapture(ServiceLocator::LocateConsoleWindow()->GetWindowHandle());
ButtonFlags = RIGHTMOST_BUTTON_PRESSED;
EventFlags = 0;
break;
case WM_MBUTTONDOWN:
SetCapture(ServiceLocator::LocateConsoleWindow()->GetWindowHandle());
ButtonFlags = FROM_LEFT_2ND_BUTTON_PRESSED;
EventFlags = 0;
break;
case WM_MOUSEMOVE:
ButtonFlags = 0;
EventFlags = MOUSE_MOVED;
break;
case WM_LBUTTONDBLCLK:
ButtonFlags = FROM_LEFT_1ST_BUTTON_PRESSED;
EventFlags = DOUBLE_CLICK;
break;
case WM_RBUTTONDBLCLK:
ButtonFlags = RIGHTMOST_BUTTON_PRESSED;
EventFlags = DOUBLE_CLICK;
break;
case WM_MBUTTONDBLCLK:
ButtonFlags = FROM_LEFT_2ND_BUTTON_PRESSED;
EventFlags = DOUBLE_CLICK;
break;
case WM_MOUSEWHEEL:
ButtonFlags = ((UINT)wParam & 0xFFFF0000);
EventFlags = MOUSE_WHEELED;
break;
case WM_MOUSEHWHEEL:
ButtonFlags = ((UINT)wParam & 0xFFFF0000);
EventFlags = MOUSE_HWHEELED;
break;
default:
RIPMSG1(RIP_ERROR, "Invalid message 0x%x", Message);
ButtonFlags = 0;
EventFlags = 0;
break;
}
ULONG EventsWritten = 0;
try
{
auto mouseEvent = std::make_unique<MouseEvent>(
MousePosition,
ConvertMouseButtonState(ButtonFlags, static_cast<UINT>(wParam)),
GetControlKeyState(0),
EventFlags);
EventsWritten = static_cast<ULONG>(gci.pInputBuffer->Write(std::move(mouseEvent)));
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
EventsWritten = 0;
}
if (EventsWritten != 1)
{
RIPMSG1(RIP_WARNING, "PutInputInBuffer: EventsWritten != 1 (0x%x), 1 expected", EventsWritten);
}
return FALSE;
}
// ----------------------------
// Window Initialization
// ----------------------------
// Routine Description:
// - This routine gets called to filter input to console dialogs so that we can do the special processing that StoreKeyInfo does.
LRESULT CALLBACK DialogHookProc(int nCode, WPARAM /*wParam*/, LPARAM lParam)
{
auto msg = *((PMSG)lParam);
if (nCode == MSGF_DIALOGBOX)
{
if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
{
if (msg.message != WM_CHAR && msg.message != WM_DEADCHAR && msg.message != WM_SYSCHAR && msg.message != WM_SYSDEADCHAR)
{
// don't store key info if dialog box input
if (GetWindowLongPtrW(msg.hwnd, GWLP_HWNDPARENT) == 0)
{
StoreKeyInfo(&msg);
}
}
}
}
return 0;
}
// Routine Description:
// - This routine gets called by the console input thread to set up the console window.
NTSTATUS InitWindowsSubsystem(_Out_ HHOOK* phhook)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto ProcessData = gci.ProcessHandleList.FindProcessInList(ConsoleProcessList::ROOT_PROCESS_ID);
FAIL_FAST_IF(!(ProcessData != nullptr && ProcessData->fRootProcess));
// Create and activate the main window
auto Status = Window::CreateInstance(&gci, gci.ScreenBuffers);
if (!NT_SUCCESS(Status))
{
RIPMSG2(RIP_WARNING, "CreateWindowsWindow failed with status 0x%x, gle = 0x%x", Status, GetLastError());
return Status;