Compare commits

...

17 Commits

Author SHA1 Message Date
Dustin Howett
5dac5aa280 Fix the botched cherry-pick in the previous commit
(cherry picked from commit eff3e93d79)
2020-08-10 19:05:20 -07:00
Dustin L. Howett
c235ab38fb Resolve the default profile during defaults load, don't crash on launch (#7237)
The "default profile as name" feature in 1.1 broke the loading of
default settings, as we would never get to the validation phase where
the default profile string was transformed into a guid.

I moved knowledge of the "unparsed default profile" optional to the
consumer so that we could make sure we only attempted to deserialize it
once (and only if it was present.)

Fixes #7236.

* [x] Closes #7236

(cherry picked from commit c03677b0c9)
(cherry picked from commit e354313dc4)
2020-08-10 17:42:14 -07:00
Carlos Zamora
61234f6585 Pass mouse button state into HandleMouse instead of asking win32 (#6765)
MouseInput was directly asking user32 about the state of the mouse buttons,
which was somewhat of a layering violation. This commit makes all callers
have to pass the mouse state in themselves.

Closes #4869

(cherry picked from commit 20a288020e)
2020-08-10 17:42:14 -07:00
Moshe Schorr
9d75011f9c Batch RTL runs to ensure proper draw order (#7190)
Consecutive RTL GlyphRuns are drawn from the last to the first.

References
#538, #7149, all those issues asking for RTL closed as dupes.

As @miniksa suggested in a comment on #7149 -- handle the thingy on the
render side.

If we have GlyphRuns abcdEFGh, where EFG are RTL, we draw them now in
order abcdGFEh.

This has ransom-noting, because I didn't touch the font scaling at all.
This should fix the majority of RTL issues, except it *doesn't* fix
issues with colors, because those get split in the TextBuffer phase in
the renderer I think, so they show up separately by the GlyphRun phase.

(cherry picked from commit 60b44c856e)
2020-08-10 17:42:14 -07:00
Dustin L. Howett
9ecd8f574f Disable MinimalCoreWin when OpenConsoleUniversalApp is false (#7203)
This fixes the build the rest of the way in VS 16.7. Something about the
way we were using the Store/Container flags caused some of our projects
to end up linking kernel32.lib only (MinimalCoreWin==KernelOnly).
The best way to solve it once and for all is to make sure MinimalCoreWin
is always set.

References 313568d0e5.

(cherry picked from commit 858905f492)
2020-08-10 17:42:13 -07:00
Leonard Hecker
5d7455795c Fix #7064: Ignore key events without scan code (#7145)
Up until #4999 we deferred all key events to the character event handler
for which `ToUnicodeEx` returned a valid character and alternatively
those who aren't a special key combination as listed in
`TerminalInput`'s implementation.

Since #4999 we started acknowledging/handling all key events no matter
whether they're actually a known key combination. Given non-ASCII inputs
the Win32 `SendInput()` method generates certain sequences that aren't
recognizable combinations though and if they're handled by the key event
handler no follow up character event is sent containing the unicode
character.

This PR adds another condition and defers all key events without scan
code (i.e. those not representable by the current keyboard layout) to
the character event handler.

I'm absolutely not certain that this PR doesn't have a negative effect
on other kinds of inputs.

Is it common for key events to not contain a scan code? I personally
haven't seen it happen before AutoHotKey/SendInput.

Before this PR is merged it'd be nice to have a good testing plan in
place in order to ensure nothing breaks.

## Validation Steps Performed

Remapped `AltGr+8` to `»` using AutoHotKey using `<^>!8::SendInput {Raw}»`.
Ensured `»` is printed if `AltGr+8` is pressed.

Closes #7064
Closes #7120

(cherry picked from commit b617c434a1)
2020-08-10 17:42:13 -07:00
Dustin L. Howett
4fe9165788 wpf: fixup mouse wheel events from screen coords (#7168)
I found this while crawling through conhost's WindowIo. Mouse wheel
events come in in screen coordinates, unlike literally every other mouse
event.

The WPF control was doing it wrong.

(cherry picked from commit cd7235661e)
2020-08-04 11:21:21 -07:00
Dustin L. Howett
f4b97fe37d Fix VT mouse capture issues in Terminal and conhost (#7166)
This pull request fixes capture and event generation in VT mouse mode
for both conhost and terminal.

Fixes #6401.

[1/3] Terminal: clamp mouse events to the viewport, don't throw them away

 gnome-terminal (at least) sends mouse events whose x/y are at the
 extreme ends of the buffer when a drag starts inside the terminal and
 then exits it.

 We would previously discard any mouse events that exited the borders of
 the viewport. Now we will keep emitting events where X/Y=0/w/h.

[2/3] conhost: clamp VT mouse to viewport, capture pointer

 This is the same as (1), but for conhost. conhost wasn't already
 capturing the pointer when VT mouse mode was in use. By capturing, we
 ensure that events that happen outside the screen still result in events
 sent to an application (like a release after a drag)

[3/3] wpf: capture the pointer when VT mouse is enabled

 This is the same as (2), but for the WPF control. Clamping is handled
 in TerminalCore in (1), so we didn't need to do it in WPF.

(cherry picked from commit d29be591a8)
2020-08-04 11:21:16 -07:00
Dustin Howett
fc83a4cbeb Research how many characters users are typing before dismissing the cmdpal (#7165)
Add some user research to determine what the average number of characters a user types before executing a cmdpal action.

This might need to be modified when it merges with #6732

(cherry picked from commit 46f777226+7bf9225c1)
2020-08-04 11:20:09 -07:00
PankajBhojwani
39a1ce0539 Implement split pane with new tab button (#7117)
Allows splitting pane (with default settings) by holding down ALT and pressing the new tab button ('+')

## PR Checklist
* [X] Closes #6757
* [X] Works here.
* [X] Manual test (below)
* [X] Is core contributor.

## More detailed description

Pretty much exactly the code added in #5928 (all credit to @carlos-zamora), but put at the new tab button event binding

## Validation steps

Seems to work - holding ALT while pressing '+' opens a pane instead of a tab. Holding ALT while starting up terminal for the first time does not seem to affect the behaviour.

(cherry picked from commit 8b669b5484)
2020-08-04 11:18:53 -07:00
Dustin L. Howett
139414723b Send ENHANCED_KEY in Win32 input mode in the wpf/uwp controls (#7106)
When we added support for win32 input mode, we neglected to pass
`ENHANCED_KEY` through the two surfaces that would generate events. This
broke arrow keys in much the same way was #2397, but in a different
layer.

While I was working on the WPF control, I took a moment to refactor the
message cracking out into a helper. It's a lot easier on the eyes than
four lines of bit shifting repeated three times.

Fixes #7074

(cherry picked from commit dd0f7b701a)
2020-07-31 10:17:25 -07:00
PankajBhojwani
288436a2e9 Implement SetCursorColor in Terminal (#7123)
This was never hooked up to the TerminalCore implementation.

Closes #7102

(cherry picked from commit 2f5ba9471d)
2020-07-31 10:17:25 -07:00
PankajBhojwani
6084670f7b Fixed window title not updating with tab rename (#7119)
* Fixed window title not updating with tab rename

* pass the correct title to event handler instead

(cherry picked from commit bf90869f30)
2020-07-31 10:17:25 -07:00
Javier
ac99c3065c Expose text selection through terminal WPF control API (#7121)
We've been trying to improve the copy/paste experience with the terminal
in Visual Studio. Once of our problematic scenarios is when the terminal
is connected to a remote environment and the user attempts to
copy/paste. This gets forwarded to the remote shell that copy/paste into
the remote clipboard instead of the local one.

So we opted to add ctrl+shift+c/v to the terminal and need access to the
selected text via the terminal control

VALIDATION
Tested with Visual Studio integrated terminal

(cherry picked from commit f486a6504c)
2020-07-31 10:17:25 -07:00
Dustin L. Howett
4d00019a9b wpf: fix a handful of issues with the wpf control (#6983)
* send alt/F10 through the control
  We were not listening for WM_SYSKEY{UP,DOWN}
* extract the actual scancode during WM_CHAR, not the bitfield
  We were accidentally sending some of the additional keypress data in with
  the character event in Win32 Input Mode
* set default fg/bg to campbell
  The WPF control starts up in PowerShell blue even though it's not typically used
  in PowerShell blue.
* don't rely on the font to determine wideness
  This is a cross-port of #2928 to the WPF control
* deterministic shutdown
  In testing, I saw a handful of crashes on teardown because we were not shutting
  down the render thread properly.
* don't pass 10 for the font weight ...
  When Cascadia Code is set, it just looks silly.
* trigger render when selection is cleared, do it under lock

Fixes #6966.

(cherry picked from commit 76de2aedc2)
2020-07-20 16:14:00 -07:00
Carlos Zamora
d48ecd24f4 UIA: use full buffer comparison in rects and endpoint setter (#6447)
In UiaTextRange, `_getBufferSize` returns an optimized version of the
size of the buffer to be the origin and the last character in the
buffer. This is to improve performance on search or checking if you are
currently on the last word/line.

When setting the endpoint and drawing the bounding rectangles, we should
be retrieving the true buffer size. This is because it is still possible
to create UiaTextRanges that are outside of this optimized size. The
main source of this is `ExpandToEnclosingUnit()` when the unit is
`Document`. The end _should_ be the last visible character, but it isn't
because that would break our tests.

This is an incomplete solution. #6986 is a follow up to completely test
and implement the solution.

The crash in #6402 was caused by getting the document range (a range of
the full text buffer),  then moving the end by one character. When we
get the document range, we get the optimized size of the buffer (the
position of the last character). Moving by one character is valid
because the buffer still has more to explore. We then crash from
checking if the new position is valid on the **optimized size**, not the
**real size**.

REFERENCES

#6986 - follow up to properly handle/test this "end of buffer" problem

Closes #6402

(cherry picked from commit c390b61648)
2020-07-20 16:14:00 -07:00
Dustin L. Howett
ccf201fe44 Swap brightBlack/black in the Solarized color schemes (#6985)
Original notes from @M-Pixel:

> Console applications assume that backgrounds are black, and that
> `lightBlack`/`DarkGrey` are lighter than `black`/`Black`.  This
> assumption is accounted for by all color schemes in `defaults.json`,
> except for the Solarized themes.
>
> The Solarized Dark theme, in particular, makes `-Parameters` invisible
> against the background in PowerShell, which is obviously an unacceptable
> usability flaw.
>
> This change makes `black` and `background` to the same (which is common
> throughout the color schemes), and makes `brightBlack` (`DarkGray` in
> .NET) lighter than black (which is obviously more correct given the
> meanings of those words).

Out of the box, we ship a pretty bad behavior.

If I look at all of the existing shipped color schemes--and that
includes things like Tango and One Half--we are universally following a
`background` == `black` rule.

If I consult gnome-terminal or xterm, they do the same thing; Xterm by
default, gnome-terminal for solarized. The background generally matches
color index `0` across all their **dark** schemes. Konsole and
lxterminal disagree and map background to `0 intense` for Solarized.

I want to put our Solarized schemes on a deprecation path, but
unfortunately we still need to ship _something_ for users who we're
going to strand on them.

I'm going to have to swallow my bitter and say that yes, we should
probably just change the index mapping and go with something that works
right out of the box while we figure out how to do perceptual color
nudging and eventually remove bad defaults (like Solarized).

From #6618.

Fixes #4047.
Closes #6618.

(cherry picked from commit 04f5ee7ebf)
2020-07-20 13:51:55 -07:00
34 changed files with 573 additions and 202 deletions

View File

@@ -0,0 +1,10 @@
abcd
dst
EFG
EFGh
EMPTYBOX
GFEh
nrcs
Remoting
Scs
Shobjidl

View File

@@ -99,7 +99,6 @@ AStomps
ASYNCWINDOWPOS
atch
ATest
atg
attr
ATTRCOLOR
aumid
@@ -160,7 +159,6 @@ bitsavers
bitset
BKCOLOR
BKGND
BKMK
Bksp
blog
Blt
@@ -295,7 +293,6 @@ codepage
codepoint
codeproject
COINIT
colo
colorizing
colororacle
colorref
@@ -352,7 +349,6 @@ conpty
conptylib
consecteturadipiscingelit
conserv
consoleaccessibility
consoleapi
CONSOLECONTROL
CONSOLEENDTASK
@@ -640,7 +636,6 @@ DROPDOWNLIST
DROPFILES
drv
dsm
Dst
DSwap
DTest
dtor
@@ -683,7 +678,6 @@ Elems
elif
elseif
emacs
emptybox
enabledelayedexpansion
endian
endif
@@ -752,7 +746,6 @@ fdw
fea
fesb
FFDE
FFF
FFrom
FGCOLOR
fgetc
@@ -769,7 +762,6 @@ FILETYPE
FILEW
FILLATTR
FILLCONSOLEOUTPUT
Filledbox
FILTERONPASTE
finalizer
FINDCASE
@@ -821,7 +813,6 @@ fsproj
fstream
fte
Ftm
fullcolor
fullscreen
fullwidth
func
@@ -1011,7 +1002,6 @@ hwheel
hwnd
HWNDPARENT
hxx
hyperlink
IAccessibility
IAction
IApi
@@ -1034,7 +1024,6 @@ ICore
IData
IDCANCEL
IDD
IDefault
IDesktop
IDictionary
IDISHWND
@@ -1384,7 +1373,6 @@ mincore
mindbogglingly
mingw
minkernel
minmax
minwin
minwindef
Mip
@@ -1545,7 +1533,6 @@ NOYIELD
NOZORDER
NPM
npos
NRCS
NSTATUS
ntapi
ntcon
@@ -1722,7 +1709,6 @@ pguid
pgup
PHANDLE
phhook
phsl
phwnd
pid
pidl
@@ -1752,7 +1738,6 @@ POBJECT
Podcast
POINTSLIST
POLYTEXTW
popclip
popd
POPF
poppack
@@ -1935,7 +1920,6 @@ REGSTR
reingest
Relayout
RELBINPATH
remoting
renderengine
rendersize
reparent
@@ -1943,7 +1927,6 @@ reparenting
replatformed
Replymessage
repositorypath
rerendered
rescap
Resequence
Reserialize
@@ -1989,7 +1972,6 @@ RMENU
roadmap
robomac
roundtrip
ROWSTOSCROLL
rparen
RRF
RRRGGGBB
@@ -2051,7 +2033,6 @@ SCROLLSCALE
SCROLLSCREENBUFFER
Scrollup
Scrolluppage
SCS
scursor
sddl
sdeleted
@@ -2121,7 +2102,6 @@ Shl
shlguid
shlobj
shlwapi
shobjidl
SHORTPATH
SHOWCURSOR
SHOWMAXIMIZED
@@ -2194,14 +2174,11 @@ STARTWPARMSW
Statusline
stdafx
STDAPI
stdarg
stdcall
stddef
stderr
stdexcept
stdin
stdio
stdlib
STDMETHODCALLTYPE
STDMETHODIMP
stdout
@@ -2295,7 +2272,6 @@ telnetd
telnetpp
templated
terminalcore
terminalnuget
TERMINALSCROLLING
terminfo
TEs
@@ -2351,7 +2327,6 @@ TMult
tmultiple
tmux
todo
Tofill
tofrom
tokenhelpers
tokenized
@@ -2447,7 +2422,6 @@ undef
Unescape
unexpand
Unfocus
unfocuses
unhighlighting
unhosted
unicode
@@ -2753,33 +2727,26 @@ WWith
wx
wxh
xa
xab
xact
xamarin
xaml
Xamlmeta
xargs
xaz
xb
xbc
xbf
xbutton
XBUTTONDBLCLK
XBUTTONDOWN
XBUTTONUP
xca
XCast
xce
XCENTER
XColors
xcopy
XCount
xdd
xdy
xe
XEncoding
xff
xffff
XFile
xlang
XManifest

View File

@@ -16,12 +16,35 @@ using namespace ::Microsoft::Terminal::Core;
static LPCWSTR term_window_class = L"HwndTerminalClass";
// 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) };
static constexpr bool _IsMouseMessage(UINT uMsg)
{
return uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP || uMsg == WM_LBUTTONDBLCLK ||
uMsg == WM_MBUTTONDOWN || uMsg == WM_MBUTTONUP || uMsg == WM_MBUTTONDBLCLK ||
uMsg == WM_RBUTTONDOWN || uMsg == WM_RBUTTONUP || uMsg == WM_RBUTTONDBLCLK ||
uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL;
uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL;
}
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
// See microsoft/terminal#2066 for more info.
static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) noexcept
{
return false; // glyph is not wide.
}
static bool _EnsureStaticInitialization()
{
// use C++11 magic statics to make sure we only do this once.
static bool initialized = []() {
// *** THIS IS A SINGLETON ***
SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback);
return true;
}();
return initialized;
}
LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc(
@@ -36,10 +59,29 @@ try
if (terminal)
{
if (_IsMouseMessage(uMsg) && terminal->_CanSendVTMouseInput())
if (_IsMouseMessage(uMsg))
{
if (terminal->_SendMouseEvent(uMsg, wParam, lParam))
if (terminal->_CanSendVTMouseInput() && terminal->_SendMouseEvent(uMsg, wParam, lParam))
{
// GH#6401: Capturing the mouse ensures that we get drag/release events
// even if the user moves outside the window.
// _SendMouseEvent returns false if the terminal's not in VT mode, so we'll
// fall through to release the capture.
switch (uMsg)
{
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
SetCapture(hwnd);
break;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
ReleaseCapture();
break;
}
// Suppress all mouse events that made it into the terminal.
return 0;
}
}
@@ -57,6 +99,10 @@ try
return 0;
case WM_LBUTTONUP:
terminal->_singleClickTouchdownPos = std::nullopt;
[[fallthrough]];
case WM_MBUTTONUP:
case WM_RBUTTONUP:
ReleaseCapture();
break;
case WM_MOUSEMOVE:
if (WI_IsFlagSet(wParam, MK_LBUTTON))
@@ -72,7 +118,7 @@ try
{
const auto bufferData = terminal->_terminal->RetrieveSelectedTextFromBuffer(false);
LOG_IF_FAILED(terminal->_CopyTextToSystemClipboard(bufferData, true));
terminal->_terminal->ClearSelection();
TerminalClearSelection(terminal);
}
CATCH_LOG();
}
@@ -81,6 +127,11 @@ try
terminal->_PasteTextFromClipboard();
}
return 0;
case WM_DESTROY:
// Release Terminal's hwnd so Teardown doesn't try to destroy it again
terminal->_hwnd.release();
terminal->Teardown();
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
@@ -114,14 +165,16 @@ static bool RegisterTermClass(HINSTANCE hInstance) noexcept
}
HwndTerminal::HwndTerminal(HWND parentHwnd) :
_desiredFont{ L"Consolas", 0, 10, { 0, 14 }, CP_UTF8 },
_actualFont{ L"Consolas", 0, 10, { 0, 14 }, CP_UTF8, false },
_desiredFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8 },
_actualFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8, false },
_uiaProvider{ nullptr },
_uiaProviderInitialized{ false },
_currentDpi{ USER_DEFAULT_SCREEN_DPI },
_pfnWriteCallback{ nullptr },
_multiClickTime{ 500 } // this will be overwritten by the windows system double-click time
{
_EnsureStaticInitialization();
HINSTANCE hInstance = wil::GetModuleInstanceHandle();
if (RegisterTermClass(hInstance))
@@ -148,6 +201,11 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
}
}
HwndTerminal::~HwndTerminal()
{
Teardown();
}
HRESULT HwndTerminal::Initialize()
{
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
@@ -162,9 +220,6 @@ HRESULT HwndTerminal::Initialize()
RETURN_IF_FAILED(dxEngine->Enable());
_renderer->AddRenderEngine(dxEngine.get());
const auto pfn = std::bind(&::Microsoft::Console::Render::Renderer::IsGlyphWideByFont, _renderer.get(), std::placeholders::_1);
SetGlyphWidthFallback(pfn);
_UpdateFont(USER_DEFAULT_SCREEN_DPI);
RECT windowRect;
GetWindowRect(_hwnd.get(), &windowRect);
@@ -181,8 +236,8 @@ HRESULT HwndTerminal::Initialize()
_terminal->SetBackgroundCallback([](auto) {});
_terminal->Create(COORD{ 80, 25 }, 1000, *_renderer);
_terminal->SetDefaultBackground(RGB(5, 27, 80));
_terminal->SetDefaultForeground(RGB(255, 255, 255));
_terminal->SetDefaultBackground(RGB(12, 12, 12));
_terminal->SetDefaultForeground(RGB(204, 204, 204));
_terminal->SetWriteInputCallback([=](std::wstring & input) noexcept { _WriteTextToConnection(input); });
localPointerToThread->EnablePainting();
@@ -191,6 +246,33 @@ HRESULT HwndTerminal::Initialize()
return S_OK;
}
void HwndTerminal::Teardown() noexcept
try
{
// As a rule, detach resources from the Terminal before shutting them down.
// This ensures that teardown is reentrant.
// Shut down the renderer (and therefore the thread) before we implode
if (auto localRenderEngine{ std::exchange(_renderEngine, nullptr) })
{
if (auto localRenderer{ std::exchange(_renderer, nullptr) })
{
localRenderer->TriggerTeardown();
// renderer is destroyed
}
// renderEngine is destroyed
}
if (auto localHwnd{ _hwnd.release() })
{
// If we're being called through WM_DESTROY, we won't get here (hwnd is already released)
// If we're not, we may end up in Teardown _again_... but by the time we do, all other
// resources have been released and will not be released again.
DestroyWindow(localHwnd);
}
}
CATCH_LOG();
void HwndTerminal::RegisterScrollCallback(std::function<void(int, int, int)> callback)
{
_terminal->SetScrollPositionChangedCallback(callback);
@@ -467,11 +549,21 @@ try
}
CATCH_RETURN();
void HwndTerminal::_ClearSelection() noexcept
try
{
auto lock{ _terminal->LockForWriting() };
_terminal->ClearSelection();
_renderer->TriggerSelection();
}
CATCH_LOG();
void _stdcall TerminalClearSelection(void* terminal)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
publicTerminal->_terminal->ClearSelection();
auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_ClearSelection();
}
bool _stdcall TerminalIsSelectionActive(void* terminal)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
@@ -482,9 +574,10 @@ bool _stdcall TerminalIsSelectionActive(void* terminal)
// Returns the selected text in the terminal.
const wchar_t* _stdcall TerminalGetSelection(void* terminal)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
auto publicTerminal = static_cast<HwndTerminal*>(terminal);
const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false);
publicTerminal->_ClearSelection();
// convert text: vector<string> --> string
std::wstring selectedText;
@@ -494,8 +587,6 @@ const wchar_t* _stdcall TerminalGetSelection(void* terminal)
}
auto returnText = wil::make_cotaskmem_string_nothrow(selectedText.c_str());
TerminalClearSelection(terminal);
return returnText.release();
}
@@ -541,19 +632,30 @@ bool HwndTerminal::_CanSendVTMouseInput() const noexcept
bool HwndTerminal::_SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept
try
{
const til::point cursorPosition{
til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
};
const til::size fontSize{ this->_actualFont.GetSize() };
short wheelDelta{ 0 };
if (uMsg == WM_MOUSEWHEEL)
if (uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL)
{
wheelDelta = HIWORD(wParam);
// If it's a *WHEEL event, it's in screen coordinates, not window (?!)
POINT coordsToTransform = cursorPosition;
ScreenToClient(_hwnd.get(), &coordsToTransform);
cursorPosition = coordsToTransform;
}
return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta);
const TerminalInput::MouseButtonState state{
WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed),
WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed),
WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed)
};
return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta, state);
}
catch (...)
{
@@ -561,20 +663,24 @@ catch (...)
return false;
}
void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, bool keyDown) noexcept
void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown) noexcept
try
{
const auto flags = getControlKeyState();
_terminal->SendKeyEvent(vkey, scanCode, flags, keyDown);
auto modifiers = getControlKeyState();
if (WI_IsFlagSet(flags, ENHANCED_KEY))
{
modifiers |= ControlKeyStates::EnhancedKey;
}
_terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown);
}
CATCH_LOG();
void HwndTerminal::_SendCharEvent(wchar_t ch, WORD scanCode) noexcept
void HwndTerminal::_SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept
try
{
if (_terminal->IsSelectionActive())
{
_terminal->ClearSelection();
_ClearSelection();
if (ch == UNICODE_ESC)
{
// ESC should clear any selection before it triggers input.
@@ -589,21 +695,25 @@ try
return;
}
const auto flags = getControlKeyState();
_terminal->SendCharEvent(ch, scanCode, flags);
auto modifiers = getControlKeyState();
if (WI_IsFlagSet(flags, ENHANCED_KEY))
{
modifiers |= ControlKeyStates::EnhancedKey;
}
_terminal->SendCharEvent(ch, scanCode, modifiers);
}
CATCH_LOG();
void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown)
void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_SendKeyEvent(vkey, scanCode, keyDown);
publicTerminal->_SendKeyEvent(vkey, scanCode, flags, keyDown);
}
void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode)
void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode, WORD flags)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_SendCharEvent(ch, scanCode);
publicTerminal->_SendCharEvent(ch, scanCode, flags);
}
void _stdcall DestroyTerminal(void* terminal)
@@ -632,7 +742,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
publicTerminal->_terminal->SetCursorStyle(theme.CursorStyle);
publicTerminal->_desiredFont = { fontFamily, 0, 10, { 0, fontSize }, CP_UTF8 };
publicTerminal->_desiredFont = { fontFamily, 0, DEFAULT_FONT_WEIGHT, { 0, fontSize }, CP_UTF8 };
publicTerminal->_UpdateFont(newDpi);
// When the font changes the terminal dimensions need to be recalculated since the available row and column

