Compare commits

...

15 Commits

Author SHA1 Message Date
Dustin L. Howett
e33bc3d137 build: separate vpack creation from vpack publication (#19380)
This will allow us to publish vpacks without making the build fail
waiting for us to *merge* those vpacks into Windows. It also gives us
better control over when and where the vpack update gets merged.

(cherry picked from commit 6b428577b9)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgfLAks
Service-Version: 1.24
2025-09-25 13:37:20 -05:00
Dustin L. Howett
6aae2d0b72 Move newTabMenu creation to Settings fixups (#19353)
Some of the other settings fixups require there to be a valid
NewTabMenu, rather than just a temporary object. Since the resolving all
the menu entries after loading already forces the user to have a
`newTabMenu`, let's just codify it as a real fixup.

I've moved the SSH folder fixup after the settings fixup because it
relies on there being a NTM.

I decided not to make this fixup write back to the user's settings.
There are a couple reasons for this, all of which are flimsy.

- There are a number of tests that test fixup behavior, especially those
around actions, which would need to be updated for this new mandatory
key. I did not think it proper to add `newTabMenu` to ten unrelated
tests that only contain actions (for example.)
- We actually don't currently have mandatory keys. But this one was
always being added anyway, in a later phase...
- It's consistent with the existing behavior.

Closes #19356

(cherry picked from commit e80aadd98b)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzge0HjU
Service-Version: 1.24
2025-09-16 18:31:42 -05:00
Dustin L. Howett
148a86c81c VsDev: reject VS instances which do not actually contain devshell/devcmd (#19352)
Closes #19169

(cherry picked from commit 1926c4601c)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzge0Hto
Service-Version: 1.24
2025-09-16 18:31:40 -05:00
Dustin L. Howett
94e96ca048 Avoid generating SSH profiles using stale memory (#19354)
You can't return a `string_view` to a temporary. It's a miracle this
ever worked.

Broken since inception in a5f9c85c39

Closes #19355

(cherry picked from commit 46b9572e60)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzge0HmE
Service-Version: 1.24
2025-09-16 18:31:39 -05:00
Ayman Bagabas
b53d7df066 Add support for VT horizontal mouse wheel events (#19248)
This adds support for horizontal mouse wheel events (`WM_MOUSEHWHEEL`).
With this change, applications running in the terminal can now receive
and respond to horizontal scroll inputs from the mouse/trackpad.

Closes #19245
Closes #10329

(cherry picked from commit 814f78ed2c)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgep1sM
Service-Version: 1.24
2025-09-16 18:31:37 -05:00
Leonard Hecker
1194ad0343 Fix behavior of split-pane for existing windows (#19347)
Closes #18815

## Validation Steps Performed
* `wt -w 0 sp` splits the current tab 

(cherry picked from commit 0aee174e68)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeV1i4
Service-Version: 1.24
2025-09-16 14:38:44 -05:00
John Cavanaugh
04e1290102 Fix terminal profile schema to allow null in keybinding id (#19332)
Fixes the terminal profile jsonschema to allow for null in the id. This
is to match the current implementation when disabling a built in default
keybind.

(cherry picked from commit eb16eb26ab)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgemwL0
Service-Version: 1.24
2025-09-16 14:38:43 -05:00
Leonard Hecker
f5083714c1 Fix a crash in _makeCursorVisible (#19329)
Fixes the crash and also makes `SnapOnOutput` a bit nicer.

Closes #19325

## Validation Steps Performed
* Launch vim in WSL
* Exit
* No crash 

(cherry picked from commit 384932183f)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgegVZ8
Service-Version: 1.24
2025-09-16 14:38:41 -05:00
Leonard Hecker
7642eec206 Fix right click on tabs closing them (#19273)
I do not like this.

## Validation Steps Performed
* Enable close buttons on tabs
* Open a tab
* Close the tab with middle click
* Open a tab
* Right click the tab
* Tab doesn't close, Menu opens 

(cherry picked from commit 4a34a76504)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeAlgM
Service-Version: 1.24
2025-09-16 14:38:40 -05:00
Myungchul Keum
37c674dd6e Adjust "Dimidium" color scheme (#19303)
- Add Selection BG color
- Make Bright white brighter

## Summary of the Pull Request
Final tune for Dimidium color scheme before its release.

## References and Relevant Issues
#18563

## Detailed Description of the Pull Request / Additional comments
I made little change to Dimidium color scheme.

<img width="640" height="174" alt="cmp-lightness1c"
src="https://github.com/user-attachments/assets/2e4aa6ca-5864-4901-b323-2e2bb2bf00e8"
/>

![preview-terminal](https://github.com/user-attachments/assets/8a53c54d-942a-44a2-9ee7-9ff8a6d2dfab)

<img width="584" height="207" alt="image"
src="https://github.com/user-attachments/assets/b70b0759-7961-4f8f-aaa7-762fc48e425b"
/>

- Adjusted "Bright white" slightly brighter, hoping it can be
distinguished better from "White".
- Defined "Selection Background" color.

This will be the final tune for Dimidum color scheme.

(cherry picked from commit 8011f3e28c)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeMuoQ
Service-Version: 1.24
2025-09-05 14:42:10 -05:00
Leonard Hecker
3a57ba54a0 Avoid reentrancy issues when dropping AppHost (#19296)
tl;dr: ~Apphost() may pump the message loop.
That's no bueno. See comments in the diff.

Additionally, this PR enables `_assertIsMainThread` in
release to trace down mysterious crashes in those builds.

(cherry picked from commit 8d41ace320)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeJeYA
Service-Version: 1.24
2025-09-05 14:42:09 -05:00
Leonard Hecker
fc465bdae6 Fix CoreWindow being destroyed after handoff (#19298)
As per: https://github.com/microsoft/terminal/discussions/19280#discussioncomment-14237148

## Validation Steps Performed
* Launch wtd via handoff (spawn cmd, etc.)
* Shift+Click the tab bar + button to create a new window
* Close the initial window
* UI doesn't lock up 

(cherry picked from commit 7849b00cbd)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeLN9s
Service-Version: 1.24
2025-09-05 14:42:08 -05:00
Leonard Hecker
04a5dffdcd Fix a race condition around Open/CloseClipboard (#19297)
tl;dr: Open/CloseClipboard are surprisingly not thread-safe.

## Validation Steps Performed
* Copy a large amount of text (>1MB)
* Run `edit.exe`
* Press and hold Ctrl+Shift+V
* Doesn't crash 

(cherry picked from commit 5899343237)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeLIjw
Service-Version: 1.24
2025-09-05 14:42:07 -05:00
Dustin L. Howett
894e57769b Include Profile.BellSound as a media resource (#19289)
I legitimately cannot figure out how I forgot this. Bell should support
all the same validation as other media resources! Technically this means
you can set `bellSound` to `desktopWallpaper`, but... we'll pretend that
makes sense.

I reworked the viewmodel to be a little more sensible. It no longer
requires somebody else to check that its files exist. The settings UI
now also displays `File not found` in the _preview_ for the bell if it
is a single file which failed validation!

(cherry picked from commit 4272151adc)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeF9qU
Service-Version: 1.24
2025-09-05 14:42:06 -05:00
Dustin L. Howett
eb206177a4 sb: add appId to the StoreBroker blobs (new AERO requirement) (#19290)
> _I am altering the deal. Pray I do not alter it further._
> -the AERO team, maybe

(cherry picked from commit bd14f69080)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeF9fQ
Service-Version: 1.24
2025-09-05 14:42:05 -05:00
47 changed files with 460 additions and 296 deletions

View File

@@ -20,6 +20,7 @@
"DisableAutoPackageNameFormatting": false
},
"appSubmission": {
"appId": "9N8G5RFZ9XK3",
"productId": "00014050269303149694",
"targetPublishMode": "NotSet",
"targetPublishDate": null,

View File

@@ -20,6 +20,7 @@
"DisableAutoPackageNameFormatting": false
},
"appSubmission": {
"appId": "9N0DX20HK701",
"productId": "00013926773940052066",
"targetPublishMode": "NotSet",
"targetPublishDate": null,

View File

@@ -4,7 +4,7 @@
"collection": "microsoft",
"project": "OS",
"repo": "os.2020",
"name": "official/rs_we_adept_e4d2",
"name": "official/ge_current_directwinpd_deep",
"workitem": "38106206",
"CheckinFiles": [
{

View File

@@ -53,8 +53,12 @@ parameters:
displayName: "Publish Symbols to MSDL"
type: boolean
default: true
- name: createVpack
displayName: "Create a VPack for Windows"
type: boolean
default: false
- name: publishVpackToWindows
displayName: "Publish VPack to Windows"
displayName: "Publish above VPack to Windows"
type: boolean
default: false
@@ -89,6 +93,7 @@ extends:
clientId: $(SigningOriginalClientId)
terminalInternalPackageVersion: ${{ parameters.terminalInternalPackageVersion }}
publishSymbolsToPublic: ${{ parameters.publishSymbolsToPublic }}
createVpack: ${{ parameters.createVpack }}
publishVpackToWindows: ${{ parameters.publishVpackToWindows }}
symbolPublishingSubscription: $(SymbolPublishingServiceConnection)
symbolPublishingProject: $(SymbolPublishingProject)

View File

@@ -49,6 +49,9 @@ parameters:
- name: symbolExpiryTime
type: string
default: 36530 # This is the default from PublishSymbols@2
- name: createVpack
type: boolean
default: false
- name: publishVpackToWindows
type: boolean
default: false
@@ -192,8 +195,8 @@ extends:
ob_outputDirectory: $(JobOutputDirectory)
ob_artifactBaseName: $(JobOutputArtifactName)
### This job is also in charge of submitting the vpack to Windows if it's enabled
ob_createvpack_enabled: ${{ and(parameters.buildTerminal, parameters.publishVpackToWindows) }}
ob_updateOSManifest_enabled: ${{ and(parameters.buildTerminal, parameters.publishVpackToWindows) }}
ob_createvpack_enabled: ${{ and(parameters.buildTerminal, parameters.createVpack) }}
ob_updateOSManifest_enabled: ${{ and(parameters.buildTerminal, parameters.createVpack, parameters.publishVpackToWindows) }}
### If enabled above, these options are in play.
ob_createvpack_packagename: 'WindowsTerminal.app'
ob_createvpack_owneralias: 'conhost@microsoft.com'
@@ -229,7 +232,7 @@ extends:
New-Item "$(JobOutputDirectory)/vpack" -Type Directory
displayName: Make sure the vpack directory exists
- ${{ if parameters.publishVpackToWindows }}:
- ${{ if parameters.createVpack }}:
- pwsh: |-
Copy-Item -Verbose -Path "$(MsixBundlePath)" -Destination (Join-Path "$(JobOutputDirectory)/vpack" 'Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle')
displayName: Stage msixbundle for vpack

View File

@@ -2302,8 +2302,15 @@
"additionalProperties": false,
"properties": {
"id": {
"description": "The ID of the command this keybinding should execute.",
"type": "string"
"description": "The ID of the command this keybinding should execute (or null to disable a default).",
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"keys": {
"description": "Defines the key combinations used to call the command. It must be composed of...\n -any number of modifiers (ctrl/alt/shift)\n -a non-modifier key",

View File

@@ -44,10 +44,6 @@ namespace winrt::TerminalApp::implementation
{
_args = { value.begin(), value.end() };
_parseResult = _parsed.ParseArgs(_args);
if (_parseResult == 0)
{
_parsed.ValidateStartupCommands();
}
}
winrt::com_array<winrt::hstring> CommandlineArgs::Commandline()

View File

@@ -157,12 +157,7 @@ namespace winrt::TerminalApp::implementation
// Set this tab's icon to the icon from the content
_UpdateTabIcon(*newTabImpl);
// This is necessary, because WinUI does not have support for middle clicks.
// Its Tapped event doesn't provide the information what button was used either.
tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabPointerPressed });
tabViewItem.PointerReleased({ this, &TerminalPage::_OnTabPointerReleased });
tabViewItem.PointerExited({ this, &TerminalPage::_OnTabPointerExited });
tabViewItem.PointerEntered({ this, &TerminalPage::_OnTabPointerEntered });
// When the tab requests close, try to close it (prompt for approval, if required)
newTabImpl->CloseRequested([weakTab, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
@@ -664,7 +659,7 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - returns a tab corresponding to a view item. This might return null,
// so make sure to check the result!
winrt::TerminalApp::Tab TerminalPage::_GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept
winrt::TerminalApp::Tab TerminalPage::_GetTabByTabViewItem(const IInspectable& tabViewItem) const noexcept
{
uint32_t tabIndexFromControl{};
const auto items{ _tabView.TabItems() };
@@ -876,60 +871,64 @@ namespace winrt::TerminalApp::implementation
_UpdateTabView();
}
// Method Description:
// - Additional responses to clicking on a TabView's item. Currently, just remove tab with middle click
// Arguments:
// - sender: the control that originated this event (TabViewItem)
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabPointerPressed(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs)
void TerminalPage::_OnTabPointerPressed(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e)
{
if (eventArgs.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
if (!_tabItemMiddleClickHookEnabled || !e.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
{
if (const auto tabViewItem{ sender.try_as<MUX::Controls::TabViewItem>() })
{
_tabPointerMiddleButtonPressed = tabViewItem.CapturePointer(eventArgs.Pointer());
_tabPointerMiddleButtonExited = false;
}
eventArgs.Handled(true);
return;
}
const auto tabViewItem = sender.try_as<MUX::Controls::TabViewItem>();
if (!tabViewItem || !tabViewItem.CapturePointer(e.Pointer()))
{
return;
}
_tabItemMiddleClickExited = false;
_tabItemMiddleClickPointerEntered = tabViewItem.PointerEntered(winrt::auto_revoke, [this](auto&&, auto&& e) {
_tabItemMiddleClickExited = false;
e.Handled(true);
});
_tabItemMiddleClickPointerExited = tabViewItem.PointerExited(winrt::auto_revoke, [this](auto&&, auto&& e) {
_tabItemMiddleClickExited = true;
e.Handled(true);
});
_tabItemMiddleClickPointerCaptureLost = tabViewItem.PointerCaptureLost(winrt::auto_revoke, [this](auto&& sender, auto&& e) {
// The WinUI TabView calls CapturePointer() internally and it's not reference counted,
// so when it calls ReleasePointerCapture() in its PointerReleased handler,
// we get a PointerCaptureLost before we receive the PointerReleased event.
// This makes typical handling of PointerReleased events on our side difficult.
// Well, whatever, now we just hook PointerCaptureLost because we know WinUI will trigger it.
_tabItemMiddleClickPointerEntered.revoke();
_tabItemMiddleClickPointerExited.revoke();
_tabItemMiddleClickPointerCaptureLost.revoke();
if (!_tabItemMiddleClickExited && !e.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
{
_OnTabPointerReleasedCloseTab(std::move(sender));
}
e.Handled(true);
});
e.Handled(true);
}
// Method Description:
// - Tracking pointer state for tab remove
// Arguments:
// - sender: the control that originated this event (TabViewItem)
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabPointerReleased(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs)
safe_void_coroutine TerminalPage::_OnTabPointerReleasedCloseTab(IInspectable sender)
{
if (_tabPointerMiddleButtonPressed && !eventArgs.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
{
_tabPointerMiddleButtonPressed = false;
if (auto tabViewItem{ sender.try_as<MUX::Controls::TabViewItem>() })
{
tabViewItem.ReleasePointerCapture(eventArgs.Pointer());
if (!_tabPointerMiddleButtonExited)
{
_OnTabPointerReleasedCloseTab(std::move(tabViewItem));
}
}
eventArgs.Handled(true);
}
}
safe_void_coroutine TerminalPage::_OnTabPointerReleasedCloseTab(winrt::Microsoft::UI::Xaml::Controls::TabViewItem sender)
{
const auto tab = _GetTabByTabViewItem(sender);
if (!tab)
{
co_return;
}
// WinUI asynchronously updates its tab view items, so it may happen that we're given a
// `TabViewItem` that still contains a `Tab` which has actually already been removed.
// First we must yield once, to flush out whatever TabView is currently doing.
const auto strong = get_strong();
co_await wil::resume_foreground(Dispatcher());
const auto tab = _GetTabByTabViewItem(sender);
if (!tab)
{
co_return;
}
// `tab.Shutdown()` in `_RemoveTab()` sets the content to null = This checks if the tab is closed.
if (tab.Content())
{
@@ -937,34 +936,6 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Tracking pointer state for tab remove
// Arguments:
// - sender: the control that originated this event (TabViewItem)
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabPointerEntered(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs)
{
if (eventArgs.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
{
_tabPointerMiddleButtonExited = false;
eventArgs.Handled(true);
}
}
// Method Description:
// - Tracking pointer state for tab remove
// Arguments:
// - sender: the control that originated this event (TabViewItem)
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabPointerExited(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs)
{
if (eventArgs.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
{
_tabPointerMiddleButtonExited = true;
eventArgs.Handled(true);
}
}
void TerminalPage::_UpdatedSelectedTab(const winrt::TerminalApp::Tab& tab)
{
// Unfocus all the tabs.

View File

@@ -60,8 +60,42 @@ namespace winrt
namespace clipboard
{
wil::unique_close_clipboard_call open(HWND hwnd)
static SRWLOCK lock = SRWLOCK_INIT;
struct ClipboardHandle
{
explicit ClipboardHandle(bool open) :
_open{ open }
{
}
~ClipboardHandle()
{
if (_open)
{
ReleaseSRWLockExclusive(&lock);
CloseClipboard();
}
}
explicit operator bool() const noexcept
{
return _open;
}
private:
bool _open = false;
};
ClipboardHandle open(HWND hwnd)
{
// Turns out, OpenClipboard/CloseClipboard are not thread-safe whatsoever,
// and on CloseClipboard, the GetClipboardData handle may get freed.
// The problem is that WinUI also uses OpenClipboard (through WinRT which uses OLE),
// and so even with this mutex we can still crash randomly if you copy something via WinUI.
// Makes you wonder how many Windows apps are subtly broken, huh.
AcquireSRWLockExclusive(&lock);
bool success = false;
// OpenClipboard may fail to acquire the internal lock --> retry.
@@ -80,7 +114,12 @@ namespace clipboard
Sleep(sleep);
}
return wil::unique_close_clipboard_call{ success };
if (!success)
{
ReleaseSRWLockExclusive(&lock);
}
return ClipboardHandle{ success };
}
void write(wil::zwstring_view text, std::string_view html, std::string_view rtf)
@@ -344,6 +383,8 @@ namespace winrt::TerminalApp::implementation
{
const auto visibility = theme.Tab() ? theme.Tab().ShowCloseButton() : Settings::Model::TabCloseButtonVisibility::Always;
_tabItemMiddleClickHookEnabled = visibility == Settings::Model::TabCloseButtonVisibility::Never;
switch (visibility)
{
case Settings::Model::TabCloseButtonVisibility::Never:
@@ -3808,6 +3849,8 @@ namespace winrt::TerminalApp::implementation
theme.Tab().ShowCloseButton() :
Settings::Model::TabCloseButtonVisibility::Always;
_tabItemMiddleClickHookEnabled = visibility == Settings::Model::TabCloseButtonVisibility::Never;
for (const auto& tab : _tabs)
{
tab.CloseButtonVisibility(visibility);

View File

@@ -391,7 +391,7 @@ namespace winrt::TerminalApp::implementation
std::optional<uint32_t> _GetTabIndex(const TerminalApp::Tab& tab) const noexcept;
TerminalApp::Tab _GetFocusedTab() const noexcept;
winrt::com_ptr<Tab> _GetFocusedTabImpl() const noexcept;
TerminalApp::Tab _GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept;
TerminalApp::Tab _GetTabByTabViewItem(const IInspectable& tabViewItem) const noexcept;
void _HandleClosePaneRequested(std::shared_ptr<Pane> pane);
safe_void_coroutine _SetFocusedTab(const winrt::TerminalApp::Tab tab);
@@ -435,13 +435,18 @@ namespace winrt::TerminalApp::implementation
void _TabDragStarted(const IInspectable& sender, const IInspectable& eventArgs);
void _TabDragCompleted(const IInspectable& sender, const IInspectable& eventArgs);
bool _tabPointerMiddleButtonPressed{ false };
bool _tabPointerMiddleButtonExited{ false };
// BODGY: WinUI's TabView has a broken close event handler:
// If the close button is disabled, middle-clicking the tab raises no close
// event. Because that's dumb, we implement our own middle-click handling.
// `_tabItemMiddleClickHookEnabled` is true whenever the close button is hidden,
// and that enables all of the rest of this machinery (and this workaround).
bool _tabItemMiddleClickHookEnabled = false;
bool _tabItemMiddleClickExited = false;
PointerEntered_revoker _tabItemMiddleClickPointerEntered;
PointerExited_revoker _tabItemMiddleClickPointerExited;
PointerCaptureLost_revoker _tabItemMiddleClickPointerCaptureLost;
void _OnTabPointerPressed(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
void _OnTabPointerReleased(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
safe_void_coroutine _OnTabPointerReleasedCloseTab(winrt::Microsoft::UI::Xaml::Controls::TabViewItem sender);
void _OnTabPointerEntered(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
void _OnTabPointerExited(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
safe_void_coroutine _OnTabPointerReleasedCloseTab(IInspectable sender);
void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs);
void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs);

View File

@@ -279,7 +279,7 @@ namespace winrt::TerminalApp::implementation
auto sounds{ _profile.BellSound() };
if (sounds && sounds.Size() > 0)
{
winrt::hstring soundPath{ wil::ExpandEnvironmentStringsW<std::wstring>(sounds.GetAt(rand() % sounds.Size()).c_str()) };
winrt::hstring soundPath{ sounds.GetAt(rand() % sounds.Size()).Resolved() };
winrt::Windows::Foundation::Uri uri{ soundPath };
_playBellSound(uri);
}

View File

@@ -1048,6 +1048,11 @@ namespace winrt::TerminalApp::implementation
// (or called TerminalWindow::Initialize)
if (_appArgs->ExitCode() == 0)
{
// The existing logic (before this commit) strictly relied on
// ValidateStartupCommands() only to be called for new windows.
// It modifies the actions it stores.
parsedArgs.ValidateStartupCommands();
// If the size of the arguments list is 1,
// then it contains only the executable name and no other arguments.
_hasCommandLineArguments = _appArgs->CommandlineRef().size() > 1;

View File

@@ -485,7 +485,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - modifiers: The modifiers pressed during this event, in the form of a VirtualKeyModifiers
// - delta: the mouse wheel delta that triggered this event.
bool ControlInteractivity::MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const int32_t delta,
const Core::Point delta,
const Core::Point pixelPosition,
const Control::MouseButtonState buttonState)
{
@@ -506,9 +506,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// PointerPoint to work with. So, we're just going to do a
// mousewheel event manually
return _sendMouseEventHelper(terminalPosition,
WM_MOUSEWHEEL,
delta.Y != 0 ? WM_MOUSEWHEEL : WM_MOUSEHWHEEL,
modifiers,
::base::saturated_cast<short>(delta),
::base::saturated_cast<short>(delta.Y != 0 ? delta.Y : delta.X),
buttonState);
}
@@ -517,15 +517,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (ctrlPressed && shiftPressed && _core->Settings().ScrollToChangeOpacity())
{
_mouseTransparencyHandler(delta);
_mouseTransparencyHandler(delta.Y);
}
else if (ctrlPressed && !shiftPressed && _core->Settings().ScrollToZoom())
{
_mouseZoomHandler(delta);
_mouseZoomHandler(delta.Y);
}
else
{
_mouseScrollHandler(delta, pixelPosition, WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown));
_mouseScrollHandler(delta.Y, pixelPosition, WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown));
}
return false;
}
@@ -659,7 +659,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core->IsVtMouseModeEnabled();
}
bool ControlInteractivity::_shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta)
bool ControlInteractivity::_shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const Core::Point delta)
{
// If the user is holding down Shift, suppress mouse events
// TODO GH#4875: disable/customize this functionality
@@ -667,7 +667,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return false;
}
return _core->ShouldSendAlternateScroll(WM_MOUSEWHEEL, delta);
if (delta.Y != 0)
{
return _core->ShouldSendAlternateScroll(WM_MOUSEWHEEL, delta.Y);
}
else
{
return _core->ShouldSendAlternateScroll(WM_MOUSEHWHEEL, delta.X);
}
}
// Method Description:

View File

@@ -74,7 +74,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TouchReleased();
bool MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const int32_t delta,
const Core::Point delta,
const Core::Point pixelPosition,
const Control::MouseButtonState state);
@@ -153,7 +153,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _hyperlinkHandler(const std::wstring_view uri);
bool _canSendVTMouseInput(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
bool _shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta);
bool _shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const Core::Point delta);
til::point _getTerminalPosition(const til::point pixelPosition, bool roundToNearestCell);

View File

@@ -60,7 +60,7 @@ namespace Microsoft.Terminal.Control
void TouchReleased();
Boolean MouseWheel(Microsoft.Terminal.Core.ControlKeyStates modifiers,
Int32 delta,
Microsoft.Terminal.Core.Point delta,
Microsoft.Terminal.Core.Point pixelPosition,
MouseButtonState state);

View File

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

View File

@@ -2164,15 +2164,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
RestorePointerCursor.raise(*this, nullptr);
const auto point = args.GetCurrentPoint(*this);
// GH#10329 - we don't need to handle horizontal scrolls. Only vertical ones.
// So filter out the horizontal ones.
if (point.Properties().IsHorizontalMouseWheel())
{
return;
}
auto delta = point.Properties().MouseWheelDelta();
auto result = _interactivity.MouseWheel(ControlKeyStates{ args.KeyModifiers() },
point.Properties().MouseWheelDelta(),
point.Properties().IsHorizontalMouseWheel() ?
Core::Point{ delta, 0 } :
Core::Point{ 0, delta },
_toTerminalOrigin(point.Position()),
TermControl::GetPressedMouseButtons(point));
if (result)
@@ -2192,7 +2188,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - 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 Core::Point delta,
const bool leftButtonDown,
const bool midButtonDown,
const bool rightButtonDown)

View File

@@ -147,7 +147,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
bool OnMouseWheel(const Windows::Foundation::Point location, const winrt::Microsoft::Terminal::Core::Point delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
~TermControl();

View File

@@ -720,7 +720,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
_MarkDuplicateBellSoundDirectories();
_CheckBellSoundsExistence();
_NotifyChanges(L"CurrentBellSounds");
}
@@ -732,19 +731,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
if (!_profile.HasBellSound())
{
std::vector<hstring> newSounds;
std::vector<IMediaResource> newSounds;
if (const auto inheritedSounds = _profile.BellSound())
{
// copy inherited bell sounds to the current layer
newSounds.reserve(inheritedSounds.Size());
for (const auto sound : inheritedSounds)
{
newSounds.push_back(sound);
}
newSounds = wil::to_vector(inheritedSounds);
}
// if we didn't inherit any bell sounds,
// we should still set the bell sound to an empty list (instead of null)
_profile.BellSound(winrt::single_threaded_vector<hstring>(std::move(newSounds)));
_profile.BellSound(winrt::single_threaded_vector(std::move(newSounds)));
}
}
@@ -768,56 +762,35 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
// Method Description:
// - Check if the bell sounds exist on disk. Mark any that don't exist
// so that they show the appropriate UI
safe_void_coroutine ProfileViewModel::_CheckBellSoundsExistence()
BellSoundViewModel::BellSoundViewModel(const Model::IMediaResource& resource) :
_resource{ resource }
{
co_await winrt::resume_background();
std::vector<Editor::BellSoundViewModel> markedSounds;
for (auto&& sound : _CurrentBellSounds)
if (_resource.Ok() && _resource.Path() != _resource.Resolved())
{
if (!std::filesystem::exists(std::wstring_view{ sound.Path() }))
{
markedSounds.push_back(sound);
}
// If the resource was resolved to something other than its path, show the path!
_ShowDirectory = true;
}
co_await winrt::resume_foreground(_dispatcher);
for (auto&& sound : markedSounds)
{
get_self<BellSoundViewModel>(sound)->FileExists(false);
}
}
BellSoundViewModel::BellSoundViewModel(hstring path) :
_Path{ path }
{
PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
if (args.PropertyName() == L"FileExists")
{
_NotifyChanges(L"DisplayPath", L"SubText");
}
});
}
hstring BellSoundViewModel::DisplayPath() const
{
if (_FileExists)
if (_resource.Ok())
{
// filename
const std::filesystem::path filePath{ std::wstring_view{ _Path } };
// filename; start from the resolved path to show where it actually landed
auto resolvedPath{ _resource.Resolved() };
const std::filesystem::path filePath{ std::wstring_view{ resolvedPath } };
return hstring{ filePath.filename().wstring() };
}
return _Path;
return _resource.Path();
}
hstring BellSoundViewModel::SubText() const
{
if (_FileExists)
if (_resource.Ok())
{
// Directory
const std::filesystem::path filePath{ std::wstring_view{ _Path } };
auto resolvedPath{ _resource.Resolved() };
const std::filesystem::path filePath{ std::wstring_view{ resolvedPath } };
return hstring{ filePath.parent_path().wstring() };
}
return RS_(L"Profile_BellSoundNotFound");
@@ -825,17 +798,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
hstring ProfileViewModel::BellSoundPreview()
{
const auto& currentSound = BellSound();
if (!currentSound || currentSound.Size() == 0)
if (!_CurrentBellSounds || _CurrentBellSounds.Size() == 0)
{
return RS_(L"Profile_BellSoundPreviewDefault");
}
else if (currentSound.Size() == 1)
if (_CurrentBellSounds.Size() > 1)
{
std::filesystem::path filePath{ std::wstring_view{ currentSound.GetAt(0) } };
return hstring{ filePath.filename().wstring() };
return RS_(L"Profile_BellSoundPreviewMultiple");
}
return RS_(L"Profile_BellSoundPreviewMultiple");
const auto currentBellSound = _CurrentBellSounds.GetAt(0);
if (currentBellSound.FileExists())
{
return currentBellSound.DisplayPath();
}
return RS_(L"Profile_BellSoundNotFound");
}
void ProfileViewModel::RequestAddBellSound(hstring path)
@@ -844,9 +822,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// copy it over to the current layer and apply modifications
_PrepareModelForBellSoundModification();
// No need to check if the file exists. We came from the FilePicker. That's good enough.
_CurrentBellSounds.Append(winrt::make<BellSoundViewModel>(path));
_profile.BellSound().Append(path);
auto bellResource{ MediaResourceHelper::FromString(path) };
bellResource.Resolve(path); // No need to check if the file exists. We came from the FilePicker. That's good enough.
_CurrentBellSounds.Append(winrt::make<BellSoundViewModel>(bellResource));
_profile.BellSound().Append(bellResource);
_NotifyChanges(L"CurrentBellSounds");
}

View File

@@ -30,13 +30,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
struct BellSoundViewModel : BellSoundViewModelT<BellSoundViewModel>, ViewModelHelper<BellSoundViewModel>
{
public:
BellSoundViewModel(hstring path);
BellSoundViewModel(const Model::IMediaResource& resource);
hstring Path() const { return _resource.Path(); }
bool FileExists() const { return _resource.Ok(); }
hstring DisplayPath() const;
hstring SubText() const;
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, FileExists, true);
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, Path);
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, ShowDirectory);
private:
Model::IMediaResource _resource;
};
struct ProfileViewModel : ProfileViewModelT<ProfileViewModel>, ViewModelHelper<ProfileViewModel>
@@ -190,7 +193,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void _InitializeCurrentBellSounds();
void _PrepareModelForBellSoundModification();
void _MarkDuplicateBellSoundDirectories();
safe_void_coroutine _CheckBellSoundsExistence();
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _MonospaceFontList;
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _FontList;
static Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> _BuiltInIcons;

View File

@@ -27,7 +27,7 @@ namespace Microsoft.Terminal.Settings.Editor
runtimeclass BellSoundViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
String Path;
String Path { get; };
String DisplayPath { get; };
String SubText { get; };
Boolean ShowDirectory { get; };
@@ -149,7 +149,7 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SnapOnInput);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AltGrAliasing);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.BellStyle, BellStyle);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.Collections.IVector<String>, BellSound);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.IMediaResource>, BellSound);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, Elevate);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, ReloadEnvironmentVariables);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RightClickContextMenu);

View File

@@ -559,6 +559,7 @@ bool SettingsLoader::AddDynamicProfileFolders()
folderEntry->Inlining(FolderEntryInlining::Auto);
folderEntry->RawEntries(winrt::single_threaded_vector<Model::NewTabMenuEntry>({ *matchProfilesEntry }));
// NewTabMenu is guaranteed to exist by FixupUserSettings, which runs before this fixup.
userSettings.globals->NewTabMenu().Append(folderEntry.as<Model::NewTabMenuEntry>());
state->SSHFolderGenerated(true);
return true;
@@ -689,6 +690,16 @@ bool SettingsLoader::FixupUserSettings()
fixedUp = true;
}
// Terminal 1.24
// Ensure that the user always has a newTabMenu. We used to do this last, after
// resolving all of the new tab menu entries, but there was no conceivable reason
// that it should happen so late.
if (!userSettings.globals->HasNewTabMenu())
{
userSettings.globals->NewTabMenu(winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} }));
// This one does not need to be written back to the settings file immediately, it can wait until we write one for another reason.
}
return fixedUp;
}
@@ -1263,8 +1274,8 @@ try
// DisableDeletedProfiles returns true whenever we encountered any new generated/dynamic profiles.
// Similarly FixupUserSettings returns true, when it encountered settings that were patched up.
mustWriteToDisk |= loader.DisableDeletedProfiles();
mustWriteToDisk |= loader.AddDynamicProfileFolders();
mustWriteToDisk |= loader.FixupUserSettings();
mustWriteToDisk |= loader.AddDynamicProfileFolders();
// If this throws, the app will catch it and use the default settings.
const auto settings = winrt::make_self<CascadiaSettings>(std::move(loader));
@@ -1745,16 +1756,6 @@ void CascadiaSettings::_resolveNewTabMenuProfiles() const
{
remainingProfilesEntry.Profiles(remainingProfiles);
}
// If the configuration does not have a "newTabMenu" field, GlobalAppSettings
// will return a default value containing just a "remainingProfiles" entry. However,
// this value is regenerated on every "get" operation, so the effect of setting
// the remaining profiles above will be undone. So only in the case that no custom
// value is present in GlobalAppSettings, we will store the modified default value.
if (!_globals->HasNewTabMenu())
{
_globals->NewTabMenu(entries);
}
}
// Method Description:

View File

@@ -95,7 +95,7 @@ Author(s):
X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \
X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \
X(bool, RightClickContextMenu, "rightClickContextMenu", false) \
X(Windows::Foundation::Collections::IVector<winrt::hstring>, BellSound, "bellSound", nullptr) \
X(Windows::Foundation::Collections::IVector<IMediaResource>, BellSound, "bellSound", nullptr) \
X(bool, Elevate, "elevate", false) \
X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \
X(bool, ShowMarks, "showMarksOnScrollbar", false) \

View File

@@ -121,16 +121,10 @@ winrt::com_ptr<Profile> Profile::CopySettings() const
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_COPY)
#undef PROFILE_SETTINGS_COPY
// BellSound is an IVector<hstring>, so we need to manually copy it over
if (_BellSound)
{
std::vector<hstring> sounds;
sounds.reserve(_BellSound->Size());
for (const auto& sound : *_BellSound)
{
sounds.emplace_back(sound);
}
profile->_BellSound = single_threaded_vector(std::move(sounds));
// BellSound is an IVector<>, so we need to make a new vector pointing at the same objects
profile->_BellSound = winrt::single_threaded_vector(wil::to_vector(*_BellSound));
}
if (_UnfocusedAppearance)
@@ -560,6 +554,16 @@ void Profile::ResolveMediaResources(const Model::MediaResourceResolver& resolver
{
container->ResolveMediaResources(resolver);
}
if (const auto [bellSoundSource, bellSounds]{ _getBellSoundOverrideSourceAndValueImpl() };
bellSoundSource && bellSounds && *bellSounds && bellSounds->Size() > 0)
{
for (const auto& bellSound : *bellSounds)
{
ResolveMediaResource(bellSoundSource->_Origin, bellSoundSource->SourceBasePath, bellSound, resolver);
}
// It's important that we keep the invalid bell sounds in the list, because we may want to write it back out to disk
}
}
void Profile::LogSettingChanges(std::set<std::string>& changes, const std::string_view& context) const

View File

@@ -76,7 +76,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IMap<String COMMA String>, EnvironmentVariables);
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector<String>, BellSound);
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector<IMediaResource>, BellSound);
INHERITABLE_PROFILE_SETTING(Boolean, Elevate);
INHERITABLE_PROFILE_SETTING(Boolean, AutoMarkPrompts);

View File

@@ -35,19 +35,14 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
/*static*/ const std::wregex SshHostGenerator::_configKeyValueRegex{ LR"(^\s*(\w+)\s+([^\s]+.*[^\s])\s*$)" };
/*static*/ std::wstring_view SshHostGenerator::_getProfileName(const std::wstring_view& hostName) noexcept
winrt::hstring _getProfileName(const std::wstring_view& hostName) noexcept
{
return std::wstring_view{ L"" + PROFILE_TITLE_PREFIX + hostName };
return winrt::hstring{ fmt::format(FMT_COMPILE(L"{0}{1}"), PROFILE_TITLE_PREFIX, hostName) };
}
/*static*/ std::wstring_view SshHostGenerator::_getProfileIconPath() noexcept
winrt::hstring _getProfileCommandLine(const std::wstring_view& sshExePath, const std::wstring_view& hostName) noexcept
{
return PROFILE_ICON_PATH;
}
/*static*/ std::wstring_view SshHostGenerator::_getProfileCommandLine(const std::wstring_view& sshExePath, const std::wstring_view& hostName) noexcept
{
return std::wstring_view{ L"\"" + sshExePath + L"\" " + hostName };
return winrt::hstring{ fmt::format(FMT_COMPILE(LR"("{0}" {1})"), sshExePath, hostName) };
}
/*static*/ bool SshHostGenerator::_tryFindSshExePath(std::wstring& sshExePath) noexcept
@@ -164,8 +159,8 @@ void SshHostGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementatio
{
const auto profile{ CreateDynamicProfile(_getProfileName(hostName)) };
profile->Commandline(winrt::hstring{ _getProfileCommandLine(sshExePath, hostName) });
profile->Icon(winrt::hstring{ _getProfileIconPath() });
profile->Commandline(_getProfileCommandLine(sshExePath, hostName));
profile->Icon(winrt::hstring{ PROFILE_ICON_PATH });
profiles.emplace_back(profile);
}

View File

@@ -31,10 +31,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model
private:
static const std::wregex _configKeyValueRegex;
static std::wstring_view _getProfileName(const std::wstring_view& hostName) noexcept;
static std::wstring_view _getProfileIconPath() noexcept;
static std::wstring_view _getProfileCommandLine(const std::wstring_view& sshExePath, const std::wstring_view& hostName) noexcept;
static bool _tryFindSshExePath(std::wstring& sshExePath) noexcept;
static bool _tryParseConfigKeyValue(const std::wstring_view& line, std::wstring& key, std::wstring& value) noexcept;
static void _getHostNamesFromConfigFile(const std::wstring_view& configPath, std::vector<std::wstring>& hostNames) noexcept;

View File

@@ -26,12 +26,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model
void GenerateProfiles(const VsSetupConfiguration::VsSetupInstance& instance, bool hidden, std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
private:
bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance&) const
bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance& instance) const
{
// We only support version of VS from 15.0.
// Per heaths: The [ISetupConfiguration] COM server only supports Visual Studio 15.0 and newer anyway.
// Eliding the version range will improve the discovery performance by not having to parse or compare the versions.
return true;
std::error_code ec;
return std::filesystem::exists(GetDevCmdScriptPath(instance), ec) && !ec;
}
std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance) const

