mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-18 01:39:55 +00:00
Compare commits
15 Commits
niels9001/
...
v1.24.2682
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e33bc3d137 | ||
|
|
6aae2d0b72 | ||
|
|
148a86c81c | ||
|
|
94e96ca048 | ||
|
|
b53d7df066 | ||
|
|
1194ad0343 | ||
|
|
04e1290102 | ||
|
|
f5083714c1 | ||
|
|
7642eec206 | ||
|
|
37c674dd6e | ||
|
|
3a57ba54a0 | ||
|
|
fc465bdae6 | ||
|
|
04a5dffdcd | ||
|
|
894e57769b | ||
|
|
eb206177a4 |
@@ -20,6 +20,7 @@
|
||||
"DisableAutoPackageNameFormatting": false
|
||||
},
|
||||
"appSubmission": {
|
||||
"appId": "9N8G5RFZ9XK3",
|
||||
"productId": "00014050269303149694",
|
||||
"targetPublishMode": "NotSet",
|
||||
"targetPublishDate": null,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"DisableAutoPackageNameFormatting": false
|
||||
},
|
||||
"appSubmission": {
|
||||
"appId": "9N0DX20HK701",
|
||||
"productId": "00013926773940052066",
|
||||
"targetPublishMode": "NotSet",
|
||||
"targetPublishDate": null,
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -111,6 +111,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
Released = 3,
|
||||
ScrollForward = 4,
|
||||
ScrollBack = 5,
|
||||
ScrollLeft = 6,
|
||||
ScrollRight = 7,
|
||||
};
|
||||
|
||||
constexpr unsigned short CsiMouseModifierCode_Drag = 32;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user