View File

@@ -34,8 +34,8 @@ __declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal);
__declspec(dllexport) void _stdcall DestroyTerminal(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
__declspec(dllexport) void _stdcall TerminalRegisterWriteCallback(void* terminal, const void __stdcall callback(wchar_t*));
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown);
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown);
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD flags, WORD scanCode);
__declspec(dllexport) void _stdcall TerminalBlinkCursor(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
__declspec(dllexport) void _stdcall TerminalSetFocus(void* terminal);
@@ -51,9 +51,10 @@ public:
HwndTerminal(HwndTerminal&&) = default;
HwndTerminal& operator=(const HwndTerminal&) = default;
HwndTerminal& operator=(HwndTerminal&&) = default;
~HwndTerminal() = default;
~HwndTerminal();
HRESULT Initialize();
void Teardown() noexcept;
void SendOutput(std::wstring_view data);
HRESULT Refresh(const SIZE windowSize, _Out_ COORD* dimensions);
void RegisterScrollCallback(std::function<void(int, int, int)> callback);
@@ -92,8 +93,8 @@ private:
friend void _stdcall TerminalClearSelection(void* terminal);
friend const wchar_t* _stdcall TerminalGetSelection(void* terminal);
friend bool _stdcall TerminalIsSelectionActive(void* terminal);
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown);
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown);
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode, WORD flags);
friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
friend void _stdcall TerminalBlinkCursor(void* terminal);
friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
@@ -112,11 +113,13 @@ private:
HRESULT _MoveSelection(LPARAM lParam) noexcept;
IRawElementProviderSimple* _GetUiaProvider() noexcept;
void _ClearSelection() noexcept;
bool _CanSendVTMouseInput() const noexcept;
bool _SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept;
void _SendKeyEvent(WORD vkey, WORD scanCode, bool keyDown) noexcept;
void _SendCharEvent(wchar_t ch, WORD scanCode) noexcept;
void _SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown) noexcept;
void _SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept;
// Inherited via IControlAccessibilityInfo
COORD GetFontSize() const override;