View File

@@ -28,7 +28,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
private:
bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance& instance) const
{
return instance.VersionInRange(L"[16.2,)");
std::error_code ec;
return instance.VersionInRange(L"[16.2,)") && std::filesystem::exists(GetDevShellModulePath(instance), ec) && !ec;
}
std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance) const

View File

@@ -85,7 +85,7 @@
"background": "#141414",
"foreground": "#BAB7B6",
"cursorColor": "#37E57B",
"selectionBackground": "#FFFFFF",
"selectionBackground": "#8DB8E5",
"black": "#000000",
"red": "#CF494C",
"green": "#60B442",
@@ -101,7 +101,7 @@
"brightBlue": "#688DFD",
"brightPurple": "#ED6FE9",
"brightCyan": "#32E0FB",
"brightWhite": "#D3D8D9"
"brightWhite": "#DEE3E4"
},
{
"name": "Ottosson",

View File

@@ -170,7 +170,7 @@ namespace ControlUnitTests
// The mouse location and buttons don't matter here.
interactivity->MouseWheel(modifiers,
30,
Core::Point{ 0, 30 },
Core::Point{ 0, 0 },
buttonState);
}
@@ -188,7 +188,7 @@ namespace ControlUnitTests
// The mouse location and buttons don't matter here.
interactivity->MouseWheel(modifiers,
-30,
Core::Point{ 0, -30 },
Core::Point{ 0, 0 },
buttonState);
}
@@ -245,7 +245,7 @@ namespace ControlUnitTests
expectedTop = 20;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
@@ -254,18 +254,18 @@ namespace ControlUnitTests
{
expectedTop--;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
}
Log::Comment(L"Scrolling up more should do nothing");
expectedTop = 0;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
@@ -275,7 +275,7 @@ namespace ControlUnitTests
Log::Comment(NoThrowString().Format(L"---scroll down #%d---", i));
expectedTop++;
interactivity->MouseWheel(modifiers,
-WHEEL_DELTA,
Core::Point{ 0, -WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
Log::Comment(NoThrowString().Format(L"internal scrollbar pos:%f", interactivity->_internalScrollbarPosition));
@@ -283,11 +283,11 @@ namespace ControlUnitTests
Log::Comment(L"Scrolling down more should do nothing");
expectedTop = 21;
interactivity->MouseWheel(modifiers,
-WHEEL_DELTA,
Core::Point{ 0, -WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
interactivity->MouseWheel(modifiers,
-WHEEL_DELTA,
Core::Point{ 0, -WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
}
@@ -444,7 +444,7 @@ namespace ControlUnitTests
Log::Comment(L"Scroll up a line, with the left mouse button selected");
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
cursorPosition1.to_core_point(),
leftMouseDown);
@@ -492,55 +492,55 @@ namespace ControlUnitTests
const Core::Point mousePos{ 0, 0 };
Control::MouseButtonState state{};
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
Log::Comment(L"Scroll up 4 more times. Once we're at 3/5 scrolls, "
L"we'll round the internal scrollbar position to scrolling to the next row.");
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 3/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 3/5
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 4/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 4/5
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 5/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 5/5
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
Log::Comment(L"Jump to line 5, so we can scroll down from there.");
interactivity->UpdateScrollbar(5);
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
Log::Comment(L"Scroll down 5 times, at which point we should accumulate a whole row of delta.");
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 1/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 2/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 3/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 3/5
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 4/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 4/5
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 5/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 5/5
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
Log::Comment(L"Jump to the bottom.");
interactivity->UpdateScrollbar(21);
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
Log::Comment(L"Scroll a bit, then emit a line of text. We should reset our internal scroll position.");
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 3/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 3/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 4/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 4/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 5/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 5/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
}
@@ -709,7 +709,7 @@ namespace ControlUnitTests
{
expectedTop--;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 },
noMouseDown);
}

View File

@@ -91,6 +91,7 @@ namespace SettingsModelUnitTests
TEST_METHOD(ProfileInheritsInvalidIconAndHasNoCommandline);
TEST_METHOD(ProfileSpecifiesNullIcon);
TEST_METHOD(ProfileSpecifiesNullIconAndHasNoCommandline);
TEST_METHOD(ProfileOverwritesBellSound);
// FRAGMENT BEHAVIORS
TEST_METHOD(FragmentUpdatesBaseProfile);
@@ -125,7 +126,11 @@ namespace SettingsModelUnitTests
"backgroundImage": "imagePathFromBase",
"guid": "{862d46aa-cc9c-4e6c-b872-9cadaafcdbbe}",
"icon": "iconFromBase",
"name": "Base"
"name": "Base",
"bellSound": [
"C:\\Windows\\Media\\Alarm01.wav",
"C:\\Windows\\Media\\Alarm02.wav"
]
},
{
"backgroundImage": "focusedImagePathFromBase",
@@ -167,7 +172,7 @@ namespace SettingsModelUnitTests
]
})" };
static constexpr int numberOfMediaResourcesInDefaultSettings{ 9 };
static constexpr int numberOfMediaResourcesInDefaultSettings{ 11 };
struct Fragment
{
@@ -1021,6 +1026,37 @@ namespace SettingsModelUnitTests
VERIFY_IS_TRUE(icon.Ok()); // Profile with commandline always has an icon
VERIFY_ARE_EQUAL(cmdCommandline, icon.Resolved());
}
// A profile replaces the bell sounds (2) in the base settings; all bell sounds retained
void MediaResourceTests::ProfileOverwritesBellSound()
{
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
winrt::com_ptr<implementation::CascadiaSettings> settings;
{
auto [t, e] = requireCalled([&](auto&&, auto&&, auto&& resource) {
// All resources are invalid.
resource.Reject();
});
g_mediaResolverHook = t;
settings = createSettings(R"({
"profiles": {
"list": [
{
"guid": "{862d46aa-cc9c-4e6c-b872-9cadaafcdbbe}",
"bellSound": [
"does not matter; resolved rejected"
],
},
]
}
})");
}
auto profile{ settings->GetProfileByName(L"Base") };
auto bellSounds{ profile.BellSound() };
VERIFY_ARE_EQUAL(1u, bellSounds.Size());
VERIFY_IS_FALSE(bellSounds.GetAt(0).Ok());
}
#pragma endregion
#pragma region Real Resolver Tests