View File

@@ -226,9 +226,12 @@ void CascadiaSettings::_ValidateProfilesHaveGuid()
void CascadiaSettings::_ResolveDefaultProfile()
{
const auto unparsedDefaultProfile{ GlobalSettings().UnparsedDefaultProfile() };
auto maybeParsedDefaultProfile{ _GetProfileGuidByName(unparsedDefaultProfile) };
auto defaultProfileGuid{ Utils::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) };
GlobalSettings().DefaultProfile(defaultProfileGuid);
if (unparsedDefaultProfile)
{
auto maybeParsedDefaultProfile{ _GetProfileGuidByName(*unparsedDefaultProfile) };
auto defaultProfileGuid{ Utils::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) };
GlobalSettings().DefaultProfile(defaultProfileGuid);
}
}
// Method Description:

View File

@@ -61,7 +61,7 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadAll()
// GH 3588, we need this below to know if the user chose something that wasn't our default.
// Collect it up here in case it gets modified by any of the other layers between now and when
// the user's preferences are loaded and layered.
const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().UnparsedDefaultProfile();
const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().DefaultProfile();
std::optional<std::string> fileData = _ReadUserSettings();
const bool foundFile = fileData.has_value();
@@ -141,12 +141,11 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadAll()
// is a lot of computation we can skip if no one cares.
if (TraceLoggingProviderEnabled(g_hTerminalAppProvider, 0, MICROSOFT_KEYWORD_MEASURES))
{
const auto hardcodedDefaultGuidAsGuid = Utils::GuidFromString(hardcodedDefaultGuid);
const auto guid = resultPtr->GlobalSettings().DefaultProfile();
// Compare to the defaults.json one that we set on install.
// If it's different, log what the user chose.
if (hardcodedDefaultGuidAsGuid != guid)
if (hardcodedDefaultGuid != guid)
{
TraceLoggingWrite(
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
@@ -229,6 +228,7 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadDefaults()
// them from a file (and the potential that could fail)
resultPtr->_ParseJsonString(DefaultJson, true);
resultPtr->LayerJson(resultPtr->_defaultSettings);
resultPtr->_ResolveDefaultProfile();
return resultPtr;
}

View File

@@ -187,14 +187,16 @@ namespace winrt::TerminalApp::implementation
{
const auto actionAndArgs = command.Action();
_dispatch.DoAction(actionAndArgs);
_close();
TraceLoggingWrite(
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
"CommandPaletteDispatchedAction",
TraceLoggingDescription("Event emitted when the user selects an action in the Command Palette"),
TraceLoggingUInt32(_searchBox().Text().size(), "SearchTextLength", "Number of characters in the search string"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
_close();
}
}

View File

@@ -105,9 +105,9 @@ GUID GlobalAppSettings::DefaultProfile() const
return _defaultProfile;
}
std::wstring GlobalAppSettings::UnparsedDefaultProfile() const
std::optional<std::wstring> GlobalAppSettings::UnparsedDefaultProfile() const
{
return _unparsedDefaultProfile.value();
return _unparsedDefaultProfile;
}
AppKeyBindings GlobalAppSettings::GetKeybindings() const noexcept

View File

@@ -61,7 +61,7 @@ public:
// by higher layers in the app.
void DefaultProfile(const GUID defaultProfile) noexcept;
GUID DefaultProfile() const;
std::wstring UnparsedDefaultProfile() const;
std::optional<std::wstring> UnparsedDefaultProfile() const;
GETSET_PROPERTY(int32_t, InitialRows); // default value set in constructor
GETSET_PROPERTY(int32_t, InitialCols); // default value set in constructor

View File

@@ -166,7 +166,28 @@ namespace winrt::TerminalApp::implementation
_newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
page->_OpenNewTab(nullptr);
// if alt is pressed, open a pane
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
// Check for DebugTap
bool debugTap = page->_settings->GlobalSettings().DebugFeaturesEnabled() &&
WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (altPressed && !debugTap)
{
page->_SplitPane(TerminalApp::SplitState::Automatic,
TerminalApp::SplitType::Manual,
nullptr);
}
else
{
page->_OpenNewTab(nullptr);
}
}
});
_tabView.SelectionChanged({ this, &TerminalPage::_OnTabSelectionChanged });
@@ -1778,7 +1799,7 @@ namespace winrt::TerminalApp::implementation
tab->SetFocused(true);
// Raise an event that our title changed
_titleChangeHandlers(*this, Title());
_titleChangeHandlers(*this, tab->GetActiveTitle());
// Raise an event that our titlebar color changed
std::optional<Windows::UI::Color> color = tab->GetTabColor();

View File

@@ -187,7 +187,7 @@
"foreground": "#839496",
"background": "#002B36",
"cursorColor": "#FFFFFF",
"black": "#073642",
"black": "#002B36",
"red": "#DC322F",
"green": "#859900",
"yellow": "#B58900",
@@ -195,7 +195,7 @@
"purple": "#D33682",
"cyan": "#2AA198",
"white": "#EEE8D5",
"brightBlack": "#002B36",
"brightBlack": "#073642",
"brightRed": "#CB4B16",
"brightGreen": "#586E75",
"brightYellow": "#657B83",
@@ -209,7 +209,7 @@
"foreground": "#657B83",
"background": "#FDF6E3",
"cursorColor": "#002B36",
"black": "#073642",
"black": "#002B36",
"red": "#DC322F",
"green": "#859900",
"yellow": "#B58900",
@@ -217,7 +217,7 @@
"purple": "#D33682",
"cyan": "#2AA198",
"white": "#EEE8D5",
"brightBlack": "#002B36",
"brightBlack": "#073642",
"brightRed": "#CB4B16",
"brightGreen": "#586E75",
"brightYellow": "#657B83",

View File

@@ -11,6 +11,6 @@ namespace Microsoft.Terminal.TerminalControl
[uuid("65b8b8c5-988f-43ff-aba9-e89368da1598")]
interface IMouseWheelListener
{
Boolean OnMouseWheel(Windows.Foundation.Point coord, Int32 delta);
Boolean OnMouseWheel(Windows.Foundation.Point coord, Int32 delta, Boolean leftButtonDown, Boolean midButtonDown, Boolean rightButtonDown);
}
}