View File

@@ -745,7 +745,7 @@ void AppHost::_RaiseVisualBell(const winrt::Windows::Foundation::IInspectable&,
// - delta: the wheel delta that triggered this event.
// Return Value:
// - <none>
void AppHost::_WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const int32_t delta)
void AppHost::_WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const winrt::Microsoft::Terminal::Core::Point delta)
{
if (_windowLogic)
{

View File

@@ -73,7 +73,7 @@ private:
void _RaiseVisualBell(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& arg);
void _WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const int32_t delta);
void _WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const winrt::Microsoft::Terminal::Core::Point delta);
void _WindowActivated(bool activated);
void _WindowMoved();

View File

@@ -595,6 +595,7 @@ void IslandWindow::_OnGetMinMaxInfo(const WPARAM /*wParam*/, const LPARAM lParam
WindowCloseButtonClicked.raise();
return 0;
}
case WM_MOUSEHWHEEL:
case WM_MOUSEWHEEL:
try
{
@@ -623,7 +624,11 @@ void IslandWindow::_OnGetMinMaxInfo(const WPARAM /*wParam*/, const LPARAM lParam
const auto scale = GetCurrentDpiScale();
const winrt::Windows::Foundation::Point real{ relative.x / scale, relative.y / scale };
const auto wheelDelta = static_cast<short>(HIWORD(wparam));
winrt::Microsoft::Terminal::Core::Point wheelDelta{ 0, static_cast<int32_t>(HIWORD(wparam)) };
if (message == WM_MOUSEHWHEEL)
{
std::swap(wheelDelta.X, wheelDelta.Y);
}
// Raise an event, so any listeners can handle the mouse wheel event manually.
MouseScrolled.raise(real, wheelDelta);

View File

@@ -74,7 +74,7 @@ public:
til::event<winrt::delegate<>> DragRegionClicked;
til::event<winrt::delegate<>> WindowCloseButtonClicked;
til::event<winrt::delegate<void(winrt::Windows::Foundation::Point, int32_t)>> MouseScrolled;
til::event<winrt::delegate<void(winrt::Windows::Foundation::Point, winrt::Microsoft::Terminal::Core::Point)>> MouseScrolled;
til::event<winrt::delegate<void(bool)>> WindowActivated;
til::event<winrt::delegate<void()>> NotifyNotificationIconPressed;
til::event<winrt::delegate<void()>> NotifyWindowHidden;

View File

@@ -253,6 +253,29 @@ void WindowEmperor::CreateNewWindow(winrt::TerminalApp::WindowRequestedArgs args
_windowCount += 1;
_windows.emplace_back(std::move(host));
if (_windowCount == 1)
{
// The first CoreWindow is created implicitly by XAML and parented to the
// first XAML island. We parent it to our initial window for 2 reasons:
// * On Windows 10 the CoreWindow will show up as a visible window on the taskbar
// due to a WinUI bug, and this will hide it, because our initial window is hidden.
// * When we DestroyWindow() the island it will destroy the CoreWindow,
// and it's not possible to recreate it. That's also a WinUI bug.
//
// Note that this must be done after the first window (= first island) is created.
if (const auto coreWindow = winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread())
{
if (const auto interop = coreWindow.try_as<ICoreWindowInterop>())
{
HWND coreHandle = nullptr;
if (SUCCEEDED(interop->get_WindowHandle(&coreHandle)) && coreHandle)
{
SetParent(coreHandle, _window.get());
}
}
}
}
}
AppHost* WindowEmperor::_mostRecentWindow() const noexcept
@@ -395,24 +418,6 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectoryW(system32.c_str()));
}
// The first CoreWindow is created implicitly by XAML and parented to the
// first XAML island. We parent it to our initial window for 2 reasons:
// * On Windows 10 the CoreWindow will show up as a visible window on the taskbar
// due to a WinUI bug, and this will hide it, because our initial window is hidden.
// * When we DestroyWindow() the island it will destroy the CoreWindow,
// and it's not possible to recreate it. That's also a WinUI bug.
if (const auto coreWindow = winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread())
{
if (const auto interop = coreWindow.try_as<ICoreWindowInterop>())
{
HWND coreHandle = nullptr;
if (SUCCEEDED(interop->get_WindowHandle(&coreHandle)) && coreHandle)
{
SetParent(coreHandle, _window.get());
}
}
}
{
TerminalConnection::ConptyConnection::NewConnection([this](TerminalConnection::ConptyConnection conn) {
TerminalApp::CommandlineArgs args;
@@ -544,6 +549,8 @@ void WindowEmperor::_dispatchSpecialKey(const MSG& msg) const
void WindowEmperor::_dispatchCommandline(winrt::TerminalApp::CommandlineArgs args)
{
_assertIsMainThread();
const auto exitCode = args.ExitCode();
if (const auto msg = args.ExitMessage(); !msg.empty())
@@ -681,6 +688,8 @@ safe_void_coroutine WindowEmperor::_dispatchCommandlineCurrentDesktop(winrt::Ter
bool WindowEmperor::_summonWindow(const SummonWindowSelectionArgs& args) const
{
_assertIsMainThread();
AppHost* window = nullptr;
if (args.WindowID)
@@ -721,6 +730,8 @@ bool WindowEmperor::_summonWindow(const SummonWindowSelectionArgs& args) const
void WindowEmperor::_summonAllWindows() const
{
_assertIsMainThread();
TerminalApp::SummonWindowBehavior args;
args.ToggleVisibility(false);
@@ -858,6 +869,9 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
// Did the window counter get out of sync? It shouldn't.
assert(_windowCount == gsl::narrow_cast<int32_t>(_windows.size()));
// !!! NOTE !!!
// At least theoretically the lParam pointer may be invalid.
// We should only access it if we find it in our _windows array.
const auto host = reinterpret_cast<AppHost*>(lParam);
auto it = _windows.begin();
const auto end = _windows.end();
@@ -866,7 +880,15 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
{
if (host == it->get())
{
host->Close();
// NOTE: The AppHost destructor is highly non-trivial.
//
// It _may_ call into XAML, which _may_ pump the message loop, which would then recursively
// re-enter this function, which _may_ then handle another WM_CLOSE_TERMINAL_WINDOW,
// which would change the _windows array, and invalidate our iterator and crash.
//
// We can prevent this by deferring destruction until after the erase() call.
const auto strong = *it;
strong->Close();
_windows.erase(it);
break;
}

View File

@@ -84,14 +84,14 @@ private:
int32_t _windowCount = 0;
int32_t _messageBoxCount = 0;
#ifdef NDEBUG
#if 0 // #ifdef NDEBUG
static constexpr void _assertIsMainThread() noexcept
{
}
#else
void _assertIsMainThread() const noexcept
{
assert(_mainThreadId == GetCurrentThreadId());
WI_ASSERT_MSG(_mainThreadId == GetCurrentThreadId(), "This part of WindowEmperor must be accessed from the UI thread");
}
DWORD _mainThreadId = GetCurrentThreadId();
#endif

View File

@@ -1718,6 +1718,19 @@ void SCREEN_INFORMATION::SnapOnInput(const WORD vkey)
}
}
SCREEN_INFORMATION::SnapOnScopeExit SCREEN_INFORMATION::SnapOnOutput() noexcept
{
const auto call =
// We don't need to snap-on-output the alt buffer since it doesn't scroll anyway.
// More importantly though, in the current architecture the alt buffer gets deallocated
// the second we receive a \033[?1049l, which makes our this pointer hazardous to use.
_psiMainBuffer == nullptr &&
// We only want to snap if the user didn't intentionally scroll away.
// The snapping logic could be improved, but it's alright for now.
_viewport.IsInBounds(_textBuffer->GetCursor().GetPosition());
return SnapOnScopeExit{ call ? this : nullptr };
}
void SCREEN_INFORMATION::_makeCursorVisible()
{
if (_textBuffer->GetCursor().IsVisible())

View File

@@ -50,6 +50,25 @@ class ConversionAreaInfo; // forward decl window. circular reference
class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console::IIoProvider
{
public:
// This little helper works like wil::scope_exit but is slimmer
// (= easier to optimize) and has a concrete type (= can declare).
struct SnapOnScopeExit
{
~SnapOnScopeExit()
{
if (self)
{
try
{
self->_makeCursorVisible();
}
CATCH_LOG();
}
}
SCREEN_INFORMATION* self;
};
[[nodiscard]] static NTSTATUS CreateInstance(_In_ til::size coordWindowSize,
const FontInfo fontInfo,
_In_ til::size coordScreenBufferSize,
@@ -73,16 +92,7 @@ public:
void MakeCurrentCursorVisible();
void MakeCursorVisible(til::point position);
void SnapOnInput(WORD vkey);
auto SnapOnOutput() noexcept
{
const auto inBounds = _viewport.IsInBounds(_textBuffer->GetCursor().GetPosition());
return wil::scope_exit([this, inBounds]() {
if (inBounds)
{
_makeCursorVisible();
}
});
}
SnapOnScopeExit SnapOnOutput() noexcept;
void ClipToScreenBuffer(_Inout_ til::inclusive_rect* const psrClip) const;

View File

@@ -567,7 +567,7 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
if (!fShiftPressed && !pSelection->IsInSelectingState())
{
short sDelta = 0;
if (Message == WM_MOUSEWHEEL)
if (Message == WM_MOUSEWHEEL || Message == WM_MOUSEHWHEEL)
{
sDelta = GET_WHEEL_DELTA_WPARAM(wParam);
}

View File

@@ -127,10 +127,13 @@ public:
wch = L'!';
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
Log::Comment(NoThrowString().Format(L"MOUSEWHEEL"));
wch = L'`' + (sScrollDelta > 0 ? 0 : 1);
break;
case WM_MOUSEHWHEEL:
Log::Comment(NoThrowString().Format(L"MOUSEHWHEEL"));
wch = L'b' + (sScrollDelta > 0 ? 1 : 0);
break;
case WM_MOUSEMOVE:
default:
Log::Comment(NoThrowString().Format(L"DEFAULT"));
@@ -168,9 +171,11 @@ public:
result = 3 + 0x20; // we add 0x20 to hover events, which are all encoded as WM_MOUSEMOVE events
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
result = (sScrollDelta > 0 ? 64 : 65);
break;
case WM_MOUSEHWHEEL:
result = (sScrollDelta > 0 ? 67 : 66);
break;
default:
result = 0;
break;
@@ -582,6 +587,12 @@ public:
Log::Comment(L"Test mouse wheel scrolling down");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1B[B"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling right");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1B[C"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling left");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1B[D"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Enable cursor keys mode");
mouseInput.SetInputMode(TerminalInput::Mode::CursorKey, true);
@@ -591,6 +602,12 @@ public:
Log::Comment(L"Test mouse wheel scrolling down");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1BOB"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling right");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1BOC"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling left");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1BOD"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Confirm no effect when scroll mode is disabled");
mouseInput.UseAlternateScreenBuffer();
mouseInput.SetInputMode(TerminalInput::Mode::AlternateScroll, false);

View File

@@ -163,9 +163,11 @@ static constexpr wchar_t _windowsButtonToXEncoding(const unsigned int button,
xvalue = 1;
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x40 : 0x41;
break;
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x43 : 0x42;
break;
default:
xvalue = 0;
break;
@@ -221,9 +223,11 @@ static constexpr int _windowsButtonToSGREncoding(const unsigned int button,
xvalue = 3;
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x40 : 0x41;
break;
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x43 : 0x42;
break;
default:
xvalue = 0;
break;
@@ -378,7 +382,7 @@ TerminalInput::OutputType TerminalInput::HandleMouse(const til::point position,
if (ShouldSendAlternateScroll(button, delta))
{
return _makeAlternateScrollOutput(delta);
return _makeAlternateScrollOutput(button, delta);
}
return {};
@@ -493,14 +497,32 @@ bool TerminalInput::ShouldSendAlternateScroll(const unsigned int button, const s
// - Sends a sequence to the input corresponding to cursor up / down depending on the sScrollDelta.
// Parameters:
// - delta: The scroll wheel delta of the input event
TerminalInput::OutputType TerminalInput::_makeAlternateScrollOutput(const short delta) const
TerminalInput::OutputType TerminalInput::_makeAlternateScrollOutput(const unsigned int button, const short delta) const
{
if (delta > 0)
if (button == WM_MOUSEWHEEL)
{
return MakeOutput(_keyMap.at(VK_UP));
if (delta > 0)
{
return MakeOutput(_keyMap.at(VK_UP));
}
else
{
return MakeOutput(_keyMap.at(VK_DOWN));
}
}
else if (button == WM_MOUSEHWHEEL)
{
if (delta > 0)
{
return MakeOutput(_keyMap.at(VK_RIGHT));
}
else
{
return MakeOutput(_keyMap.at(VK_LEFT));
}
}
else
{
return MakeOutput(_keyMap.at(VK_DOWN));
return {};
}
}

View File

@@ -111,7 +111,7 @@ namespace Microsoft::Console::VirtualTerminal
[[nodiscard]] OutputType _GenerateUtf8Sequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta);
[[nodiscard]] OutputType _GenerateSGRSequence(til::point position, unsigned int button, bool isRelease, bool isHover, short modifierKeyState, short delta);
[[nodiscard]] OutputType _makeAlternateScrollOutput(short delta) const;
[[nodiscard]] OutputType _makeAlternateScrollOutput(unsigned int button, short delta) const;
static constexpr unsigned int s_GetPressedButton(const MouseButtonState state) noexcept;
#pragma endregion

View File

@@ -897,6 +897,22 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
eventFlags |= MOUSE_WHEELED;
break;
}
case CsiMouseButtonCodes::ScrollLeft:
{
// set high word to proper scroll direction
// scroll intensity is assumed to be constant value
buttonState |= SCROLL_DELTA_BACKWARD;
eventFlags |= MOUSE_HWHEELED;
break;
}
case CsiMouseButtonCodes::ScrollRight:
{
// set high word to proper scroll direction
// scroll intensity is assumed to be constant value
buttonState |= SCROLL_DELTA_FORWARD;
eventFlags |= MOUSE_HWHEELED;
break;
}
case CsiMouseButtonCodes::Released:
// hover event, we still want to send these but we don't
// need to do anything special here, so just break

View File

@@ -111,6 +111,8 @@ namespace Microsoft::Console::VirtualTerminal
Released = 3,
ScrollForward = 4,
ScrollBack = 5,
ScrollLeft = 6,
ScrollRight = 7,
};
constexpr unsigned short CsiMouseModifierCode_Drag = 32;

View File

@@ -1194,8 +1194,10 @@ void InputEngineTest::SGRMouseTest_Scroll()
// NOTE: scrolling events do NOT send a mouse up event
const std::vector<std::tuple<SGR_PARAMS, MOUSE_EVENT_PARAMS>> testData = {
// TEST INPUT EXPECTED OUTPUT
{ { CsiMouseButtonCodes::ScrollForward, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_FORWARD, 0, { 0, 0 }, MOUSE_WHEELED } },
{ { CsiMouseButtonCodes::ScrollBack, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_BACKWARD, 0, { 0, 0 }, MOUSE_WHEELED } },
{ { CsiMouseButtonCodes::ScrollForward, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_FORWARD, 0, { 0, 0 }, MOUSE_WHEELED } },
{ { CsiMouseButtonCodes::ScrollBack, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_BACKWARD, 0, { 0, 0 }, MOUSE_WHEELED } },
{ { CsiMouseButtonCodes::ScrollLeft, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_BACKWARD, 0, { 0, 0 }, MOUSE_HWHEELED } },
{ { CsiMouseButtonCodes::ScrollRight, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_FORWARD, 0, { 0, 0 }, MOUSE_HWHEELED } },
};
// clang-format on
VerifySGRMouseData(testData);