View File

@@ -16,6 +16,7 @@
#include "TermControlAutomationPeer.h"
using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Console::VirtualTerminal;
using namespace ::Microsoft::Terminal::Core;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Input;
@@ -737,7 +738,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto ch = e.Character();
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
const auto modifiers = _GetPressedModifierKeys();
auto modifiers = _GetPressedModifierKeys();
if (e.KeyStatus().IsExtendedKey)
{
modifiers |= ControlKeyStates::EnhancedKey;
}
const bool handled = _terminal->SendCharEvent(ch, scanCode, modifiers);
e.Handled(handled);
}
@@ -826,9 +831,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return;
}
const auto modifiers = _GetPressedModifierKeys();
auto modifiers = _GetPressedModifierKeys();
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
if (e.KeyStatus().IsExtendedKey)
{
modifiers |= ControlKeyStates::EnhancedKey;
}
// Alt-Numpad# input will send us a character once the user releases
// Alt, so we should be ignoring the individual keydowns. The character
@@ -1001,7 +1010,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
const auto modifiers = _GetPressedModifierKeys();
return _terminal->SendMouseEvent(terminalPosition, uiButton, modifiers, sWheelDelta);
const TerminalInput::MouseButtonState state{ props.IsLeftButtonPressed(), props.IsMiddleButtonPressed(), props.IsRightButtonPressed() };
return _terminal->SendMouseEvent(terminalPosition, uiButton, modifiers, sWheelDelta, state);
}
// Method Description:
@@ -1325,10 +1335,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
const auto point = args.GetCurrentPoint(*this);
const auto props = point.Properties();
const TerminalInput::MouseButtonState state{ props.IsLeftButtonPressed(), props.IsMiddleButtonPressed(), props.IsRightButtonPressed() };
auto result = _DoMouseWheel(point.Position(),
ControlKeyStates{ args.KeyModifiers() },
point.Properties().MouseWheelDelta(),
point.Properties().IsLeftButtonPressed());
state);
if (result)
{
args.Handled(true);
@@ -1351,7 +1363,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
bool TermControl::_DoMouseWheel(const Windows::Foundation::Point point,
const ControlKeyStates modifiers,
const int32_t delta,
const bool isLeftButtonPressed)
const TerminalInput::MouseButtonState state)
{
if (_CanSendVTMouseInput())
{
@@ -1363,7 +1375,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return _terminal->SendMouseEvent(_GetTerminalPosition(point),
WM_MOUSEWHEEL,
_GetPressedModifierKeys(),
::base::saturated_cast<short>(delta));
::base::saturated_cast<short>(delta),
state);
}
const auto ctrlPressed = modifiers.IsCtrlPressed();
@@ -1379,7 +1392,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
else
{
_MouseScrollHandler(delta, point, isLeftButtonPressed);
_MouseScrollHandler(delta, point, state.isLeftButtonDown);
}
return false;
}
@@ -1393,11 +1406,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - location: the location of the mouse during this event. This location is
// relative to the origin of the control
// - delta: the mouse wheel delta that triggered this event.
// - state: the state for each of the mouse buttons individually (pressed/unpressed)
bool TermControl::OnMouseWheel(const Windows::Foundation::Point location,
const int32_t delta)
const int32_t delta,
const bool leftButtonDown,
const bool midButtonDown,
const bool rightButtonDown)
{
const auto modifiers = _GetPressedModifierKeys();
return _DoMouseWheel(location, modifiers, delta, false);
TerminalInput::MouseButtonState state{ leftButtonDown, midButtonDown, rightButtonDown };
return _DoMouseWheel(location, modifiers, delta, state);
}
// Method Description:

View File

@@ -17,6 +17,11 @@
#include "SearchBoxControl.h"
#include "ThrottledFunc.h"
namespace Microsoft::Console::VirtualTerminal
{
struct MouseButtonState;
}
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
struct CopyToClipboardEventArgs :
@@ -89,7 +94,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
bool OnDirectKeyEvent(const uint32_t vkey, const bool down);
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta);
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
~TermControl();
@@ -221,7 +226,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _MouseScrollHandler(const double mouseDelta, const Windows::Foundation::Point point, const bool isLeftButtonPressed);
void _MouseZoomHandler(const double delta);
void _MouseTransparencyHandler(const double delta);
bool _DoMouseWheel(const Windows::Foundation::Point point, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta, const bool isLeftButtonPressed);
bool _DoMouseWheel(const Windows::Foundation::Point point, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta, const ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state);
bool _CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
bool _ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);

View File

@@ -39,6 +39,7 @@ namespace Microsoft::Terminal::Core
virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD color) noexcept = 0;
virtual bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) noexcept = 0;
virtual bool SetCursorColor(const DWORD color) noexcept = 0;
virtual bool SetDefaultForeground(const DWORD color) noexcept = 0;
virtual bool SetDefaultBackground(const DWORD color) noexcept = 0;

View File

@@ -17,7 +17,7 @@ namespace Microsoft::Terminal::Core
ITerminalInput& operator=(ITerminalInput&&) = default;
virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states, const bool keyDown) = 0;
virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) = 0;
virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) = 0;
virtual bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0;
// void SendMouseEvent(uint row, uint col, KeyModifiers modifiers);

View File

@@ -430,6 +430,19 @@ bool Terminal::SendKeyEvent(const WORD vkey,
_StoreKeyEvent(vkey, scanCode);
// As a Terminal we're mostly interested in getting key events from physical hardware (mouse & keyboard).
// We're thus ignoring events whose values are outside the valid range and unlikely to be generated by the current keyboard.
// It's very likely that a proper followup character event will be sent to us.
// This prominently happens using AutoHotKey's keyboard remapping feature,
// which sends input events whose vkey is 0xff and scanCode is 0.
// We need to check for this early, as _CharacterFromKeyEvent() always returns 0 for such invalid values,
// making us believe that this is an actual non-character input, while it usually isn't.
// GH#7064
if (vkey == 0 || vkey >= 0xff || scanCode == 0)
{
return false;
}
const auto isAltOnlyPressed = states.IsAltPressed() && !states.IsCtrlPressed();
const auto isSuppressedAltGrAlias = !_altGrAliasing && states.IsAltPressed() && states.IsCtrlPressed();
@@ -484,16 +497,16 @@ bool Terminal::SendKeyEvent(const WORD vkey,
// Return Value:
// - true if we translated the key event, and it should not be processed any further.
// - false if we did not translate the key, and it should be processed into a character.
bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta)
bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const TerminalInput::MouseButtonState state)
{
// viewportPos must be within the dimensions of the viewport
const auto viewportDimensions = _mutableViewport.Dimensions();
if (viewportPos.X < 0 || viewportPos.X >= viewportDimensions.X || viewportPos.Y < 0 || viewportPos.Y >= viewportDimensions.Y)
{
return false;
}
return _terminalInput->HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta);
// 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)].
#pragma warning(suppress : 26496) // analysis can't tell we're assigning through a reference below
auto clampedPos{ viewportPos };
_mutableViewport.ToOrigin().Clamp(clampedPos);
return _terminalInput->HandleMouse(clampedPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta, state);
}
// Method Description:

View File

@@ -93,6 +93,7 @@ public:
bool SetWindowTitle(std::wstring_view title) noexcept override;
bool SetColorTableEntry(const size_t tableIndex, const COLORREF color) noexcept override;
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) noexcept override;
bool SetCursorColor(const COLORREF color) noexcept override;
bool SetDefaultForeground(const COLORREF color) noexcept override;
bool SetDefaultBackground(const COLORREF color) noexcept override;
@@ -115,7 +116,7 @@ public:
#pragma region ITerminalInput
// These methods are defined in Terminal.cpp
bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override;
bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) override;
bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) override;
bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override;
[[nodiscard]] HRESULT UserResize(const COORD viewportSize) noexcept override;

View File

@@ -64,6 +64,14 @@ COORD Terminal::GetCursorPosition() noexcept
return newPos;
}
bool Terminal::SetCursorColor(const COLORREF color) noexcept
try
{
_buffer->GetCursor().SetColor(color);
return true;
}
CATCH_LOG_RETURN_FALSE()
// Method Description:
// - Moves the cursor down one line, and possibly also to the leftmost column.
// Arguments:

View File

@@ -146,6 +146,13 @@ try
}
CATCH_LOG_RETURN_FALSE()
bool TerminalDispatch::SetCursorColor(const DWORD color) noexcept
try
{
return _terminalApi.SetCursorColor(color);
}
CATCH_LOG_RETURN_FALSE()
bool TerminalDispatch::SetClipboard(std::wstring_view content) noexcept
try
{

View File

@@ -35,6 +35,7 @@ public:
bool SetColorTableEntry(const size_t tableIndex, const DWORD color) noexcept override;
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) noexcept override;
bool SetCursorColor(const DWORD color) noexcept override;
bool SetClipboard(std::wstring_view content) noexcept override;

View File

@@ -30,6 +30,7 @@ namespace TerminalCoreUnitTests
TEST_METHOD(AltShiftKey);
TEST_METHOD(AltSpace);
TEST_METHOD(InvalidKeyEvent);
void _VerifyExpectedInput(std::wstring& actualInput)
{
@@ -65,4 +66,14 @@ namespace TerminalCoreUnitTests
VERIFY_IS_FALSE(term.SendKeyEvent(L' ', 0, ControlKeyStates::LeftAltPressed, false));
VERIFY_IS_FALSE(term.SendCharEvent(L' ', 0, ControlKeyStates::LeftAltPressed));
}
void InputTest::InvalidKeyEvent()
{
// Certain applications like AutoHotKey and its keyboard remapping feature,
// send us key events using SendInput() whose values are outside of the valid range.
// We don't want to handle those events as we're probably going to get a proper followup character event.
VERIFY_IS_FALSE(term.SendKeyEvent(0, 123, {}, true));
VERIFY_IS_FALSE(term.SendKeyEvent(255, 123, {}, true));
VERIFY_IS_FALSE(term.SendKeyEvent(123, 0, {}, true));
}
}

View File

@@ -17,6 +17,10 @@ using namespace winrt::Windows::Foundation::Numerics;
using namespace ::Microsoft::Console;
using namespace ::Microsoft::Console::Types;
// 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) };
AppHost::AppHost() noexcept :
_app{},
_logic{ nullptr }, // don't make one, we're going to take a ref on app's
@@ -405,7 +409,11 @@ void AppHost::_WindowMouseWheeled(const til::point coord, const int32_t delta)
const til::point offsetPoint = coord - controlOrigin;
if (control.OnMouseWheel(offsetPoint, delta))
const auto lButtonDown = WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed);
const auto mButtonDown = WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed);
const auto rButtonDown = WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed);
if (control.OnMouseWheel(offsetPoint, delta, lButtonDown, mButtonDown, rButtonDown))
{
// If the element handled the mouse wheel event, don't
// continue to iterate over the remaining controls.

View File

@@ -62,6 +62,16 @@ namespace Microsoft.Terminal.Wpf
/// </summary>
WM_CHAR = 0x0102,
/// <summary>
/// The WM_SYSKEYDOWN message is posted to the window with the keyboard focus when a system key is pressed. A system key is F10 or Alt+Something.
/// </summary>
WM_SYSKEYDOWN = 0x0104,
/// <summary>
/// The WM_SYSKEYDOWN message is posted to the window with the keyboard focus when a system key is released. A system key is F10 or Alt+Something.
/// </summary>
WM_SYSKEYUP = 0x0105,
/// <summary>
/// The WM_MOUSEMOVE message is posted to a window when the cursor moves. If the mouse is not captured, the message is posted to the window that contains the cursor. Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
@@ -215,10 +225,10 @@ namespace Microsoft.Terminal.Wpf
public static extern void DestroyTerminal(IntPtr terminal);
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern void TerminalSendKeyEvent(IntPtr terminal, ushort vkey, ushort scanCode, bool keyDown);
public static extern void TerminalSendKeyEvent(IntPtr terminal, ushort vkey, ushort scanCode, ushort flags, bool keyDown);
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern void TerminalSendCharEvent(IntPtr terminal, char ch, ushort scanCode);
public static extern void TerminalSendCharEvent(IntPtr terminal, char ch, ushort scanCode, ushort flags);
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern void TerminalSetTheme(IntPtr terminal, [MarshalAs(UnmanagedType.Struct)] TerminalTheme theme, string fontFamily, short fontSize, int newDpi);

View File

@@ -20,6 +20,20 @@ namespace Microsoft.Terminal.Wpf
/// </remarks>
public class TerminalContainer : HwndHost
{
private static void UnpackKeyMessage(IntPtr wParam, IntPtr lParam, out ushort vkey, out ushort scanCode, out ushort flags)
{
ulong scanCodeAndFlags = (((ulong)lParam) & 0xFFFF0000) >> 16;
scanCode = (ushort)(scanCodeAndFlags & 0x00FFu);
flags = (ushort)(scanCodeAndFlags & 0xFF00u);
vkey = (ushort)wParam;
}
private static void UnpackCharMessage(IntPtr wParam, IntPtr lParam, out char character, out ushort scanCode, out ushort flags)
{
UnpackKeyMessage(wParam, lParam, out ushort vKey, out scanCode, out flags);
character = (char)vKey;
}
private ITerminalConnection connection;
private IntPtr hwnd;
private IntPtr terminal;
@@ -124,6 +138,20 @@ namespace Microsoft.Terminal.Wpf
this.TriggerResize(this.RenderSize);
}
/// <summary>
/// Gets the selected text from the terminal renderer and clears the selection.
/// </summary>
/// <returns>The selected text, empty if no text is selected.</returns>
internal string GetSelectedText()
{
if (NativeMethods.TerminalIsSelectionActive(this.terminal))
{
return NativeMethods.TerminalGetSelection(this.terminal);
}
return string.Empty;
}
/// <summary>
/// Triggers a refresh of the terminal with the given size.
/// </summary>
@@ -235,29 +263,35 @@ namespace Microsoft.Terminal.Wpf
this.Focus();
NativeMethods.SetFocus(this.hwnd);
break;
case NativeMethods.WindowMessage.WM_SYSKEYDOWN: // fallthrough
case NativeMethods.WindowMessage.WM_KEYDOWN:
{
// WM_KEYDOWN lParam layout documentation: https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown
NativeMethods.TerminalSetCursorVisible(this.terminal, true);
ulong scanCode = (((ulong)lParam) & 0x00FF0000) >> 16;
NativeMethods.TerminalSendKeyEvent(this.terminal, (ushort)wParam, (ushort)scanCode, true);
UnpackKeyMessage(wParam, lParam, out ushort vkey, out ushort scanCode, out ushort flags);
NativeMethods.TerminalSendKeyEvent(this.terminal, vkey, scanCode, flags, true);
this.blinkTimer?.Start();
break;
}
case NativeMethods.WindowMessage.WM_SYSKEYUP: // fallthrough
case NativeMethods.WindowMessage.WM_KEYUP:
{
// WM_KEYUP lParam layout documentation: https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keyup
ulong scanCode = (((ulong)lParam) & 0x00FF0000) >> 16;
NativeMethods.TerminalSendKeyEvent(this.terminal, (ushort)wParam, (ushort)scanCode, false);
UnpackKeyMessage(wParam, lParam, out ushort vkey, out ushort scanCode, out ushort flags);
NativeMethods.TerminalSendKeyEvent(this.terminal, (ushort)wParam, scanCode, flags, false);
break;
}
case NativeMethods.WindowMessage.WM_CHAR:
// WM_CHAR lParam layout documentation: https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-char
NativeMethods.TerminalSendCharEvent(this.terminal, (char)wParam, (ushort)((uint)lParam >> 16));
break;
{
// WM_CHAR lParam layout documentation: https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-char
UnpackCharMessage(wParam, lParam, out char character, out ushort scanCode, out ushort flags);
NativeMethods.TerminalSendCharEvent(this.terminal, character, scanCode, flags);
break;
}
case NativeMethods.WindowMessage.WM_WINDOWPOSCHANGED:
var windowpos = (NativeMethods.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(NativeMethods.WINDOWPOS));
if (((NativeMethods.SetWindowPosFlags)windowpos.flags).HasFlag(NativeMethods.SetWindowPosFlags.SWP_NOSIZE))

View File

@@ -70,6 +70,15 @@ namespace Microsoft.Terminal.Wpf
this.termContainer.SetTheme(theme, fontFamily, fontSize);
}
/// <summary>
/// Gets the selected text in the terminal, clearing the selection. Otherwise returns an empty string.
/// </summary>
/// <returns>Selected text, empty string if no content is selected.</returns>
public string GetSelectedText()
{
return this.termContainer.GetSelectedText();
}
/// <summary>
/// Resizes the terminal to the specified rows and columns.
/// </summary>

View File

@@ -22,12 +22,20 @@
at the winmd we build to generate type info.
In general, cppwinrt projects all want this.
-->
<PropertyGroup Condition="'$(OpenConsoleUniversalApp)'!='false'">
<PropertyGroup Condition="'$(OpenConsoleUniversalApp)'=='true'">
<MinimalCoreWin>true</MinimalCoreWin>
<AppContainerApplication>true</AppContainerApplication>
<WindowsStoreApp>true</WindowsStoreApp>
<ApplicationType>Windows Store</ApplicationType>
</PropertyGroup>
<PropertyGroup Condition="'$(OpenConsoleUniversalApp)'!='true'">
<!-- Some of our projects include the cppwinrt build options to
just build cppwinrt things, but don't want the store bits
in full swing. MinimalCoreWin != false means "don't let me
use win32 APIs"
-->
<MinimalCoreWin>false</MinimalCoreWin>
</PropertyGroup>
<!-- This is magic that tells msbuild to link against the Desktop platform
instead of the App platform. This you definitely want, because we're not

View File

@@ -23,12 +23,17 @@
#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
// ----------------------------
@@ -123,7 +128,20 @@ bool HandleTerminalMouseEvent(const COORD cMousePosition,
// Virtual terminal input mode
if (IsInVirtualTerminalInputMode())
{
fWasHandled = gci.GetActiveInputBuffer()->GetTerminalInput().HandleMouse(cMousePosition, uiButton, sModifierKeystate, sWheelDelta);
const TerminalInput::MouseButtonState state{
WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed),
WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed),
WI_IsFlagSet(GetKeyState(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;
@@ -635,6 +653,25 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
if (HandleTerminalMouseEvent(MousePosition, Message, GET_KEYSTATE_WPARAM(wParam), sDelta))
{
// 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;
}
}

View File

@@ -847,57 +847,113 @@ CATCH_RETURN();
auto mutableOrigin = origin;
// Draw each run separately.
for (UINT32 runIndex = 0; runIndex < _runs.size(); ++runIndex)
for (INT32 runIndex = 0; runIndex < gsl::narrow<INT32>(_runs.size()); ++runIndex)
{
// Get the run
const Run& run = _runs.at(runIndex);
// Prepare the glyph run and description objects by converting our
// internal storage representation into something that matches DWrite's structures.
DWRITE_GLYPH_RUN glyphRun;
glyphRun.bidiLevel = run.bidiLevel;
glyphRun.fontEmSize = _format->GetFontSize() * run.fontScale;
glyphRun.fontFace = run.fontFace.Get();
glyphRun.glyphAdvances = &_glyphAdvances.at(run.glyphStart);
glyphRun.glyphCount = run.glyphCount;
glyphRun.glyphIndices = &_glyphIndices.at(run.glyphStart);
glyphRun.glyphOffsets = &_glyphOffsets.at(run.glyphStart);
glyphRun.isSideways = false;
DWRITE_GLYPH_RUN_DESCRIPTION glyphRunDescription;
glyphRunDescription.clusterMap = _glyphClusters.data();
glyphRunDescription.localeName = _localeName.data();
glyphRunDescription.string = _text.data();
glyphRunDescription.stringLength = run.textLength;
glyphRunDescription.textPosition = run.textStart;
// Calculate the origin for the next run based on the amount of space
// that would be consumed. We are doing this calculation now, not after,
// because if the text is RTL then we need to advance immediately, before the
// write call since DirectX expects the origin to the RIGHT of the text for RTL.
const auto postOriginX = std::accumulate(_glyphAdvances.begin() + run.glyphStart,
_glyphAdvances.begin() + run.glyphStart + run.glyphCount,
mutableOrigin.x);
// Check for RTL, if it is, apply space adjustment.
if (WI_IsFlagSet(glyphRun.bidiLevel, 1))
if (!WI_IsFlagSet(run.bidiLevel, 1))
{
mutableOrigin.x = postOriginX;
RETURN_IF_FAILED(_DrawGlyphRun(clientDrawingContext, renderer, mutableOrigin, run));
}
// This is the RTL behavior. We will advance to the last contiguous RTL run, draw that,
// and then keep on going backwards from there, and then move runIndex beyond.
// Let's say we have runs abcdEFGh, where runs EFG are RTL.
// Then we will draw them in the order abcdGFEh
else
{
const INT32 originalRunIndex = runIndex;
INT32 lastIndexRTL = runIndex;
// Try to draw it
RETURN_IF_FAILED(renderer->DrawGlyphRun(clientDrawingContext,
mutableOrigin.x,
mutableOrigin.y,
DWRITE_MEASURING_MODE_NATURAL,
&glyphRun,
&glyphRunDescription,
run.drawingEffect.Get()));
// Step 1: Get to the last contiguous RTL run from here
while (lastIndexRTL < gsl::narrow<INT32>(_runs.size()) - 1) // only could ever advance if there's something left
{
const Run& nextRun = _runs.at(gsl::narrow_cast<size_t>(lastIndexRTL + 1));
if (WI_IsFlagSet(nextRun.bidiLevel, 1))
{
lastIndexRTL++;
}
else
{
break;
}
}
// Either way, we should be at this point by the end of writing this sequence,
// whether it was LTR or RTL.
// Go from the last to the first and draw
for (runIndex = lastIndexRTL; runIndex >= originalRunIndex; runIndex--)
{
const Run& currentRun = _runs.at(runIndex);
RETURN_IF_FAILED(_DrawGlyphRun(clientDrawingContext, renderer, mutableOrigin, currentRun));
}
runIndex = lastIndexRTL; // and the for loop will take the increment to the last one
}
}
}
CATCH_RETURN();
return S_OK;
}
// Routine Description:
// - Draw the given run
// - The origin is updated to be after the run.
// Arguments:
// - clientDrawingContext - Optional pointer to information that the renderer might need
// while attempting to graphically place the text onto the screen
// - renderer - The interface to be used for actually putting text onto the screen
// - origin - pixel point of top left corner on final surface for drawing
// - run - the run to be drawn
[[nodiscard]] HRESULT CustomTextLayout::_DrawGlyphRun(_In_opt_ void* clientDrawingContext,
gsl::not_null<IDWriteTextRenderer*> renderer,
D2D_POINT_2F& mutableOrigin,
const Run& run) noexcept
{
try
{
// Prepare the glyph run and description objects by converting our
// internal storage representation into something that matches DWrite's structures.
DWRITE_GLYPH_RUN glyphRun;
glyphRun.bidiLevel = run.bidiLevel;
glyphRun.fontEmSize = _format->GetFontSize() * run.fontScale;
glyphRun.fontFace = run.fontFace.Get();
glyphRun.glyphAdvances = &_glyphAdvances.at(run.glyphStart);
glyphRun.glyphCount = run.glyphCount;
glyphRun.glyphIndices = &_glyphIndices.at(run.glyphStart);
glyphRun.glyphOffsets = &_glyphOffsets.at(run.glyphStart);
glyphRun.isSideways = false;
DWRITE_GLYPH_RUN_DESCRIPTION glyphRunDescription;
glyphRunDescription.clusterMap = _glyphClusters.data();
glyphRunDescription.localeName = _localeName.data();
glyphRunDescription.string = _text.data();
glyphRunDescription.stringLength = run.textLength;
glyphRunDescription.textPosition = run.textStart;
// Calculate the origin for the next run based on the amount of space
// that would be consumed. We are doing this calculation now, not after,
// because if the text is RTL then we need to advance immediately, before the
// write call since DirectX expects the origin to the RIGHT of the text for RTL.
const auto postOriginX = std::accumulate(_glyphAdvances.begin() + run.glyphStart,
_glyphAdvances.begin() + run.glyphStart + run.glyphCount,
mutableOrigin.x);
// Check for RTL, if it is, apply space adjustment.
if (WI_IsFlagSet(glyphRun.bidiLevel, 1))
{
mutableOrigin.x = postOriginX;
}
// Try to draw it
RETURN_IF_FAILED(renderer->DrawGlyphRun(clientDrawingContext,
mutableOrigin.x,
mutableOrigin.y,
DWRITE_MEASURING_MODE_NATURAL,
&glyphRun,
&glyphRunDescription,
run.drawingEffect.Get()));
// Either way, we should be at this point by the end of writing this sequence,
// whether it was LTR or RTL.
mutableOrigin.x = postOriginX;
}
CATCH_RETURN();
return S_OK;

View File

@@ -147,6 +147,10 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT _DrawGlyphRuns(_In_opt_ void* clientDrawingContext,
IDWriteTextRenderer* renderer,
const D2D_POINT_2F origin) noexcept;
[[nodiscard]] HRESULT _DrawGlyphRun(_In_opt_ void* clientDrawingContext,
gsl::not_null<IDWriteTextRenderer*> renderer,
D2D_POINT_2F& mutableOrigin,
const Run& run) noexcept;
[[nodiscard]] static constexpr UINT32 _EstimateGlyphCount(const UINT32 textLength) noexcept;

View File

@@ -292,7 +292,7 @@ public:
bool fExpectedKeyHandled = false;
s_pwszInputExpected = L"\x0";
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta));
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta, {}));
mouseInput->EnableDefaultTracking(true);
@@ -309,7 +309,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
@@ -327,7 +328,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
@@ -345,7 +347,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
}
@@ -371,7 +374,7 @@ public:
bool fExpectedKeyHandled = false;
s_pwszInputExpected = L"\x0";
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta));
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta, {}));
mouseInput->SetUtf8ExtendedMode(true);
@@ -391,7 +394,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
@@ -409,7 +413,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
@@ -427,7 +432,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
}
@@ -453,7 +459,7 @@ public:
bool fExpectedKeyHandled = false;
s_pwszInputExpected = L"\x0";
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta));
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta, {}));
mouseInput->SetSGRExtendedMode(true);
@@ -471,7 +477,7 @@ public:
// validate translation
VERIFY_ARE_EQUAL(fExpectedKeyHandled,
mouseInput->HandleMouse(Coord, uiButton, sModifierKeystate, sScrollDelta),
mouseInput->HandleMouse(Coord, uiButton, sModifierKeystate, sScrollDelta, {}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
@@ -488,7 +494,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
@@ -506,7 +513,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
}
@@ -532,7 +540,7 @@ public:
bool fExpectedKeyHandled = false;
s_pwszInputExpected = L"\x0";
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta));
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta, {}));
// Default Tracking, Default Encoding
mouseInput->EnableDefaultTracking(true);
@@ -550,7 +558,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
@@ -570,7 +579,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
@@ -589,7 +599,8 @@ public:
mouseInput->HandleMouse(Coord,
uiButton,
sModifierKeystate,
sScrollDelta),
sScrollDelta,
{}),
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
}
@@ -606,31 +617,31 @@ public:
Log::Comment(L"Test mouse wheel scrolling up");
s_pwszInputExpected = L"\x1B[A";
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA));
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling down");
s_pwszInputExpected = L"\x1B[B";
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA));
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Enable cursor keys mode");
mouseInput->ChangeCursorKeysMode(true);
Log::Comment(L"Test mouse wheel scrolling up");
s_pwszInputExpected = L"\x1BOA";
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA));
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling down");
s_pwszInputExpected = L"\x1BOB";
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA));
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Confirm no effect when scroll mode is disabled");
mouseInput->UseAlternateScreenBuffer();
mouseInput->EnableAlternateScroll(false);
VERIFY_IS_FALSE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA));
VERIFY_IS_FALSE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA, {}));
Log::Comment(L"Confirm no effect when using the main buffer");
mouseInput->UseMainScreenBuffer();
mouseInput->EnableAlternateScroll(true);
VERIFY_IS_FALSE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA));
VERIFY_IS_FALSE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA, {}));
}
};

View File

@@ -13,10 +13,6 @@ using namespace Microsoft::Console::VirtualTerminal;
#endif
static const int s_MaxDefaultCoordinate = 94;
// 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) };
// Alternate scroll sequences
static constexpr std::wstring_view CursorUpSequence{ L"\x1b[A" };
static constexpr std::wstring_view CursorDownSequence{ L"\x1b[B" };
@@ -105,22 +101,22 @@ static constexpr bool _isButtonDown(const unsigned int button) noexcept
// - Retrieves which mouse button is currently pressed. This is needed because
// MOUSEMOVE events do not also tell us if any mouse buttons are pressed during the move.
// Parameters:
// <none>
// - state - the current state of which mouse buttons are pressed
// Return value:
// - a button corresponding to any pressed mouse buttons, else WM_LBUTTONUP if none are pressed.
unsigned int TerminalInput::s_GetPressedButton() noexcept
constexpr unsigned int TerminalInput::s_GetPressedButton(const MouseButtonState state) noexcept
{
// TODO GH#4869: Have the caller pass the mouse button state into HandleMouse
unsigned int button = WM_LBUTTONUP; // Will be treated as a release, or no button pressed.
if (WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed))
// Will be treated as a release, or no button pressed.
unsigned int button = WM_LBUTTONUP;
if (state.isLeftButtonDown)
{
button = WM_LBUTTONDOWN;
}
else if (WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed))
else if (state.isMiddleButtonDown)
{
button = WM_MBUTTONDOWN;
}
else if (WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed))
else if (state.isRightButtonDown)
{
button = WM_RBUTTONDOWN;
}
@@ -298,12 +294,14 @@ bool TerminalInput::IsTrackingMouseInput() const noexcept
// - button - the message to decode.
// - modifierKeyState - the modifier keys pressed with this button
// - delta - the amount that the scroll wheel changed (should be 0 unless button is a WM_MOUSE*WHEEL)
// - state - the state of the mouse buttons at this moment
// Return value:
// - true if the event was handled and we should stop event propagation to the default window handler.
bool TerminalInput::HandleMouse(const COORD position,
const unsigned int button,
const short modifierKeyState,
const short delta)
const short delta,
const MouseButtonState state)
{
if (Utils::Sign(delta) != Utils::Sign(_mouseInputState.accumulatedDelta))
{
@@ -354,7 +352,7 @@ bool TerminalInput::HandleMouse(const COORD position,
// _GetPressedButton will return the first pressed mouse button.
// If it returns WM_LBUTTONUP, then we can assume that the mouse
// moved without a button being pressed.
const unsigned int realButton = isHover ? s_GetPressedButton() : button;
const unsigned int realButton = isHover ? s_GetPressedButton(state) : button;
// In default mode, only button presses/releases are sent
// In ButtonEvent mode, changing coord hovers WITH A BUTTON PRESSED

View File

@@ -43,10 +43,19 @@ namespace Microsoft::Console::VirtualTerminal
#pragma region MouseInput
// These methods are defined in mouseInput.cpp
struct MouseButtonState
{
bool isLeftButtonDown;
bool isMiddleButtonDown;
bool isRightButtonDown;
};
bool HandleMouse(const COORD position,
const unsigned int button,
const short modifierKeyState,
const short delta);
const short delta,
const MouseButtonState state);
bool IsTrackingMouseInput() const noexcept;
#pragma endregion
@@ -136,7 +145,7 @@ namespace Microsoft::Console::VirtualTerminal
bool _ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept;
bool _SendAlternateScroll(const short delta) const noexcept;
static unsigned int s_GetPressedButton() noexcept;
static constexpr unsigned int s_GetPressedButton(const MouseButtonState state) noexcept;
#pragma endregion
};
}

View File

@@ -145,7 +145,9 @@ const COORD UiaTextRangeBase::GetEndpoint(TextPatternRangeEndpoint endpoint) con
// - true if range is degenerate, false otherwise.
bool UiaTextRangeBase::SetEndpoint(TextPatternRangeEndpoint endpoint, const COORD val) noexcept
{
const auto bufferSize = _getBufferSize();
// GH#6402: Get the actual buffer size here, instead of the one
// constrained by the virtual bottom.
const auto bufferSize = _pData->GetTextBuffer().GetSize();
switch (endpoint)
{
case TextPatternRangeEndpoint_End:
@@ -284,6 +286,8 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) noexc
}
else
{
// TODO GH#6986: properly handle "end of buffer" as last character
// instead of last cell
// expand to document
_start = bufferSize.Origin();
_end = bufferSize.EndExclusive();
@@ -393,7 +397,9 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_
// set of coords.
std::vector<double> coords;
const auto bufferSize = _getBufferSize();
// GH#6402: Get the actual buffer size here, instead of the one
// constrained by the virtual bottom.
const auto bufferSize = _pData->GetTextBuffer().GetSize();
// these viewport vars are converted to the buffer coordinate space
const auto viewport = bufferSize.ConvertToOrigin(_pData->GetViewport());