mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-20 05:54:23 +00:00
Compare commits
16 Commits
dev/cazamo
...
dev/lhecke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
940e8d5c2a | ||
|
|
173f897751 | ||
|
|
d875ff4d6c | ||
|
|
ad501c9d92 | ||
|
|
89d82638ab | ||
|
|
a449899789 | ||
|
|
cb88820fa6 | ||
|
|
a65bff17d8 | ||
|
|
cdf4bd9209 | ||
|
|
5d0e8c238c | ||
|
|
07792774f6 | ||
|
|
8f3d1d8d01 | ||
|
|
3ec372c176 | ||
|
|
71409f84f7 | ||
|
|
6723ca2239 | ||
|
|
c8549bebed |
2
.github/actions/spelling/expect/expect.txt
vendored
2
.github/actions/spelling/expect/expect.txt
vendored
@@ -866,6 +866,7 @@ KILLACTIVE
|
||||
KILLFOCUS
|
||||
kinda
|
||||
KIYEOK
|
||||
KKP
|
||||
KLF
|
||||
KLMNO
|
||||
KOK
|
||||
@@ -885,6 +886,7 @@ LBUTTONDOWN
|
||||
LBUTTONUP
|
||||
lcb
|
||||
lci
|
||||
LCMAP
|
||||
LCONTROL
|
||||
LCTRL
|
||||
lcx
|
||||
|
||||
@@ -28,7 +28,7 @@ extends:
|
||||
official: true
|
||||
branding: Canary
|
||||
buildTerminal: true
|
||||
pgoBuildMode: None # BODGY - OneBranch is on VS 17.10, which is known to be the worst
|
||||
pgoBuildMode: Optimize
|
||||
codeSign: true
|
||||
signingIdentity:
|
||||
serviceName: $(SigningServiceName)
|
||||
|
||||
@@ -50,6 +50,7 @@ stages:
|
||||
buildEverything: true
|
||||
pgoBuildMode: Instrument
|
||||
artifactStem: -instrumentation
|
||||
keepAllExpensiveBuildOutputs: false # the ARM64 build agent runs out of memory downloading the artifacts...
|
||||
|
||||
- stage: RunPGO
|
||||
displayName: Run PGO
|
||||
|
||||
@@ -26,6 +26,7 @@ jobs:
|
||||
pgoToolsPath: $(Build.SourcesDirectory)\build\PGO
|
||||
nuspecPath: $(pgoToolsPath)\NuSpecs
|
||||
nuspecFilename: PGO.nuspec
|
||||
nugetFeed: "https://pkgs.dev.azure.com/shine-oss/terminal/_packaging/TerminalDependencies/nuget/v3/index.json"
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
@@ -45,7 +46,8 @@ jobs:
|
||||
|
||||
- task: NuGetAuthenticate@1
|
||||
inputs:
|
||||
nuGetServiceConnections: 'Terminal Public Artifact Feed'
|
||||
azureDevOpsServiceConnection: 'Terminal Public Artifact Feed'
|
||||
feedUrl: $(nugetFeed)
|
||||
|
||||
# In the Microsoft Azure DevOps tenant, NuGetCommand is ambiguous.
|
||||
# This should be `task: NuGetCommand@2`
|
||||
@@ -68,15 +70,7 @@ jobs:
|
||||
artifact: pgo-nupkg-${{ parameters.buildConfiguration }}${{ parameters.artifactStem }}
|
||||
displayName: "Publish Pipeline Artifact"
|
||||
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
displayName: 'NuGet push'
|
||||
inputs:
|
||||
command: push
|
||||
nuGetFeedType: external
|
||||
packagesToPush: $(Build.ArtifactStagingDirectory)/*.nupkg
|
||||
# The actual URL and PAT for this feed is configured at
|
||||
# https://microsoft.visualstudio.com/Dart/_settings/adminservices
|
||||
# This is the name of that connection
|
||||
publishFeedCredentials: 'Terminal Public Artifact Feed'
|
||||
feedsToUse: config
|
||||
nugetConfigPath: '$(Build.SourcesDirectory)/NuGet.config'
|
||||
- pwsh: |-
|
||||
$nupkg = Get-Item "$(Build.ArtifactStagingDirectory)/*.nupkg"
|
||||
& nuget push -ApiKey az -Source "$(nugetFeed)" "$nupkg"
|
||||
displayName: NuGet push
|
||||
|
||||
@@ -45,6 +45,10 @@ jobs:
|
||||
displayName: Extract the unpackaged build for PGO
|
||||
|
||||
- template: steps-ensure-nuget-version.yml
|
||||
parameters:
|
||||
${{ if eq(parameters.buildPlatform, 'arm64') }}:
|
||||
# The ARM64 agents do not seem to have NuGet installed
|
||||
forceNugetInstallation: true
|
||||
|
||||
- powershell: |-
|
||||
$Package = 'Microsoft.Internal.Windows.Terminal.TestContent'
|
||||
|
||||
@@ -210,6 +210,8 @@ extends:
|
||||
ob_createvpack_taskLogVerbosity: Detailed
|
||||
ob_createvpack_verbose: true
|
||||
ob_createvpack_vpackdirectory: '$(JobOutputDirectory)\vpack'
|
||||
ob_createvpack_versionAs: string
|
||||
ob_createvpack_version: '$(XES_APPXMANIFESTVERSION)'
|
||||
ob_updateOSManifest_gitcheckinConfigPath: '$(Build.SourcesDirectory)\build\config\GitCheckin.json'
|
||||
# We're skipping the 'fetch' part of the OneBranch rules, but that doesn't mean
|
||||
# that it doesn't expect to have downloaded a manifest directly to some 'destination'
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
parameters:
|
||||
- name: forceNugetInstallation
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
steps:
|
||||
- ${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
|
||||
- ${{ if and(ne(parameters.forceNugetInstallation, true), eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564')) }}:
|
||||
- pwsh: |-
|
||||
Write-Host "Assuming NuGet is already installed..."
|
||||
& nuget.exe help
|
||||
@@ -7,6 +12,6 @@ steps:
|
||||
|
||||
- ${{ else }}:
|
||||
- task: NuGetToolInstaller@1
|
||||
displayName: Use NuGet 6.6.1
|
||||
displayName: Use NuGet 7.0.1
|
||||
inputs:
|
||||
versionSpec: 6.6.1
|
||||
versionSpec: 7.0.1
|
||||
|
||||
@@ -600,96 +600,12 @@ namespace
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#pragma region TAEF hookup for the test case array above
|
||||
struct ArrayIndexTaefAdapterRow : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataRow>
|
||||
{
|
||||
HRESULT RuntimeClassInitialize(const size_t index)
|
||||
{
|
||||
_index = index;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
const auto indexString{ wil::str_printf<std::wstring>(L"%zu", _index) };
|
||||
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
||||
LONG index{ 0 };
|
||||
auto indexBstr{ wil::make_bstr(indexString.c_str()) };
|
||||
(void)SafeArrayPutElement(safeArray, &index, indexBstr.release());
|
||||
*ppData = safeArray;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
|
||||
{
|
||||
*ppMetadataNames = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
*ppData = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetName(BSTR* ppszRowName) override
|
||||
{
|
||||
*ppszRowName = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _index;
|
||||
};
|
||||
|
||||
struct ArrayIndexTaefAdapterSource : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataSource>
|
||||
{
|
||||
STDMETHODIMP Advance(IDataRow** ppDataRow) override
|
||||
{
|
||||
if (_index < std::extent_v<decltype(testCases)>)
|
||||
{
|
||||
Microsoft::WRL::MakeAndInitialize<ArrayIndexTaefAdapterRow>(ppDataRow, _index++);
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppDataRow = nullptr;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP Reset() override
|
||||
{
|
||||
_index = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
|
||||
{
|
||||
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
||||
LONG index{ 0 };
|
||||
auto dataNameBstr{ wil::make_bstr(L"index") };
|
||||
(void)SafeArrayPutElement(safeArray, &index, dataNameBstr.release());
|
||||
*names = safeArray;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
|
||||
{
|
||||
*type = nullptr;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _index{ 0 };
|
||||
};
|
||||
#pragma endregion
|
||||
}
|
||||
|
||||
extern "C" HRESULT __declspec(dllexport) __cdecl ReflowTestDataSource(IDataSource** ppDataSource, void*)
|
||||
{
|
||||
auto source{ Microsoft::WRL::Make<ArrayIndexTaefAdapterSource>() };
|
||||
return source.CopyTo(ppDataSource);
|
||||
*ppDataSource = new ArrayIndexTaefAdapterSource>(std::size(testCases));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
class ReflowTests
|
||||
|
||||
@@ -34,7 +34,7 @@ struct
|
||||
#else // DEV
|
||||
__declspec(uuid("52065414-e077-47ec-a3ac-1cc5455e1b54"))
|
||||
#endif
|
||||
OpenTerminalHere : public RuntimeClass<RuntimeClassFlags<ClassicCom | InhibitFtmBase>, IExplorerCommand, IObjectWithSite>
|
||||
OpenTerminalHere : public RuntimeClass<RuntimeClassFlags<ClassicCom | InhibitFtmBase>, IExplorerCommand, IObjectWithSite>
|
||||
{
|
||||
#pragma region IExplorerCommand
|
||||
STDMETHODIMP Invoke(IShellItemArray* psiItemArray,
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
|
||||
{
|
||||
public:
|
||||
explicit DebugTapConnection(Microsoft::Terminal::TerminalConnection::ITerminalConnection wrappedConnection);
|
||||
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/){};
|
||||
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) {};
|
||||
~DebugTapConnection();
|
||||
void Start();
|
||||
void WriteInput(const winrt::array_view<const char16_t> data);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace winrt::TerminalApp::implementation
|
||||
#pragma region IPaneContent
|
||||
winrt::Windows::UI::Xaml::FrameworkElement GetRoot();
|
||||
|
||||
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings&){};
|
||||
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings&) {};
|
||||
|
||||
winrt::Windows::Foundation::Size MinimumSize() { return { 1, 1 }; };
|
||||
void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic) { reason; };
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Default to unset, 0%.
|
||||
TaskbarState::TaskbarState() :
|
||||
TaskbarState(0, 0){};
|
||||
TaskbarState(0, 0) {};
|
||||
|
||||
TaskbarState::TaskbarState(const uint64_t dispatchTypesState, const uint64_t progressParam) :
|
||||
_State{ dispatchTypesState },
|
||||
|
||||
@@ -31,7 +31,7 @@ Author(s):
|
||||
using NewHandoffFunction = HRESULT (*)(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo);
|
||||
|
||||
struct __declspec(uuid(__CLSID_CTerminalHandoff))
|
||||
CTerminalHandoff : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::ClassicCom>, ITerminalHandoff3>
|
||||
CTerminalHandoff : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::ClassicCom>, ITerminalHandoff3>
|
||||
{
|
||||
#pragma region ITerminalHandoff
|
||||
STDMETHODIMP EstablishPtyHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) override;
|
||||
|
||||
@@ -1736,7 +1736,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// - resetOnly: If true, only Reset() will be called, if anything. FindNext() will never be called.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
SearchResults ControlCore::Search(SearchRequest request)
|
||||
SearchResults ControlCore::Search(const SearchRequest& request)
|
||||
{
|
||||
const auto lock = _terminal->LockForWriting();
|
||||
|
||||
@@ -1745,15 +1745,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
WI_SetFlagIf(flags, SearchFlag::RegularExpression, request.RegularExpression);
|
||||
const auto searchInvalidated = _searcher.IsStale(*_terminal.get(), request.Text, flags);
|
||||
|
||||
if (searchInvalidated || !request.ResetOnly)
|
||||
if (searchInvalidated || request.ExecuteSearch)
|
||||
{
|
||||
std::vector<til::point_span> oldResults;
|
||||
til::point_span oldFocused;
|
||||
|
||||
if (const auto focused = _terminal->GetSearchHighlightFocused())
|
||||
{
|
||||
oldFocused = *focused;
|
||||
}
|
||||
|
||||
if (searchInvalidated)
|
||||
{
|
||||
@@ -1762,18 +1756,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_terminal->SetSearchHighlights(_searcher.Results());
|
||||
}
|
||||
|
||||
if (!request.ResetOnly)
|
||||
if (request.ExecuteSearch)
|
||||
{
|
||||
_searcher.FindNext(!request.GoForward);
|
||||
}
|
||||
|
||||
_terminal->SetSearchHighlightFocused(gsl::narrow<size_t>(std::max<ptrdiff_t>(0, _searcher.CurrentMatch())));
|
||||
_renderer->TriggerSearchHighlight(oldResults);
|
||||
}
|
||||
|
||||
if (const auto focused = _terminal->GetSearchHighlightFocused(); focused && *focused != oldFocused)
|
||||
{
|
||||
_terminal->ScrollToSearchHighlight(request.ScrollOffset);
|
||||
}
|
||||
if (request.ScrollIntoView)
|
||||
{
|
||||
_terminal->ScrollToSearchHighlight(request.ScrollOffset);
|
||||
}
|
||||
|
||||
int32_t totalMatches = 0;
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void SetSelectionAnchor(const til::point position);
|
||||
void SetEndSelectionPoint(const til::point position);
|
||||
|
||||
SearchResults Search(SearchRequest request);
|
||||
SearchResults Search(const SearchRequest& request);
|
||||
const std::vector<til::point_span>& SearchResultRows() const noexcept;
|
||||
void ClearSearch();
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ namespace Microsoft.Terminal.Control
|
||||
Boolean GoForward;
|
||||
Boolean CaseSensitive;
|
||||
Boolean RegularExpression;
|
||||
Boolean ResetOnly;
|
||||
Boolean ExecuteSearch;
|
||||
Boolean ScrollIntoView;
|
||||
Int32 ScrollOffset;
|
||||
};
|
||||
|
||||
|
||||
@@ -958,7 +958,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
|
||||
for (size_t tableIndex = 0; tableIndex < 16; tableIndex++)
|
||||
{
|
||||
// It's using gsl::at to check the index is in bounds, but the analyzer still calls this array-to-pointer-decay
|
||||
GSL_SUPPRESS(bounds .3)
|
||||
GSL_SUPPRESS(bounds.3)
|
||||
renderSettings.SetColorTableEntry(tableIndex, gsl::at(theme.ColorTable, tableIndex));
|
||||
}
|
||||
|
||||
|
||||
@@ -714,8 +714,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto request = SearchRequest{ _searchBox->Text(), goForward, _searchBox->CaseSensitive(), _searchBox->RegularExpression(), false, _searchScrollOffset };
|
||||
_handleSearchResults(_core.Search(request));
|
||||
_handleSearchResults(_core.Search(SearchRequest{
|
||||
.Text = _searchBox->Text(),
|
||||
.GoForward = goForward,
|
||||
.CaseSensitive = _searchBox->CaseSensitive(),
|
||||
.RegularExpression = _searchBox->RegularExpression(),
|
||||
.ExecuteSearch = true,
|
||||
.ScrollIntoView = true,
|
||||
.ScrollOffset = _searchScrollOffset,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,8 +756,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
if (_searchBox && _searchBox->IsOpen())
|
||||
{
|
||||
const auto request = SearchRequest{ text, goForward, caseSensitive, regularExpression, false, _searchScrollOffset };
|
||||
_handleSearchResults(_core.Search(request));
|
||||
_handleSearchResults(_core.Search(SearchRequest{
|
||||
.Text = text,
|
||||
.GoForward = goForward,
|
||||
.CaseSensitive = caseSensitive,
|
||||
.RegularExpression = regularExpression,
|
||||
.ExecuteSearch = true,
|
||||
.ScrollIntoView = true,
|
||||
.ScrollOffset = _searchScrollOffset,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -769,11 +783,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
if (_searchBox && _searchBox->IsOpen())
|
||||
{
|
||||
// We only want to update the search results based on the new text. Set
|
||||
// `resetOnly` to true so we don't accidentally update the current match index.
|
||||
const auto request = SearchRequest{ text, goForward, caseSensitive, regularExpression, true, _searchScrollOffset };
|
||||
const auto result = _core.Search(request);
|
||||
_handleSearchResults(result);
|
||||
_handleSearchResults(_core.Search(SearchRequest{
|
||||
.Text = text,
|
||||
.GoForward = goForward,
|
||||
.CaseSensitive = caseSensitive,
|
||||
.RegularExpression = regularExpression,
|
||||
.ExecuteSearch = false,
|
||||
.ScrollIntoView = true,
|
||||
.ScrollOffset = _searchScrollOffset,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3742,8 +3760,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const auto goForward = _searchBox->GoForward();
|
||||
const auto caseSensitive = _searchBox->CaseSensitive();
|
||||
const auto regularExpression = _searchBox->RegularExpression();
|
||||
const auto request = SearchRequest{ text, goForward, caseSensitive, regularExpression, true, _searchScrollOffset };
|
||||
_handleSearchResults(_core.Search(request));
|
||||
_handleSearchResults(_core.Search(SearchRequest{
|
||||
.Text = text,
|
||||
.GoForward = goForward,
|
||||
.CaseSensitive = caseSensitive,
|
||||
.RegularExpression = regularExpression,
|
||||
.ExecuteSearch = false,
|
||||
.ScrollIntoView = false,
|
||||
.ScrollOffset = _searchScrollOffset,
|
||||
}));
|
||||
}
|
||||
|
||||
void TermControl::_handleSearchResults(SearchResults results)
|
||||
|
||||
@@ -121,6 +121,7 @@ namespace Microsoft.Terminal.Core
|
||||
String WordDelimiters { get; };
|
||||
|
||||
Boolean ForceVTInput { get; };
|
||||
Boolean AllowKittyKeyboardMode { get; };
|
||||
Boolean AllowVtChecksumReport { get; };
|
||||
Boolean AllowVtClipboardWrite { get; };
|
||||
Boolean TrimBlockSelection { get; };
|
||||
|
||||
@@ -98,6 +98,7 @@ void Terminal::UpdateSettings(ICoreSettings settings)
|
||||
}
|
||||
|
||||
_getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput());
|
||||
_getTerminalInput().ForceDisableKittyKeyboardProtocol(!settings.AllowKittyKeyboardMode());
|
||||
|
||||
if (settings.TabColor() == nullptr)
|
||||
{
|
||||
|
||||
@@ -349,6 +349,7 @@ namespace winrt::Microsoft::Terminal::Settings
|
||||
_ReloadEnvironmentVariables = profile.ReloadEnvironmentVariables();
|
||||
_RainbowSuggestions = profile.RainbowSuggestions();
|
||||
_ForceVTInput = profile.ForceVTInput();
|
||||
_AllowKittyKeyboardMode = profile.AllowKittyKeyboardMode();
|
||||
_AllowVtChecksumReport = profile.AllowVtChecksumReport();
|
||||
_AllowVtClipboardWrite = profile.AllowVtClipboardWrite();
|
||||
_PathTranslationStyle = profile.PathTranslationStyle();
|
||||
|
||||
@@ -220,7 +220,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
break;
|
||||
}
|
||||
}
|
||||
actionsPageVM.DeleteKeyChord(args);
|
||||
if (args)
|
||||
{
|
||||
actionsPageVM.DeleteKeyChord(args);
|
||||
}
|
||||
}
|
||||
});
|
||||
kcVM.PropertyChanged([weakThis{ get_weak() }](const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) {
|
||||
@@ -1032,6 +1035,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
// if we're in edit mode, populate the text box with the current keys
|
||||
ProposedKeys(_currentKeys);
|
||||
}
|
||||
else if (!_currentKeys)
|
||||
{
|
||||
// we have left edit mode but don't have any current keys - delete this view model
|
||||
DeleteKeyChord();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyChordViewModel::AcceptChanges()
|
||||
|
||||
330
src/cascadia/TerminalSettingsEditor/IconPicker.cpp
Normal file
330
src/cascadia/TerminalSettingsEditor/IconPicker.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "IconPicker.h"
|
||||
#include "IconPicker.g.cpp"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
#include "SegoeFluentIconList.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
#include "../WinRTUtils/inc/Utils.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::UI;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Navigation;
|
||||
using namespace winrt::Windows::UI::Xaml::Controls;
|
||||
using namespace winrt::Windows::UI::Xaml::Media;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Microsoft::UI::Xaml::Controls;
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
namespace MUX = Microsoft::UI::Xaml;
|
||||
namespace WUX = Windows::UI::Xaml;
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
static constexpr std::wstring_view HideIconValue{ L"none" };
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::_BuiltInIcons{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::_IconTypes{ nullptr };
|
||||
DependencyProperty IconPicker::_CurrentIconPathProperty{ nullptr };
|
||||
DependencyProperty IconPicker::_WindowRootProperty{ nullptr };
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::BuiltInIcons() noexcept
|
||||
{
|
||||
if (!_BuiltInIcons)
|
||||
{
|
||||
// lazy load the built-in icons
|
||||
std::vector<Editor::EnumEntry> builtInIcons;
|
||||
for (auto& [val, name] : s_SegoeFluentIcons)
|
||||
{
|
||||
builtInIcons.emplace_back(make<EnumEntry>(hstring{ name }, box_value(val)));
|
||||
}
|
||||
_BuiltInIcons = single_threaded_observable_vector<Editor::EnumEntry>(std::move(builtInIcons));
|
||||
}
|
||||
return _BuiltInIcons;
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::IconTypes() noexcept
|
||||
{
|
||||
if (!_IconTypes)
|
||||
{
|
||||
// lazy load the icon types
|
||||
std::vector<Editor::EnumEntry> iconTypes;
|
||||
iconTypes.reserve(4);
|
||||
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeNone"), box_value(IconType::None)));
|
||||
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeFontIcon"), box_value(IconType::FontIcon)));
|
||||
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeEmoji"), box_value(IconType::Emoji)));
|
||||
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeImage"), box_value(IconType::Image)));
|
||||
_IconTypes = winrt::single_threaded_observable_vector<Editor::EnumEntry>(std::move(iconTypes));
|
||||
}
|
||||
return _IconTypes;
|
||||
}
|
||||
|
||||
IconPicker::IconPicker()
|
||||
{
|
||||
_InitializeProperties();
|
||||
InitializeComponent();
|
||||
|
||||
_DeduceCurrentIconType();
|
||||
PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
|
||||
const auto propertyName{ args.PropertyName() };
|
||||
// "CurrentIconPath" changes are handled by _OnCurrentIconPathChanged()
|
||||
if (propertyName == L"CurrentIconType")
|
||||
{
|
||||
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingNoIcon" });
|
||||
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingBuiltInIcon" });
|
||||
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingEmojiIcon" });
|
||||
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingImageIcon" });
|
||||
}
|
||||
else if (propertyName == L"CurrentBuiltInIcon")
|
||||
{
|
||||
CurrentIconPath(unbox_value<hstring>(_CurrentBuiltInIcon.EnumValue()));
|
||||
}
|
||||
else if (propertyName == L"CurrentEmojiIcon")
|
||||
{
|
||||
CurrentIconPath(CurrentEmojiIcon());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IconPicker::_InitializeProperties()
|
||||
{
|
||||
// Initialize any dependency properties here.
|
||||
// This performs a lazy load on these properties, instead of
|
||||
// initializing them when the DLL loads.
|
||||
if (!_CurrentIconPathProperty)
|
||||
{
|
||||
_CurrentIconPathProperty =
|
||||
DependencyProperty::Register(
|
||||
L"CurrentIconPath",
|
||||
xaml_typename<hstring>(),
|
||||
xaml_typename<Editor::IconPicker>(),
|
||||
PropertyMetadata{ nullptr, PropertyChangedCallback{ &IconPicker::_OnCurrentIconPathChanged } });
|
||||
}
|
||||
if (!_WindowRootProperty)
|
||||
{
|
||||
_WindowRootProperty =
|
||||
DependencyProperty::Register(
|
||||
L"WindowRoot",
|
||||
xaml_typename<IHostedInWindow>(),
|
||||
xaml_typename<Editor::IconPicker>(),
|
||||
PropertyMetadata{ nullptr });
|
||||
}
|
||||
}
|
||||
|
||||
void IconPicker::_OnCurrentIconPathChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
|
||||
{
|
||||
d.as<IconPicker>()->_DeduceCurrentIconType();
|
||||
}
|
||||
|
||||
safe_void_coroutine IconPicker::Icon_Click(const IInspectable&, const RoutedEventArgs&)
|
||||
{
|
||||
auto lifetime = get_strong();
|
||||
|
||||
const auto parentHwnd{ reinterpret_cast<HWND>(WindowRoot().GetHostingWindow()) };
|
||||
auto file = co_await OpenImagePicker(parentHwnd);
|
||||
if (!file.empty())
|
||||
{
|
||||
CurrentIconPath(file);
|
||||
}
|
||||
}
|
||||
|
||||
void IconPicker::BuiltInIconPicker_GotFocus(const IInspectable& sender, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
_updateIconFilter({});
|
||||
sender.as<AutoSuggestBox>().IsSuggestionListOpen(true);
|
||||
}
|
||||
|
||||
void IconPicker::BuiltInIconPicker_QuerySubmitted(const AutoSuggestBox& /*sender*/, const AutoSuggestBoxQuerySubmittedEventArgs& e)
|
||||
{
|
||||
const auto iconEntry = unbox_value_or<Editor::EnumEntry>(e.ChosenSuggestion(), nullptr);
|
||||
if (!iconEntry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
CurrentBuiltInIcon(iconEntry);
|
||||
}
|
||||
|
||||
void IconPicker::BuiltInIconPicker_TextChanged(const AutoSuggestBox& sender, const AutoSuggestBoxTextChangedEventArgs& e)
|
||||
{
|
||||
if (e.Reason() != AutoSuggestionBoxTextChangeReason::UserInput)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::wstring_view filter{ sender.Text() };
|
||||
filter = til::trim(filter, L' ');
|
||||
_updateIconFilter(filter);
|
||||
}
|
||||
|
||||
void IconPicker::_updateIconFilter(std::wstring_view filter)
|
||||
{
|
||||
if (_iconFilter != filter)
|
||||
{
|
||||
_filteredBuiltInIcons = nullptr;
|
||||
_iconFilter = filter;
|
||||
_updateFilteredIconList();
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FilteredBuiltInIconList" });
|
||||
}
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::FilteredBuiltInIconList()
|
||||
{
|
||||
if (!_filteredBuiltInIcons)
|
||||
{
|
||||
_updateFilteredIconList();
|
||||
}
|
||||
return _filteredBuiltInIcons;
|
||||
}
|
||||
|
||||
void IconPicker::_updateFilteredIconList()
|
||||
{
|
||||
_filteredBuiltInIcons = BuiltInIcons();
|
||||
if (_iconFilter.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Find matching icons and populate the filtered list
|
||||
std::vector<Editor::EnumEntry> filtered;
|
||||
filtered.reserve(_filteredBuiltInIcons.Size());
|
||||
for (const auto& icon : _filteredBuiltInIcons)
|
||||
{
|
||||
if (til::contains_linguistic_insensitive(icon.EnumName(), _iconFilter))
|
||||
{
|
||||
filtered.emplace_back(icon);
|
||||
}
|
||||
}
|
||||
_filteredBuiltInIcons = winrt::single_threaded_observable_vector(std::move(filtered));
|
||||
}
|
||||
|
||||
void IconPicker::CurrentIconType(const Windows::Foundation::IInspectable& value)
|
||||
{
|
||||
if (_currentIconType != value)
|
||||
{
|
||||
// Switching from...
|
||||
if (_currentIconType && unbox_value<IconType>(_currentIconType.as<Editor::EnumEntry>().EnumValue()) == IconType::Image)
|
||||
{
|
||||
// Stash the current value of Icon. If the user
|
||||
// switches out of then back to IconType::Image, we want
|
||||
// the path that we display in the text box to remain unchanged.
|
||||
_lastIconPath = CurrentIconPath();
|
||||
}
|
||||
|
||||
// Set the member here instead of after setting Icon() below!
|
||||
// We have an Icon property changed handler defined for when we discard changes.
|
||||
// Inadvertently, that means that we call this setter again.
|
||||
// Setting the member here means that we early exit at the beginning of the function
|
||||
// because _currentIconType == value.
|
||||
_currentIconType = value;
|
||||
|
||||
// Switched to...
|
||||
switch (unbox_value<IconType>(value.as<Editor::EnumEntry>().EnumValue()))
|
||||
{
|
||||
case IconType::None:
|
||||
{
|
||||
CurrentIconPath(winrt::hstring{ HideIconValue });
|
||||
break;
|
||||
}
|
||||
case IconType::Image:
|
||||
{
|
||||
if (!_lastIconPath.empty())
|
||||
{
|
||||
// Conversely, if we switch to Image,
|
||||
// retrieve that saved value and apply it
|
||||
CurrentIconPath(_lastIconPath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IconType::FontIcon:
|
||||
{
|
||||
if (_CurrentBuiltInIcon)
|
||||
{
|
||||
CurrentIconPath(unbox_value<hstring>(_CurrentBuiltInIcon.EnumValue()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IconType::Emoji:
|
||||
{
|
||||
// Don't set Icon here!
|
||||
// Clear out the text box so we direct the user to use the emoji picker.
|
||||
CurrentEmojiIcon({});
|
||||
}
|
||||
}
|
||||
// We're not using the VM's Icon() setter above,
|
||||
// so notify HasIcon changed manually
|
||||
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"CurrentIconType" });
|
||||
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"HasIcon" });
|
||||
}
|
||||
}
|
||||
|
||||
bool IconPicker::UsingNoIcon() const
|
||||
{
|
||||
return _currentIconType == IconTypes().GetAt(0);
|
||||
}
|
||||
|
||||
bool IconPicker::UsingBuiltInIcon() const
|
||||
{
|
||||
return _currentIconType == IconTypes().GetAt(1);
|
||||
}
|
||||
|
||||
bool IconPicker::UsingEmojiIcon() const
|
||||
{
|
||||
return _currentIconType == IconTypes().GetAt(2);
|
||||
}
|
||||
|
||||
bool IconPicker::UsingImageIcon() const
|
||||
{
|
||||
return _currentIconType == IconTypes().GetAt(3);
|
||||
}
|
||||
|
||||
void IconPicker::_DeduceCurrentIconType()
|
||||
{
|
||||
const auto icon = CurrentIconPath();
|
||||
if (icon.empty() || icon == HideIconValue)
|
||||
{
|
||||
_currentIconType = IconTypes().GetAt(0);
|
||||
}
|
||||
else if (icon.size() == 1 && (L'\uE700' <= til::at(icon, 0) && til::at(icon, 0) <= L'\uF8B3'))
|
||||
{
|
||||
_currentIconType = IconTypes().GetAt(1);
|
||||
_DeduceCurrentBuiltInIcon();
|
||||
}
|
||||
else if (::Microsoft::Console::Utils::IsLikelyToBeEmojiOrSymbolIcon(icon))
|
||||
{
|
||||
// We already did a range check for MDL2 Assets in the previous one,
|
||||
// so if we're out of that range but still short, assume we're an emoji
|
||||
_currentIconType = IconTypes().GetAt(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentIconType = IconTypes().GetAt(3);
|
||||
}
|
||||
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"CurrentIconType" });
|
||||
}
|
||||
|
||||
void IconPicker::_DeduceCurrentBuiltInIcon()
|
||||
{
|
||||
const auto icon = CurrentIconPath();
|
||||
for (uint32_t i = 0; i < BuiltInIcons().Size(); i++)
|
||||
{
|
||||
const auto& builtIn = BuiltInIcons().GetAt(i);
|
||||
if (icon == unbox_value<hstring>(builtIn.EnumValue()))
|
||||
{
|
||||
CurrentBuiltInIcon(builtIn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
CurrentBuiltInIcon(BuiltInIcons().GetAt(0));
|
||||
}
|
||||
|
||||
WUX::Controls::IconSource IconPicker::BuiltInIconConverter(const IInspectable& iconVal)
|
||||
{
|
||||
return Microsoft::Terminal::UI::IconPathConverter::IconSourceWUX(unbox_value<hstring>(iconVal));
|
||||
}
|
||||
}
|
||||
65
src/cascadia/TerminalSettingsEditor/IconPicker.h
Normal file
65
src/cascadia/TerminalSettingsEditor/IconPicker.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IconPicker.g.h"
|
||||
#include "EnumEntry.h"
|
||||
#include "Utils.h"
|
||||
#include "cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct IconPicker : public HasScrollViewer<IconPicker>, IconPickerT<IconPicker>
|
||||
{
|
||||
public:
|
||||
IconPicker();
|
||||
|
||||
static constexpr std::wstring_view HideIconValue{ L"none" };
|
||||
static Windows::UI::Xaml::Controls::IconSource BuiltInIconConverter(const Windows::Foundation::IInspectable& iconVal);
|
||||
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> BuiltInIcons() noexcept;
|
||||
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconTypes() noexcept;
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> FilteredBuiltInIconList();
|
||||
safe_void_coroutine Icon_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void BuiltInIconPicker_GotFocus(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void BuiltInIconPicker_TextChanged(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxTextChangedEventArgs& e);
|
||||
void BuiltInIconPicker_QuerySubmitted(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxQuerySubmittedEventArgs& e);
|
||||
|
||||
Windows::Foundation::IInspectable CurrentIconType() const noexcept { return _currentIconType; }
|
||||
void CurrentIconType(const Windows::Foundation::IInspectable& value);
|
||||
|
||||
bool UsingNoIcon() const;
|
||||
bool UsingBuiltInIcon() const;
|
||||
bool UsingEmojiIcon() const;
|
||||
bool UsingImageIcon() const;
|
||||
|
||||
til::property_changed_event PropertyChanged;
|
||||
WINRT_OBSERVABLE_PROPERTY(hstring, CurrentEmojiIcon, PropertyChanged.raise);
|
||||
WINRT_OBSERVABLE_PROPERTY(Editor::EnumEntry, CurrentBuiltInIcon, PropertyChanged.raise, nullptr);
|
||||
|
||||
DEPENDENCY_PROPERTY(hstring, CurrentIconPath);
|
||||
DEPENDENCY_PROPERTY(IHostedInWindow, WindowRoot);
|
||||
|
||||
private:
|
||||
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _BuiltInIcons;
|
||||
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _IconTypes;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _filteredBuiltInIcons;
|
||||
std::wstring _iconFilter;
|
||||
Windows::Foundation::IInspectable _currentIconType{};
|
||||
winrt::hstring _lastIconPath;
|
||||
|
||||
static void _InitializeProperties();
|
||||
static void _OnCurrentIconPathChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
|
||||
|
||||
void _DeduceCurrentIconType();
|
||||
void _DeduceCurrentBuiltInIcon();
|
||||
void _updateIconFilter(std::wstring_view filter);
|
||||
void _updateFilteredIconList();
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(IconPicker);
|
||||
}
|
||||
42
src/cascadia/TerminalSettingsEditor/IconPicker.idl
Normal file
42
src/cascadia/TerminalSettingsEditor/IconPicker.idl
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "EnumEntry.idl";
|
||||
import "MainPage.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
enum IconType
|
||||
{
|
||||
None = 0,
|
||||
FontIcon,
|
||||
Image,
|
||||
Emoji
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass IconPicker : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
IconPicker();
|
||||
|
||||
IInspectable CurrentIconType;
|
||||
Windows.Foundation.Collections.IObservableVector<EnumEntry> IconTypes { get; };
|
||||
|
||||
Boolean UsingBuiltInIcon { get; };
|
||||
Boolean UsingEmojiIcon { get; };
|
||||
Boolean UsingImageIcon { get; };
|
||||
|
||||
String CurrentEmojiIcon;
|
||||
|
||||
EnumEntry CurrentBuiltInIcon;
|
||||
Windows.Foundation.Collections.IObservableVector<EnumEntry> BuiltInIcons { get; };
|
||||
Windows.Foundation.Collections.IObservableVector<EnumEntry> FilteredBuiltInIconList { get; };
|
||||
|
||||
static Windows.UI.Xaml.Controls.IconSource BuiltInIconConverter(IInspectable iconVal);
|
||||
|
||||
String CurrentIconPath;
|
||||
static Windows.UI.Xaml.DependencyProperty CurrentIconPathProperty { get; };
|
||||
|
||||
IHostedInWindow WindowRoot;
|
||||
static Windows.UI.Xaml.DependencyProperty WindowRootProperty { get; };
|
||||
}
|
||||
}
|
||||
100
src/cascadia/TerminalSettingsEditor/IconPicker.xaml
Normal file
100
src/cascadia/TerminalSettingsEditor/IconPicker.xaml
Normal file
@@ -0,0 +1,100 @@
|
||||
<!--
|
||||
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information.
|
||||
-->
|
||||
<UserControl x:Class="Microsoft.Terminal.Settings.Editor.IconPicker"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:Microsoft.Terminal.Settings.Model"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="CommonResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon Type -->
|
||||
<ComboBox x:Uid="IconPicker_IconType"
|
||||
Grid.Column="0"
|
||||
ItemsSource="{x:Bind IconTypes}"
|
||||
SelectedItem="{x:Bind CurrentIconType, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:EnumEntry">
|
||||
<TextBlock Text="{x:Bind EnumName}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<!-- Built-In Icon -->
|
||||
<AutoSuggestBox x:Uid="IconPicker_BuiltInIcon"
|
||||
Grid.Column="1"
|
||||
GotFocus="BuiltInIconPicker_GotFocus"
|
||||
ItemsSource="{x:Bind FilteredBuiltInIconList, Mode=OneWay}"
|
||||
QuerySubmitted="BuiltInIconPicker_QuerySubmitted"
|
||||
Text="{x:Bind CurrentBuiltInIcon.EnumName, Mode=OneWay}"
|
||||
TextBoxStyle="{StaticResource TextBoxSettingStyle}"
|
||||
TextChanged="BuiltInIconPicker_TextChanged"
|
||||
Visibility="{x:Bind UsingBuiltInIcon, Mode=OneWay}">
|
||||
<AutoSuggestBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:EnumEntry">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<IconSourceElement Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind local:IconPicker.BuiltInIconConverter(EnumValue), Mode=OneTime}" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{x:Bind EnumName}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</AutoSuggestBox.ItemTemplate>
|
||||
</AutoSuggestBox>
|
||||
|
||||
<!-- Image (File) Icon -->
|
||||
<TextBox x:Uid="IconPicker_ImagePathBox"
|
||||
Grid.Column="1"
|
||||
MaxWidth="Infinity"
|
||||
HorizontalAlignment="Stretch"
|
||||
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
|
||||
IsSpellCheckEnabled="False"
|
||||
Style="{StaticResource TextBoxSettingStyle}"
|
||||
Text="{x:Bind CurrentIconPath, Mode=TwoWay}"
|
||||
Visibility="{x:Bind UsingImageIcon, Mode=OneWay}" />
|
||||
<Button x:Uid="IconPicker_IconBrowse"
|
||||
Grid.Column="2"
|
||||
Margin="0"
|
||||
VerticalAlignment="Top"
|
||||
Click="Icon_Click"
|
||||
Style="{StaticResource BrowseButtonStyle}"
|
||||
Visibility="{x:Bind UsingImageIcon, Mode=OneWay}" />
|
||||
|
||||
<!-- Emoji Icon -->
|
||||
<TextBox x:Uid="IconPicker_EmojiBox"
|
||||
Grid.Column="1"
|
||||
MaxWidth="Infinity"
|
||||
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
|
||||
IsSpellCheckEnabled="False"
|
||||
Style="{StaticResource TextBoxSettingStyle}"
|
||||
Text="{x:Bind CurrentEmojiIcon, Mode=TwoWay}"
|
||||
Visibility="{x:Bind UsingEmojiIcon, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -83,7 +83,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(newTabMenuTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
}
|
||||
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
|
||||
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), winrt::make<NavigateToPageArgs>(_newTabMenuPageVM, *this));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -538,7 +538,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
else
|
||||
{
|
||||
// Navigate to the NewTabMenu page
|
||||
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
|
||||
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), winrt::make<NavigateToPageArgs>(_newTabMenuPageVM, *this));
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
}
|
||||
@@ -659,7 +659,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
_PreNavigateHelper();
|
||||
|
||||
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
|
||||
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), winrt::make<NavigateToPageArgs>(_newTabMenuPageVM, *this));
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(newTabMenuTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "MainPage.g.h"
|
||||
#include "Breadcrumb.g.h"
|
||||
#include "NavigateToPageArgs.g.h"
|
||||
#include "Utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
@@ -23,6 +24,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
WINRT_PROPERTY(BreadcrumbSubPage, SubPage);
|
||||
};
|
||||
|
||||
struct NavigateToPageArgs : NavigateToPageArgsT<NavigateToPageArgs>
|
||||
{
|
||||
public:
|
||||
NavigateToPageArgs(Windows::Foundation::IInspectable viewModel, Editor::IHostedInWindow windowRoot) :
|
||||
_ViewModel(viewModel),
|
||||
_WindowRoot(windowRoot) {}
|
||||
|
||||
Editor::IHostedInWindow WindowRoot() const noexcept { return _WindowRoot; }
|
||||
Windows::Foundation::IInspectable ViewModel() const noexcept { return _ViewModel; }
|
||||
|
||||
private:
|
||||
Editor::IHostedInWindow _WindowRoot{ nullptr };
|
||||
Windows::Foundation::IInspectable _ViewModel{ nullptr };
|
||||
};
|
||||
|
||||
struct MainPage : MainPageT<MainPage>
|
||||
{
|
||||
MainPage() = delete;
|
||||
|
||||
@@ -16,6 +16,14 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
UInt64 GetHostingWindow();
|
||||
}
|
||||
|
||||
// This is used to pass data between pages during navigation.
|
||||
// All pages will migrate to using this in GH #19519.
|
||||
runtimeclass NavigateToPageArgs
|
||||
{
|
||||
IHostedInWindow WindowRoot { get; };
|
||||
IInspectable ViewModel { get; };
|
||||
}
|
||||
|
||||
enum BreadcrumbSubPage
|
||||
{
|
||||
None = 0,
|
||||
|
||||
@@ -69,6 +69,10 @@
|
||||
<DependentUpon>NullableColorPicker.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IconPicker.h">
|
||||
<DependentUpon>IconPicker.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="EditColorScheme.h">
|
||||
<DependentUpon>EditColorScheme.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
@@ -193,6 +197,9 @@
|
||||
<Page Include="NullableColorPicker.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="IconPicker.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="EditColorScheme.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
@@ -269,6 +276,10 @@
|
||||
<DependentUpon>NullableColorPicker.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClCompile>
|
||||
<ClCompile Include="IconPicker.cpp">
|
||||
<DependentUpon>IconPicker.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClCompile>
|
||||
<ClCompile Include="EditColorScheme.cpp">
|
||||
<DependentUpon>EditColorScheme.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
@@ -397,6 +408,10 @@
|
||||
<DependentUpon>NullableColorPicker.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="IconPicker.idl">
|
||||
<DependentUpon>IconPicker.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="EditColorScheme.idl">
|
||||
<DependentUpon>EditColorScheme.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
<Page Include="AddProfile.xaml" />
|
||||
<Page Include="KeyChordListener.xaml" />
|
||||
<Page Include="NullableColorPicker.xaml" />
|
||||
<Page Include="IconPicker.xaml" />
|
||||
<Page Include="NewTabMenu.xaml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "pch.h"
|
||||
#include "NewTabMenu.h"
|
||||
#include "NewTabMenu.g.cpp"
|
||||
#include "NavigateToPageArgs.g.h"
|
||||
#include "NewTabMenuEntryTemplateSelector.g.cpp"
|
||||
#include "EnumEntry.h"
|
||||
|
||||
@@ -41,7 +42,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
void NewTabMenu::OnNavigatedTo(const NavigationEventArgs& e)
|
||||
{
|
||||
_ViewModel = e.Parameter().as<Editor::NewTabMenuViewModel>();
|
||||
const auto args = e.Parameter().as<Editor::NavigateToPageArgs>();
|
||||
_ViewModel = args.ViewModel().as<Editor::NewTabMenuViewModel>();
|
||||
_windowRoot = args.WindowRoot();
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalSettingsEditorProvider,
|
||||
|
||||
@@ -38,11 +38,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
void AddFolderNameTextBox_KeyDown(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
|
||||
void AddFolderNameTextBox_TextChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs& e);
|
||||
|
||||
Editor::IHostedInWindow WindowRoot() const noexcept { return _windowRoot; }
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
WINRT_OBSERVABLE_PROPERTY(Editor::NewTabMenuViewModel, ViewModel, _PropertyChangedHandlers, nullptr);
|
||||
|
||||
private:
|
||||
Editor::NewTabMenuEntryViewModel _draggedEntry{ nullptr };
|
||||
Editor::IHostedInWindow _windowRoot;
|
||||
|
||||
void _ScrollToEntry(const Editor::NewTabMenuEntryViewModel& entry);
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
NewTabMenu();
|
||||
NewTabMenuViewModel ViewModel { get; };
|
||||
IHostedInWindow WindowRoot { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass NewTabMenuEntryTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector
|
||||
|
||||
@@ -319,29 +319,48 @@
|
||||
Visibility="{x:Bind ViewModel.IsFolderView, Mode=OneWay}">
|
||||
<TextBlock x:Uid="NewTabMenu_CurrentFolderTextBlock"
|
||||
Style="{StaticResource TextBlockSubHeaderStyle}" />
|
||||
|
||||
<!-- TODO GH #18281: Icon -->
|
||||
<!-- Once PR #17965 merges, we can add that kind of control to set an icon -->
|
||||
|
||||
<!-- Name -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderName"
|
||||
Grid.Row="0"
|
||||
CurrentValue="{x:Bind ViewModel.CurrentFolderName, Mode=OneWay}"
|
||||
Style="{StaticResource ExpanderSettingContainerStyle}">
|
||||
<TextBox Style="{StaticResource TextBoxSettingStyle}"
|
||||
Text="{x:Bind ViewModel.CurrentFolderName, Mode=TwoWay}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Icon -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderIcon"
|
||||
CurrentValueAccessibleName="{x:Bind ViewModel.CurrentFolderLocalizedIcon, Mode=OneWay}"
|
||||
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
|
||||
<local:SettingContainer.CurrentValue>
|
||||
<Grid>
|
||||
<ContentControl Width="16"
|
||||
Height="16"
|
||||
Content="{x:Bind ViewModel.CurrentFolderIconPreview, Mode=OneWay}"
|
||||
IsTabStop="False"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.CurrentFolderUsingNoIcon), Mode=OneWay}" />
|
||||
<TextBlock Margin="0,0,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
|
||||
Style="{StaticResource SettingsPageItemDescriptionStyle}"
|
||||
Text="{x:Bind ViewModel.CurrentFolderLocalizedIcon, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.CurrentFolderUsingNoIcon, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</local:SettingContainer.CurrentValue>
|
||||
<local:SettingContainer.Content>
|
||||
<local:IconPicker CurrentIconPath="{x:Bind ViewModel.CurrentFolderIconPath, Mode=TwoWay}"
|
||||
WindowRoot="{x:Bind WindowRoot, Mode=OneWay}" />
|
||||
</local:SettingContainer.Content>
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Inlining -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderInlining"
|
||||
Grid.Row="1">
|
||||
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderInlining">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.CurrentFolderInlining, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Allow Empty -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderAllowEmpty"
|
||||
Grid.Row="2">
|
||||
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderAllowEmpty">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.CurrentFolderAllowEmpty, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "NewTabMenuViewModel.h"
|
||||
#include "IconPicker.h"
|
||||
|
||||
#include "NewTabMenuViewModel.g.cpp"
|
||||
#include "FolderTreeViewEntry.g.cpp"
|
||||
@@ -160,6 +161,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
// FolderTree needs to be updated when a folder is renamed
|
||||
_folderTreeCache = nullptr;
|
||||
}
|
||||
else if (viewModelProperty == L"Icon")
|
||||
{
|
||||
_NotifyChanges(L"CurrentFolderIconPreview", L"CurrentFolderLocalizedIcon", L"CurrentFolderIconPath", L"CurrentFolderUsingNoIcon");
|
||||
}
|
||||
}
|
||||
|
||||
hstring NewTabMenuViewModel::CurrentFolderName() const
|
||||
@@ -216,6 +221,60 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::Controls::IconElement NewTabMenuViewModel::CurrentFolderIconPreview() const
|
||||
{
|
||||
if (!_CurrentFolder)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
// IconWUX sets the icon width/height to 32 by default
|
||||
auto icon = Microsoft::Terminal::UI::IconPathConverter::IconWUX(_CurrentFolder.Icon());
|
||||
icon.Width(16);
|
||||
icon.Height(16);
|
||||
return icon;
|
||||
}
|
||||
|
||||
winrt::hstring NewTabMenuViewModel::CurrentFolderLocalizedIcon() const
|
||||
{
|
||||
if (!_CurrentFolder)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
if (CurrentFolderUsingNoIcon())
|
||||
{
|
||||
return RS_(L"IconPicker_IconTypeNone");
|
||||
}
|
||||
return _CurrentFolder.Icon(); // For display as a string
|
||||
}
|
||||
|
||||
winrt::hstring NewTabMenuViewModel::CurrentFolderIconPath() const
|
||||
{
|
||||
if (!_CurrentFolder)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return _CurrentFolder.Icon();
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::CurrentFolderIconPath(const winrt::hstring& path)
|
||||
{
|
||||
if (_CurrentFolder && _CurrentFolder.Icon() != path)
|
||||
{
|
||||
_CurrentFolder.Icon(path);
|
||||
_NotifyChanges(L"CurrentFolderIconPreview", L"CurrentFolderLocalizedIcon", L"CurrentFolderIconPath", L"CurrentFolderUsingNoIcon");
|
||||
}
|
||||
}
|
||||
|
||||
bool NewTabMenuViewModel::CurrentFolderUsingNoIcon() const noexcept
|
||||
{
|
||||
if (!_CurrentFolder)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto icon{ _CurrentFolder.Icon() };
|
||||
return icon.empty() || icon == IconPicker::HideIconValue;
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel> NewTabMenuViewModel::CurrentView() const
|
||||
{
|
||||
if (!_CurrentFolder)
|
||||
|
||||
@@ -47,6 +47,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
bool CurrentFolderAllowEmpty() const;
|
||||
void CurrentFolderAllowEmpty(bool value);
|
||||
|
||||
Windows::UI::Xaml::Controls::IconElement CurrentFolderIconPreview() const;
|
||||
winrt::hstring CurrentFolderLocalizedIcon() const;
|
||||
winrt::hstring CurrentFolderIconPath() const;
|
||||
void CurrentFolderIconPath(const winrt::hstring& path);
|
||||
bool CurrentFolderUsingNoIcon() const noexcept;
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Model::Profile> AvailableProfiles() const { return _Settings.AllProfiles(); }
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FolderTreeViewEntry> FolderTree() const;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel> CurrentView() const;
|
||||
@@ -134,6 +140,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
void Inlining(bool value);
|
||||
|
||||
hstring Icon() const { return _FolderEntry.Icon().Path(); }
|
||||
void Icon(const hstring& value)
|
||||
{
|
||||
_FolderEntry.Icon(Model::MediaResourceHelper::FromString(value));
|
||||
_NotifyChanges(L"Icon");
|
||||
}
|
||||
|
||||
GETSET_OBSERVABLE_PROJECTED_SETTING(_FolderEntry, Name);
|
||||
GETSET_OBSERVABLE_PROJECTED_SETTING(_FolderEntry, AllowEmpty);
|
||||
|
||||
@@ -41,6 +41,11 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
String ProfileMatcherCommandline;
|
||||
String AddFolderName;
|
||||
|
||||
Windows.UI.Xaml.Controls.IconElement CurrentFolderIconPreview { get; };
|
||||
String CurrentFolderLocalizedIcon { get; };
|
||||
String CurrentFolderIconPath;
|
||||
Boolean CurrentFolderUsingNoIcon { get; };
|
||||
|
||||
void RequestReorderEntry(NewTabMenuEntryViewModel vm, Boolean goingUp);
|
||||
void RequestDeleteEntry(NewTabMenuEntryViewModel vm);
|
||||
void RequestMoveEntriesToFolder(IVector<NewTabMenuEntryViewModel> entries, FolderEntryViewModel folderEntry);
|
||||
@@ -83,7 +88,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
FolderEntryViewModel(Microsoft.Terminal.Settings.Model.FolderEntry folderEntry, Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
|
||||
|
||||
String Name;
|
||||
String Icon { get; };
|
||||
String Icon;
|
||||
Boolean Inlining;
|
||||
Boolean AllowEmpty;
|
||||
IObservableVector<Microsoft.Terminal.Settings.Editor.NewTabMenuEntryViewModel> Entries;
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
#include "pch.h"
|
||||
#include "ProfileViewModel.h"
|
||||
#include "ProfileViewModel.g.cpp"
|
||||
#include "EnumEntry.h"
|
||||
#include "Appearances.h"
|
||||
#include "EnumEntry.h"
|
||||
#include "IconPicker.h"
|
||||
|
||||
#include "../WinRTUtils/inc/Utils.h"
|
||||
#include "../../renderer/base/FontCache.h"
|
||||
#include "../TerminalSettingsAppAdapterLib/TerminalSettings.h"
|
||||
#include "SegoeFluentIconList.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
using namespace winrt::Windows::UI::Text;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
@@ -28,9 +27,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_MonospaceFontList{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_FontList{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> ProfileViewModel::_BuiltInIcons{ nullptr };
|
||||
|
||||
static constexpr std::wstring_view HideIconValue{ L"none" };
|
||||
|
||||
ProfileViewModel::ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& appSettings, const Windows::UI::Core::CoreDispatcher& dispatcher) :
|
||||
_profile{ profile },
|
||||
@@ -47,17 +43,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
_InitializeCurrentBellSounds();
|
||||
|
||||
// set up IconTypes
|
||||
std::vector<IInspectable> iconTypes;
|
||||
iconTypes.reserve(4);
|
||||
iconTypes.emplace_back(make<EnumEntry>(RS_(L"Profile_IconTypeNone"), box_value(IconType::None)));
|
||||
iconTypes.emplace_back(make<EnumEntry>(RS_(L"Profile_IconTypeFontIcon"), box_value(IconType::FontIcon)));
|
||||
iconTypes.emplace_back(make<EnumEntry>(RS_(L"Profile_IconTypeEmoji"), box_value(IconType::Emoji)));
|
||||
iconTypes.emplace_back(make<EnumEntry>(RS_(L"Profile_IconTypeImage"), box_value(IconType::Image)));
|
||||
_IconTypes = winrt::single_threaded_vector<IInspectable>(std::move(iconTypes));
|
||||
_DeduceCurrentIconType();
|
||||
_DeduceCurrentBuiltInIcon();
|
||||
|
||||
// Add a property changed handler to our own property changed event.
|
||||
// This propagates changes from the settings model to anybody listening to our
|
||||
// unique view model members.
|
||||
@@ -92,32 +77,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
else if (viewModelProperty == L"Icon")
|
||||
{
|
||||
// _DeduceCurrentIconType() ends with a "CurrentIconType" notification
|
||||
// so we don't need to call _UpdateIconPreview() here
|
||||
_DeduceCurrentIconType();
|
||||
// The icon changed; let's re-evaluate it with its new context.
|
||||
_appSettings.ResolveMediaResources();
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentIconType")
|
||||
{
|
||||
// "Using*" handles the visibility of the IconType-related UI.
|
||||
// The others propagate the rendered icon into a preview (i.e. nav view, container item)
|
||||
_NotifyChanges(L"UsingNoIcon",
|
||||
L"UsingBuiltInIcon",
|
||||
L"UsingEmojiIcon",
|
||||
L"UsingImageIcon",
|
||||
L"LocalizedIcon",
|
||||
|
||||
// Propagate the rendered icon into a preview (i.e. nav view, container item)
|
||||
_NotifyChanges(L"LocalizedIcon",
|
||||
L"IconPreview",
|
||||
L"IconPath",
|
||||
L"EvaluatedIcon");
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentBuiltInIcon")
|
||||
{
|
||||
IconPath(unbox_value<hstring>(_CurrentBuiltInIcon.EnumValue()));
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentEmojiIcon")
|
||||
{
|
||||
IconPath(CurrentEmojiIcon());
|
||||
L"EvaluatedIcon",
|
||||
L"UsingNoIcon");
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentBellSounds")
|
||||
{
|
||||
@@ -190,61 +158,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_defaultAppearanceViewModel.IsDefault(true);
|
||||
}
|
||||
|
||||
void ProfileViewModel::_UpdateBuiltInIcons()
|
||||
{
|
||||
std::vector<Editor::EnumEntry> builtInIcons;
|
||||
for (auto& [val, name] : s_SegoeFluentIcons)
|
||||
{
|
||||
builtInIcons.emplace_back(make<EnumEntry>(hstring{ name }, box_value(val)));
|
||||
}
|
||||
_BuiltInIcons = single_threaded_observable_vector<Editor::EnumEntry>(std::move(builtInIcons));
|
||||
}
|
||||
|
||||
void ProfileViewModel::_DeduceCurrentIconType()
|
||||
{
|
||||
const auto profileIcon = IconPath();
|
||||
if (profileIcon == HideIconValue)
|
||||
{
|
||||
_currentIconType = _IconTypes.GetAt(0);
|
||||
}
|
||||
else if (profileIcon.size() == 1 && (L'\uE700' <= til::at(profileIcon, 0) && til::at(profileIcon, 0) <= L'\uF8B3'))
|
||||
{
|
||||
_currentIconType = _IconTypes.GetAt(1);
|
||||
_DeduceCurrentBuiltInIcon();
|
||||
}
|
||||
else if (::Microsoft::Console::Utils::IsLikelyToBeEmojiOrSymbolIcon(profileIcon))
|
||||
{
|
||||
// We already did a range check for MDL2 Assets in the previous one,
|
||||
// so if we're out of that range but still short, assume we're an emoji
|
||||
_currentIconType = _IconTypes.GetAt(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentIconType = _IconTypes.GetAt(3);
|
||||
}
|
||||
_NotifyChanges(L"CurrentIconType");
|
||||
}
|
||||
|
||||
void ProfileViewModel::_DeduceCurrentBuiltInIcon()
|
||||
{
|
||||
if (!_BuiltInIcons)
|
||||
{
|
||||
_UpdateBuiltInIcons();
|
||||
}
|
||||
const auto profileIcon = IconPath();
|
||||
for (uint32_t i = 0; i < _BuiltInIcons.Size(); i++)
|
||||
{
|
||||
const auto& builtIn = _BuiltInIcons.GetAt(i);
|
||||
if (profileIcon == unbox_value<hstring>(builtIn.EnumValue()))
|
||||
{
|
||||
_CurrentBuiltInIcon = builtIn;
|
||||
return;
|
||||
}
|
||||
}
|
||||
_CurrentBuiltInIcon = _BuiltInIcons.GetAt(0);
|
||||
_NotifyChanges(L"CurrentBuiltInIcon");
|
||||
}
|
||||
|
||||
void ProfileViewModel::LeftPadding(double value) noexcept
|
||||
{
|
||||
if (std::abs(_parsedPadding.Left - value) >= .0001)
|
||||
@@ -636,9 +549,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
winrt::hstring ProfileViewModel::LocalizedIcon() const
|
||||
{
|
||||
if (_currentIconType && unbox_value<IconType>(_currentIconType.as<Editor::EnumEntry>().EnumValue()) == IconType::None)
|
||||
if (UsingNoIcon())
|
||||
{
|
||||
return RS_(L"Profile_IconTypeNone");
|
||||
return RS_(L"IconPicker_IconTypeNone");
|
||||
}
|
||||
return IconPath(); // For display as a string
|
||||
}
|
||||
@@ -652,83 +565,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
return icon;
|
||||
}
|
||||
|
||||
void ProfileViewModel::CurrentIconType(const Windows::Foundation::IInspectable& value)
|
||||
bool ProfileViewModel::UsingNoIcon() const noexcept
|
||||
{
|
||||
if (_currentIconType != value)
|
||||
{
|
||||
// Switching from...
|
||||
if (_currentIconType && unbox_value<IconType>(_currentIconType.as<Editor::EnumEntry>().EnumValue()) == IconType::Image)
|
||||
{
|
||||
// Stash the current value of Icon. If the user
|
||||
// switches out of then back to IconType::Image, we want
|
||||
// the path that we display in the text box to remain unchanged.
|
||||
_lastIconPath = IconPath();
|
||||
}
|
||||
|
||||
// Set the member here instead of after setting Icon() below!
|
||||
// We have an Icon property changed handler defined for when we discard changes.
|
||||
// Inadvertently, that means that we call this setter again.
|
||||
// Setting the member here means that we early exit at the beginning of the function
|
||||
// because _currentIconType == value.
|
||||
_currentIconType = value;
|
||||
|
||||
// Switched to...
|
||||
switch (unbox_value<IconType>(value.as<Editor::EnumEntry>().EnumValue()))
|
||||
{
|
||||
case IconType::None:
|
||||
{
|
||||
IconPath(winrt::hstring{ HideIconValue });
|
||||
break;
|
||||
}
|
||||
case IconType::Image:
|
||||
{
|
||||
if (!_lastIconPath.empty())
|
||||
{
|
||||
// Conversely, if we switch to Image,
|
||||
// retrieve that saved value and apply it
|
||||
IconPath(_lastIconPath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IconType::FontIcon:
|
||||
{
|
||||
if (_CurrentBuiltInIcon)
|
||||
{
|
||||
IconPath(unbox_value<hstring>(_CurrentBuiltInIcon.EnumValue()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IconType::Emoji:
|
||||
{
|
||||
// Don't set Icon here!
|
||||
// Clear out the text box so we direct the user to use the emoji picker.
|
||||
CurrentEmojiIcon({});
|
||||
}
|
||||
}
|
||||
// We're not using the VM's Icon() setter above,
|
||||
// so notify HasIcon changed manually
|
||||
_NotifyChanges(L"CurrentIconType", L"HasIcon");
|
||||
}
|
||||
}
|
||||
|
||||
bool ProfileViewModel::UsingNoIcon() const
|
||||
{
|
||||
return _currentIconType == _IconTypes.GetAt(0);
|
||||
}
|
||||
|
||||
bool ProfileViewModel::UsingBuiltInIcon() const
|
||||
{
|
||||
return _currentIconType == _IconTypes.GetAt(1);
|
||||
}
|
||||
|
||||
bool ProfileViewModel::UsingEmojiIcon() const
|
||||
{
|
||||
return _currentIconType == _IconTypes.GetAt(2);
|
||||
}
|
||||
|
||||
bool ProfileViewModel::UsingImageIcon() const
|
||||
{
|
||||
return _currentIconType == _IconTypes.GetAt(3);
|
||||
const auto iconPath{ IconPath() };
|
||||
return iconPath.empty() || iconPath == IconPicker::HideIconValue;
|
||||
}
|
||||
|
||||
hstring ProfileViewModel::BellStylePreview() const
|
||||
|
||||
@@ -49,7 +49,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
static void UpdateFontList() noexcept;
|
||||
static Windows::Foundation::Collections::IObservableVector<Editor::Font> CompleteFontList() noexcept { return _FontList; };
|
||||
static Windows::Foundation::Collections::IObservableVector<Editor::Font> MonospaceFontList() noexcept { return _MonospaceFontList; };
|
||||
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> BuiltInIcons() noexcept { return _BuiltInIcons; };
|
||||
|
||||
ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& settings, const Windows::UI::Core::CoreDispatcher& dispatcher);
|
||||
Control::IControlSettings TermSettings() const;
|
||||
@@ -86,23 +85,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
return _profile.Icon().Resolved();
|
||||
}
|
||||
Windows::Foundation::IInspectable CurrentIconType() const noexcept
|
||||
{
|
||||
return _currentIconType;
|
||||
}
|
||||
Windows::UI::Xaml::Controls::IconElement IconPreview() const;
|
||||
winrt::hstring LocalizedIcon() const;
|
||||
void CurrentIconType(const Windows::Foundation::IInspectable& value);
|
||||
bool UsingNoIcon() const;
|
||||
bool UsingBuiltInIcon() const;
|
||||
bool UsingEmojiIcon() const;
|
||||
bool UsingImageIcon() const;
|
||||
winrt::hstring IconPath() const { return _profile.Icon().Path(); }
|
||||
void IconPath(const winrt::hstring& path)
|
||||
{
|
||||
Icon(Model::MediaResourceHelper::FromString(path));
|
||||
_NotifyChanges(L"Icon", L"IconPath");
|
||||
}
|
||||
bool UsingNoIcon() const noexcept;
|
||||
|
||||
// starting directory
|
||||
hstring CurrentStartingDirectoryPreview() const;
|
||||
@@ -134,8 +125,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(ProfileSubPage, CurrentPage);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::BellSoundViewModel>, CurrentBellSounds);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::EnumEntry, CurrentBuiltInIcon, nullptr);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentEmojiIcon);
|
||||
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid);
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, ConnectionType);
|
||||
@@ -166,6 +155,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, AutoMarkPrompts);
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, RepositionCursorWithMouse);
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, ForceVTInput);
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, AllowKittyKeyboardMode);
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, AllowVtChecksumReport);
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, AllowVtClipboardWrite);
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, AnswerbackMessage);
|
||||
@@ -174,7 +164,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
WINRT_PROPERTY(bool, IsBaseLayer, false);
|
||||
WINRT_PROPERTY(bool, FocusDeleteButton, false);
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable>, IconTypes);
|
||||
GETSET_BINDABLE_ENUM_SETTING(AntiAliasingMode, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode);
|
||||
GETSET_BINDABLE_ENUM_SETTING(CloseOnExitMode, Microsoft::Terminal::Settings::Model::CloseOnExitMode, CloseOnExit);
|
||||
GETSET_BINDABLE_ENUM_SETTING(ScrollState, Microsoft::Terminal::Control::ScrollbarState, ScrollState);
|
||||
@@ -185,8 +174,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
winrt::guid _originalProfileGuid{};
|
||||
winrt::hstring _lastBgImagePath;
|
||||
winrt::hstring _lastStartingDirectoryPath;
|
||||
winrt::hstring _lastIconPath;
|
||||
Windows::Foundation::IInspectable _currentIconType{};
|
||||
Editor::AppearanceViewModel _defaultAppearanceViewModel;
|
||||
Windows::UI::Core::CoreDispatcher _dispatcher;
|
||||
|
||||
@@ -197,13 +184,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
void _MarkDuplicateBellSoundDirectories();
|
||||
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _MonospaceFontList;
|
||||
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _FontList;
|
||||
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _BuiltInIcons;
|
||||
|
||||
Model::CascadiaSettings _appSettings;
|
||||
Editor::AppearanceViewModel _unfocusedAppearanceViewModel;
|
||||
void _UpdateBuiltInIcons();
|
||||
void _DeduceCurrentIconType();
|
||||
void _DeduceCurrentBuiltInIcon();
|
||||
};
|
||||
|
||||
struct DeleteProfileEventArgs :
|
||||
|
||||
@@ -42,14 +42,6 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
Advanced = 3
|
||||
};
|
||||
|
||||
enum IconType
|
||||
{
|
||||
None = 0,
|
||||
FontIcon,
|
||||
Image,
|
||||
Emoji
|
||||
};
|
||||
|
||||
runtimeclass ProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
event Windows.Foundation.TypedEventHandler<ProfileViewModel, DeleteProfileEventArgs> DeleteProfileRequested;
|
||||
@@ -107,17 +99,8 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
Windows.UI.Xaml.Controls.IconElement IconPreview { get; };
|
||||
String EvaluatedIcon { get; };
|
||||
String LocalizedIcon { get; };
|
||||
String CurrentEmojiIcon;
|
||||
IInspectable CurrentIconType;
|
||||
Windows.Foundation.Collections.IVector<IInspectable> IconTypes { get; };
|
||||
Boolean UsingNoIcon { get; };
|
||||
Boolean UsingBuiltInIcon { get; };
|
||||
Boolean UsingEmojiIcon { get; };
|
||||
Boolean UsingImageIcon { get; };
|
||||
String IconPath;
|
||||
|
||||
EnumEntry CurrentBuiltInIcon;
|
||||
Windows.Foundation.Collections.IObservableVector<EnumEntry> BuiltInIcons { get; };
|
||||
Boolean UsingNoIcon { get; };
|
||||
|
||||
String TabTitlePreview { get; };
|
||||
String AnswerbackMessagePreview { get; };
|
||||
@@ -157,6 +140,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AutoMarkPrompts);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RepositionCursorWithMouse);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, ForceVTInput);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowKittyKeyboardMode);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowVtChecksumReport);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(String, AnswerbackMessage);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RainbowSuggestions);
|
||||
|
||||
@@ -133,18 +133,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
}
|
||||
|
||||
safe_void_coroutine Profiles_Base::Icon_Click(const IInspectable&, const RoutedEventArgs&)
|
||||
{
|
||||
auto lifetime = get_strong();
|
||||
|
||||
const auto parentHwnd{ reinterpret_cast<HWND>(_windowRoot.GetHostingWindow()) };
|
||||
auto file = co_await OpenImagePicker(parentHwnd);
|
||||
if (!file.empty())
|
||||
{
|
||||
_Profile.IconPath(file);
|
||||
}
|
||||
}
|
||||
|
||||
safe_void_coroutine Profiles_Base::StartingDirectory_Click(const IInspectable&, const RoutedEventArgs&)
|
||||
{
|
||||
auto lifetime = get_strong();
|
||||
@@ -169,77 +157,4 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_Profile.StartingDirectory(folder);
|
||||
}
|
||||
}
|
||||
|
||||
IconSource Profiles_Base::BuiltInIconConverter(const IInspectable& iconVal)
|
||||
{
|
||||
return Microsoft::Terminal::UI::IconPathConverter::IconSourceWUX(unbox_value<hstring>(iconVal));
|
||||
}
|
||||
|
||||
void Profiles_Base::BuiltInIconPicker_GotFocus(const IInspectable& sender, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
_updateIconFilter({});
|
||||
sender.as<AutoSuggestBox>().IsSuggestionListOpen(true);
|
||||
}
|
||||
|
||||
void Profiles_Base::BuiltInIconPicker_QuerySubmitted(const AutoSuggestBox& /*sender*/, const AutoSuggestBoxQuerySubmittedEventArgs& e)
|
||||
{
|
||||
const auto iconEntry = unbox_value_or<EnumEntry>(e.ChosenSuggestion(), nullptr);
|
||||
if (!iconEntry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_Profile.CurrentBuiltInIcon(iconEntry);
|
||||
}
|
||||
|
||||
void Profiles_Base::BuiltInIconPicker_TextChanged(const AutoSuggestBox& sender, const AutoSuggestBoxTextChangedEventArgs& e)
|
||||
{
|
||||
if (e.Reason() != AutoSuggestionBoxTextChangeReason::UserInput)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::wstring_view filter{ sender.Text() };
|
||||
filter = til::trim(filter, L' ');
|
||||
_updateIconFilter(filter);
|
||||
}
|
||||
|
||||
void Profiles_Base::_updateIconFilter(std::wstring_view filter)
|
||||
{
|
||||
if (_iconFilter != filter)
|
||||
{
|
||||
_filteredBuiltInIcons = nullptr;
|
||||
_iconFilter = filter;
|
||||
_updateFilteredIconList();
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FilteredBuiltInIconList" });
|
||||
}
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> Profiles_Base::FilteredBuiltInIconList()
|
||||
{
|
||||
if (!_filteredBuiltInIcons)
|
||||
{
|
||||
_updateFilteredIconList();
|
||||
}
|
||||
return _filteredBuiltInIcons;
|
||||
}
|
||||
|
||||
void Profiles_Base::_updateFilteredIconList()
|
||||
{
|
||||
_filteredBuiltInIcons = ProfileViewModel::BuiltInIcons();
|
||||
if (_iconFilter.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Find matching icons and populate the filtered list
|
||||
std::vector<Editor::EnumEntry> filtered;
|
||||
filtered.reserve(_filteredBuiltInIcons.Size());
|
||||
for (const auto& icon : _filteredBuiltInIcons)
|
||||
{
|
||||
if (til::contains_linguistic_insensitive(icon.EnumName(), _iconFilter))
|
||||
{
|
||||
filtered.emplace_back(icon);
|
||||
}
|
||||
}
|
||||
_filteredBuiltInIcons = winrt::single_threaded_observable_vector(std::move(filtered));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,32 +18,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
void OnNavigatedFrom(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
|
||||
|
||||
safe_void_coroutine StartingDirectory_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
safe_void_coroutine Icon_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
safe_void_coroutine Commandline_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void Appearance_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void Terminal_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void Advanced_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void DeleteConfirmation_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> FilteredBuiltInIconList();
|
||||
void BuiltInIconPicker_GotFocus(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void BuiltInIconPicker_TextChanged(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxTextChangedEventArgs& e);
|
||||
void BuiltInIconPicker_QuerySubmitted(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxQuerySubmittedEventArgs& e);
|
||||
|
||||
static Windows::UI::Xaml::Controls::IconSource BuiltInIconConverter(const Windows::Foundation::IInspectable& iconVal);
|
||||
|
||||
til::property_changed_event PropertyChanged;
|
||||
Editor::IHostedInWindow WindowRoot() const noexcept { return _windowRoot; }
|
||||
WINRT_PROPERTY(Editor::ProfileViewModel, Profile, nullptr);
|
||||
|
||||
private:
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker;
|
||||
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
Editor::IHostedInWindow _windowRoot;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _filteredBuiltInIcons;
|
||||
std::wstring _iconFilter;
|
||||
|
||||
void _updateIconFilter(std::wstring_view filter);
|
||||
void _updateFilteredIconList();
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "ProfileViewModel.idl";
|
||||
import "MainPage.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
@@ -9,8 +10,6 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
Profiles_Base();
|
||||
ProfileViewModel Profile { get; };
|
||||
Windows.Foundation.Collections.IObservableVector<EnumEntry> FilteredBuiltInIconList { get; };
|
||||
|
||||
static Windows.UI.Xaml.Controls.IconSource BuiltInIconConverter(IInspectable iconVal);
|
||||
IHostedInWindow WindowRoot { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,82 +122,8 @@
|
||||
</Grid>
|
||||
</local:SettingContainer.CurrentValue>
|
||||
<local:SettingContainer.Content>
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon Type -->
|
||||
<ComboBox x:Uid="Profile_IconType"
|
||||
Grid.Column="0"
|
||||
ItemsSource="{x:Bind Profile.IconTypes}"
|
||||
SelectedItem="{x:Bind Profile.CurrentIconType, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:EnumEntry">
|
||||
<TextBlock Text="{x:Bind EnumName}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<!-- Built-In Icon -->
|
||||
<AutoSuggestBox x:Uid="Profile_BuiltInIcon"
|
||||
Grid.Column="1"
|
||||
GotFocus="BuiltInIconPicker_GotFocus"
|
||||
ItemsSource="{x:Bind FilteredBuiltInIconList, Mode=OneWay}"
|
||||
QuerySubmitted="BuiltInIconPicker_QuerySubmitted"
|
||||
Text="{x:Bind Profile.CurrentBuiltInIcon.EnumName, Mode=OneWay}"
|
||||
TextBoxStyle="{StaticResource TextBoxSettingStyle}"
|
||||
TextChanged="BuiltInIconPicker_TextChanged"
|
||||
Visibility="{x:Bind Profile.UsingBuiltInIcon, Mode=OneWay}">
|
||||
<AutoSuggestBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:EnumEntry">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<IconSourceElement Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind local:Profiles_Base.BuiltInIconConverter(EnumValue), Mode=OneTime}" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{x:Bind EnumName}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</AutoSuggestBox.ItemTemplate>
|
||||
</AutoSuggestBox>
|
||||
|
||||
<!-- Image (File) Icon -->
|
||||
<TextBox x:Uid="Profile_IconBox"
|
||||
Grid.Column="1"
|
||||
MaxWidth="Infinity"
|
||||
HorizontalAlignment="Stretch"
|
||||
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
|
||||
IsSpellCheckEnabled="False"
|
||||
Style="{StaticResource TextBoxSettingStyle}"
|
||||
Text="{x:Bind Profile.IconPath, Mode=TwoWay}"
|
||||
Visibility="{x:Bind Profile.UsingImageIcon, Mode=OneWay}" />
|
||||
<Button x:Uid="Profile_IconBrowse"
|
||||
Grid.Column="2"
|
||||
Margin="0"
|
||||
VerticalAlignment="Top"
|
||||
Click="Icon_Click"
|
||||
Style="{StaticResource BrowseButtonStyle}"
|
||||
Visibility="{x:Bind Profile.UsingImageIcon, Mode=OneWay}" />
|
||||
|
||||
<!-- Emoji Icon -->
|
||||
<TextBox x:Uid="Profile_IconEmojiBox"
|
||||
Grid.Column="1"
|
||||
MaxWidth="Infinity"
|
||||
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
|
||||
IsSpellCheckEnabled="False"
|
||||
Style="{StaticResource TextBoxSettingStyle}"
|
||||
Text="{x:Bind Profile.CurrentEmojiIcon, Mode=TwoWay}"
|
||||
Visibility="{x:Bind Profile.UsingEmojiIcon, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<local:IconPicker CurrentIconPath="{x:Bind Profile.IconPath, Mode=TwoWay}"
|
||||
WindowRoot="{x:Bind WindowRoot, Mode=OneWay}" />
|
||||
</local:SettingContainer.Content>
|
||||
</local:SettingContainer>
|
||||
|
||||
|
||||
@@ -49,6 +49,15 @@
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Kitty Keyboard Mode -->
|
||||
<local:SettingContainer x:Uid="Profile_AllowKittyKeyboardMode"
|
||||
ClearSettingValue="{x:Bind Profile.ClearAllowKittyKeyboardMode}"
|
||||
HasSettingValue="{x:Bind Profile.HasAllowKittyKeyboardMode, Mode=OneWay}"
|
||||
SettingOverrideSource="{x:Bind Profile.AllowKittyKeyboardModeOverrideSource, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind Profile.AllowKittyKeyboardMode, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Allow VT Checksum Report -->
|
||||
<local:SettingContainer x:Uid="Profile_AllowVtChecksumReport"
|
||||
ClearSettingValue="{x:Bind Profile.ClearAllowVtChecksumReport}"
|
||||
|
||||
@@ -546,6 +546,14 @@
|
||||
<value>Use the legacy input encoding</value>
|
||||
<comment>Header for a control to toggle legacy input encoding for the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_AllowKittyKeyboardMode.Header" xml:space="preserve">
|
||||
<value>Kitty keyboard protocol mode</value>
|
||||
<comment>Header for a control to set the kitty keyboard protocol mode.</comment>
|
||||
</data>
|
||||
<data name="Profile_AllowKittyKeyboardMode.HelpText" xml:space="preserve">
|
||||
<value>Sets the baseline flags for the kitty keyboard protocol. Value is a sum of: 1=Disambiguate, 2=Report event types, 4=Report alternate keys, 8=Report all keys, 16=Report text.</value>
|
||||
<comment>Additional description for what the "kitty keyboard mode" setting does.</comment>
|
||||
</data>
|
||||
<data name="Profile_AllowVtChecksumReport.Header" xml:space="preserve">
|
||||
<value>Allow DECRQCRA (Request Checksum of Rectangular Area)</value>
|
||||
<comment>{Locked="DECRQCRA"}{Locked="Request Checksum of Rectangular Area"}Header for a control to toggle support for the DECRQCRA control sequence.</comment>
|
||||
@@ -1110,11 +1118,11 @@
|
||||
<value>Icon</value>
|
||||
<comment>Header for a control to determine what icon can be used to represent this profile. This is not necessarily a file path, but can be one.</comment>
|
||||
</data>
|
||||
<data name="Profile_IconBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<data name="IconPicker_ImagePathBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Icon</value>
|
||||
<comment>Name for a control to determine what icon can be used to represent this profile. This is not necessarily a file path, but can be one.</comment>
|
||||
<comment>Name for a control to determine what icon can be used. This is not necessarily a file path, but can be one. It's usually used for images.</comment>
|
||||
</data>
|
||||
<data name="Profile_IconEmojiBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<data name="IconPicker_EmojiBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Icon</value>
|
||||
<comment>Name for a control to determine what icon can be used to represent this profile.</comment>
|
||||
</data>
|
||||
@@ -1122,7 +1130,7 @@
|
||||
<value>Emoji or image file location of the icon used in the profile.</value>
|
||||
<comment>A description for what the "icon" setting does. Presented near "Profile_Icon".</comment>
|
||||
</data>
|
||||
<data name="Profile_IconBrowse.Content" xml:space="preserve">
|
||||
<data name="IconPicker_IconBrowse.Content" xml:space="preserve">
|
||||
<value>Browse...</value>
|
||||
<comment>Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window.</comment>
|
||||
</data>
|
||||
@@ -2318,31 +2326,31 @@
|
||||
<value>Use theme color</value>
|
||||
<comment>Label for a button directing the user to use the tab color defined in the terminal's current theme.</comment>
|
||||
</data>
|
||||
<data name="Profile_IconTypeNone" xml:space="preserve">
|
||||
<data name="IconPicker_IconTypeNone" xml:space="preserve">
|
||||
<value>None</value>
|
||||
<comment>An option to choose from for the "icon style" dropdown. When selected, there will be no icon for the profile.</comment>
|
||||
<comment>An option to choose from for the "icon style" dropdown. When selected, there will be no icon set.</comment>
|
||||
</data>
|
||||
<data name="Profile_IconTypeImage" xml:space="preserve">
|
||||
<data name="IconPicker_IconTypeImage" xml:space="preserve">
|
||||
<value>File</value>
|
||||
<comment>An option to choose from for the "icon style" dropdown. When selected, a custom image can set for the profile's icon.</comment>
|
||||
<comment>An option to choose from for the "icon style" dropdown. When selected, a custom image can set as the icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_IconTypeEmoji" xml:space="preserve">
|
||||
<data name="IconPicker_IconTypeEmoji" xml:space="preserve">
|
||||
<value>Emoji</value>
|
||||
<comment>An option to choose from for the "icon style" dropdown. When selected, an emoji can be set for the profile's icon.</comment>
|
||||
<comment>An option to choose from for the "icon style" dropdown. When selected, an emoji can be set as the icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_IconTypeFontIcon" xml:space="preserve">
|
||||
<data name="IconPicker_IconTypeFontIcon" xml:space="preserve">
|
||||
<value>Built-in Icon</value>
|
||||
<comment>An option to choose from for the "icon style" dropdown. When selected, the user can choose from several preselected options to set the profile's icon.</comment>
|
||||
<comment>An option to choose from for the "icon style" dropdown. When selected, the user can choose from several preselected options to set as the icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_IconEmojiBox.PlaceholderText" xml:space="preserve">
|
||||
<data name="IconPicker_EmojiBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Use "Win + period" to open the emoji picker</value>
|
||||
<comment>"Win + period" refers to the OS key binding to open the emoji picker.</comment>
|
||||
</data>
|
||||
<data name="Profile_IconType.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<data name="IconPicker_IconType.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Icon type</value>
|
||||
<comment>Accessible name for a control allowing the user to select the type of icon they would like to use.</comment>
|
||||
</data>
|
||||
<data name="Profile_BuiltInIcon.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<data name="IconPicker_BuiltInIcon.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Icon</value>
|
||||
<comment>Accessible name for a control allowing the user to select the icon from a list of built in icons.</comment>
|
||||
</data>
|
||||
@@ -2446,6 +2454,14 @@
|
||||
<value>Folder Name</value>
|
||||
<comment>Header for a control that allows the user to modify the name of the current folder entry.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_CurrentFolderIcon.Header" xml:space="preserve">
|
||||
<value>Folder Icon</value>
|
||||
<comment>Header for a control that allows the user to modify the icon of the current folder entry.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_CurrentFolderIcon.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Folder Icon</value>
|
||||
<comment>Name for a control to that allows the user to modify the icon of the current folder entry.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_CurrentFolderInlining.Header" xml:space="preserve">
|
||||
<value>Allow inlining</value>
|
||||
<comment>Header for a control that allows the nested entries to be presented inline rather than with a folder.</comment>
|
||||
@@ -2575,19 +2591,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2701,4 +2717,8 @@
|
||||
<data name="Settings_ResetApplicationStateConfirmationButton.Content" xml:space="preserve">
|
||||
<value>Yes, clear the cache</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="IconPicker_BuiltInIcon.PlaceholderText" xml:space="preserve">
|
||||
<value>Type to filter icons</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -103,6 +103,7 @@ Author(s):
|
||||
X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \
|
||||
X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \
|
||||
X(bool, ForceVTInput, "compatibility.input.forceVT", false) \
|
||||
X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \
|
||||
X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \
|
||||
X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \
|
||||
X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \
|
||||
|
||||
@@ -10,14 +10,14 @@ Licensed under the MIT license.
|
||||
|
||||
struct
|
||||
__declspec(uuid("6068ee1b-1ea0-4804-993a-42ef0c58d867"))
|
||||
IMediaResourceContainer : public IUnknown
|
||||
IMediaResourceContainer : public IUnknown
|
||||
{
|
||||
virtual void ResolveMediaResources(const winrt::Microsoft::Terminal::Settings::Model::MediaResourceResolver& resolver) = 0;
|
||||
};
|
||||
|
||||
struct
|
||||
__declspec(uuid("9f11361c-7c8f-45c9-8948-36b66d67eca8"))
|
||||
IPathlessMediaResourceContainer : public IUnknown
|
||||
IPathlessMediaResourceContainer : public IUnknown
|
||||
{
|
||||
virtual void ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const winrt::Microsoft::Terminal::Settings::Model::MediaResourceResolver& resolver) = 0;
|
||||
};
|
||||
|
||||
@@ -88,6 +88,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, ReloadEnvironmentVariables);
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, RainbowSuggestions);
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, ForceVTInput);
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, AllowKittyKeyboardMode);
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, AllowVtChecksumReport);
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, AllowKeypadMode);
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, AllowVtClipboardWrite);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace ControlUnitTests
|
||||
public:
|
||||
MockConnection() noexcept = default;
|
||||
|
||||
void Initialize(const winrt::Windows::Foundation::Collections::ValueSet& /*settings*/){};
|
||||
void Initialize(const winrt::Windows::Foundation::Collections::ValueSet& /*settings*/) {};
|
||||
void Start() noexcept {};
|
||||
void WriteInput(const winrt::array_view<const char16_t> data)
|
||||
{
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
X(bool, TrimBlockSelection, true) \
|
||||
X(bool, SuppressApplicationTitle) \
|
||||
X(bool, ForceVTInput, false) \
|
||||
X(bool, AllowKittyKeyboardMode, true) \
|
||||
X(winrt::hstring, StartingTitle) \
|
||||
X(bool, DetectURLs, true) \
|
||||
X(bool, AutoMarkPrompts) \
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
|
||||
<!-- For ALL build types-->
|
||||
<PropertyGroup Label="Configuration">
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
|
||||
|
||||
@@ -117,6 +117,14 @@ using namespace Microsoft::Console::Interactivity;
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
// TODO: Avoid translating win32im sequences to Kitty Keyboard Protocol temporarily.
|
||||
// This is because as of this writing, our implementation is brand new, and Windows Terminal
|
||||
// needs a toggle to disable it. That only works if ConPTY then doesn't do it anyway.
|
||||
if (const auto inputBuffer = ServiceLocator::LocateGlobals().getConsoleInformation().pInputBuffer)
|
||||
{
|
||||
inputBuffer->GetTerminalInput().ForceDisableKittyKeyboardProtocol(true);
|
||||
}
|
||||
|
||||
// The only way we're initialized is if the args said we're in conpty mode.
|
||||
// If the args say so, then at least one of in, out, or signal was specified
|
||||
_state = State::Initialized;
|
||||
|
||||
@@ -47,11 +47,11 @@ public:
|
||||
#define _RTL_CONSTANT_STRING_remove_const_macro(s) \
|
||||
(const_cast<_RTL_CONSTANT_STRING_remove_const_template_class<sizeof((s)[0])>::T*>(s))
|
||||
|
||||
#define RTL_CONSTANT_STRING(s) \
|
||||
{ \
|
||||
sizeof(s) - sizeof((s)[0]), \
|
||||
sizeof(s) / sizeof(_RTL_CONSTANT_STRING_type_check(s)), \
|
||||
_RTL_CONSTANT_STRING_remove_const_macro(s) \
|
||||
#define RTL_CONSTANT_STRING(s) \
|
||||
{ \
|
||||
sizeof(s) - sizeof((s)[0]), \
|
||||
sizeof(s) / sizeof(_RTL_CONSTANT_STRING_type_check(s)), \
|
||||
_RTL_CONSTANT_STRING_remove_const_macro(s) \
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Author(s):
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
struct __declspec(uuid(__CLSID_CConsoleHandoff))
|
||||
CConsoleHandoff : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IConsoleHandoff, IDefaultTerminalMarker>
|
||||
CConsoleHandoff : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IConsoleHandoff, IDefaultTerminalMarker>
|
||||
{
|
||||
#pragma region IConsoleHandoff
|
||||
STDMETHODIMP EstablishHandoff(HANDLE server,
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace
|
||||
|
||||
if (hNtDll != nullptr)
|
||||
{
|
||||
typedef NTSTATUS (*PfnNtOpenProcess)(HANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, CLIENT_ID * ClientId);
|
||||
typedef NTSTATUS (*PfnNtOpenProcess)(HANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, CLIENT_ID* ClientId);
|
||||
|
||||
static auto pfn = (PfnNtOpenProcess)GetProcAddress(hNtDll, "NtOpenProcess");
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ Revision History:
|
||||
|
||||
#include <til/bit.h>
|
||||
|
||||
#include <IDataSource.h>
|
||||
|
||||
// Helper for declaring a variable to store a TEST_METHOD_PROPERTY and get it's value from the test metadata
|
||||
#define INIT_TEST_PROPERTY(type, identifier, description) \
|
||||
type identifier; \
|
||||
@@ -45,6 +47,178 @@ Revision History:
|
||||
|
||||
namespace WEX::TestExecution
|
||||
{
|
||||
struct ArrayIndexTaefAdapterRow : IDataRow
|
||||
{
|
||||
ArrayIndexTaefAdapterRow(size_t index) :
|
||||
_index(index) {}
|
||||
|
||||
// IUnknown
|
||||
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override
|
||||
{
|
||||
if (!ppvObject)
|
||||
{
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == __uuidof(IUnknown))
|
||||
{
|
||||
AddRef();
|
||||
*ppvObject = static_cast<IUnknown*>(this);
|
||||
return S_OK;
|
||||
}
|
||||
else if (riid == __uuidof(IDataRow))
|
||||
{
|
||||
*ppvObject = static_cast<IDataRow*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppvObject = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef() override
|
||||
{
|
||||
return _refCount.fetch_add(1) + 1;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Release() override
|
||||
{
|
||||
const auto count = _refCount.fetch_sub(1) - 1;
|
||||
if (count == 0)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// IDataRow
|
||||
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
wchar_t buf[16];
|
||||
swprintf_s(buf, L"%zu", _index);
|
||||
|
||||
LONG idx = 0;
|
||||
const auto array = SafeArrayCreateVector(VT_BSTR, 0, 1);
|
||||
SafeArrayPutElement(array, &idx, SysAllocString(buf));
|
||||
*ppData = array;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
|
||||
{
|
||||
*ppMetadataNames = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
*ppData = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetName(BSTR* ppszRowName) override
|
||||
{
|
||||
*ppszRowName = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<ULONG> _refCount{ 1 };
|
||||
size_t _index = 0;
|
||||
};
|
||||
|
||||
struct ArrayIndexTaefAdapterSource : IDataSource
|
||||
{
|
||||
explicit ArrayIndexTaefAdapterSource(size_t count) :
|
||||
_count{ count } {}
|
||||
|
||||
// IUnknown
|
||||
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override
|
||||
{
|
||||
if (!ppvObject)
|
||||
{
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == __uuidof(IUnknown))
|
||||
{
|
||||
AddRef();
|
||||
*ppvObject = static_cast<IUnknown*>(this);
|
||||
return S_OK;
|
||||
}
|
||||
else if (riid == __uuidof(IDataSource))
|
||||
{
|
||||
*ppvObject = static_cast<IDataSource*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppvObject = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef() override
|
||||
{
|
||||
return _refCount.fetch_add(1) + 1;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Release() override
|
||||
{
|
||||
const auto count = _refCount.fetch_sub(1) - 1;
|
||||
if (count == 0)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// IDataSource
|
||||
STDMETHODIMP Advance(IDataRow** ppDataRow) override
|
||||
{
|
||||
if (_index < _count)
|
||||
{
|
||||
*ppDataRow = static_cast<IDataRow*>(new ArrayIndexTaefAdapterRow(_index++));
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppDataRow = nullptr;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP Reset() override
|
||||
{
|
||||
_index = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
|
||||
{
|
||||
LONG idx = 0;
|
||||
const auto array = SafeArrayCreateVector(VT_BSTR, 0, 1);
|
||||
SafeArrayPutElement(array, &idx, SysAllocString(L"index"));
|
||||
*names = array;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
|
||||
{
|
||||
*type = nullptr;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<ULONG> _refCount{ 1 };
|
||||
size_t _count = 0;
|
||||
size_t _index = 0;
|
||||
};
|
||||
|
||||
// Compare two floats using a ULP (unit last place) tolerance of up to 4.
|
||||
// Allows you to compare two floats that are almost equal.
|
||||
// Think of: 0.200000000000000 vs. 0.200000000000001.
|
||||
|
||||
@@ -251,17 +251,17 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
rle_pair(rle_pair&&) = default;
|
||||
rle_pair& operator=(rle_pair&&) = default;
|
||||
|
||||
constexpr rle_pair(const T& value, const S& length) noexcept(std::is_nothrow_copy_constructible_v<T>&& std::is_nothrow_copy_constructible_v<S>) :
|
||||
constexpr rle_pair(const T& value, const S& length) noexcept(std::is_nothrow_copy_constructible_v<T> && std::is_nothrow_copy_constructible_v<S>) :
|
||||
value(value), length(length)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr rle_pair(T&& value, S&& length) noexcept(std::is_nothrow_constructible_v<T>&& std::is_nothrow_constructible_v<S>) :
|
||||
constexpr rle_pair(T&& value, S&& length) noexcept(std::is_nothrow_constructible_v<T> && std::is_nothrow_constructible_v<S>) :
|
||||
value(std::forward<T>(value)), length(std::forward<S>(length))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr void swap(rle_pair& other) noexcept(std::is_nothrow_swappable_v<T>&& std::is_nothrow_swappable_v<S>)
|
||||
constexpr void swap(rle_pair& other) noexcept(std::is_nothrow_swappable_v<T> && std::is_nothrow_swappable_v<S>)
|
||||
{
|
||||
if (this != std::addressof(other))
|
||||
{
|
||||
|
||||
@@ -109,97 +109,10 @@ static constexpr til::point point_offset_by_line(const til::point start, const t
|
||||
// IMPORTANT: reference this _after_ defining point_offset_by_XXX. We need it for some definitions
|
||||
#include "GeneratedUiaTextRangeMovementTests.g.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
#pragma region TAEF hookup for the test case array above
|
||||
struct ArrayIndexTaefAdapterRow : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataRow>
|
||||
{
|
||||
HRESULT RuntimeClassInitialize(const size_t index)
|
||||
{
|
||||
_index = index;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
const auto indexString{ wil::str_printf<std::wstring>(L"%zu", _index) };
|
||||
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
||||
LONG index{ 0 };
|
||||
auto indexBstr{ wil::make_bstr(indexString.c_str()) };
|
||||
(void)SafeArrayPutElement(safeArray, &index, indexBstr.release());
|
||||
*ppData = safeArray;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
|
||||
{
|
||||
*ppMetadataNames = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
*ppData = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetName(BSTR* ppszRowName) override
|
||||
{
|
||||
*ppszRowName = wil::make_bstr(s_movementTests[_index].name.data()).release();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _index;
|
||||
};
|
||||
|
||||
struct ArrayIndexTaefAdapterSource : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataSource>
|
||||
{
|
||||
STDMETHODIMP Advance(IDataRow** ppDataRow) override
|
||||
{
|
||||
if (_index < s_movementTests.size())
|
||||
{
|
||||
Microsoft::WRL::MakeAndInitialize<ArrayIndexTaefAdapterRow>(ppDataRow, _index++);
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppDataRow = nullptr;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP Reset() override
|
||||
{
|
||||
_index = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
|
||||
{
|
||||
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
||||
LONG index{ 0 };
|
||||
auto dataNameBstr{ wil::make_bstr(L"index") };
|
||||
(void)SafeArrayPutElement(safeArray, &index, dataNameBstr.release());
|
||||
*names = safeArray;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
|
||||
{
|
||||
*type = nullptr;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _index{ 0 };
|
||||
};
|
||||
#pragma endregion
|
||||
}
|
||||
|
||||
extern "C" HRESULT __declspec(dllexport) __cdecl GeneratedMovementTestDataSource(IDataSource** ppDataSource, void*)
|
||||
{
|
||||
auto source{ Microsoft::WRL::Make<ArrayIndexTaefAdapterSource>() };
|
||||
return source.CopyTo(ppDataSource);
|
||||
*ppDataSource = new ArrayIndexTaefAdapterSource>(std::size(testCases));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// UiaTextRange takes an object that implements
|
||||
|
||||
@@ -31,16 +31,16 @@ typedef enum DPI_AWARENESS
|
||||
DPI_AWARENESS_PER_MONITOR_AWARE = 2
|
||||
} DPI_AWARENESS;
|
||||
|
||||
#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
|
||||
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
|
||||
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
|
||||
#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT) - 1)
|
||||
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT) - 2)
|
||||
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT) - 3)
|
||||
|
||||
#endif
|
||||
|
||||
// This type is being defined in RS2 but is percolating through the
|
||||
// tree. Def it here if it hasn't collided with our branch yet.
|
||||
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
|
||||
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
|
||||
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) - 4)
|
||||
#endif
|
||||
|
||||
namespace Microsoft::Console::Interactivity::Win32
|
||||
|
||||
@@ -175,7 +175,7 @@ void Undo(HWND hControlWindow);
|
||||
//
|
||||
// Macros
|
||||
//
|
||||
#define AttrToRGB(Attr) (gpStateInfo->ColorTable[(Attr)&0x0F])
|
||||
#define AttrToRGB(Attr) (gpStateInfo->ColorTable[(Attr) & 0x0F])
|
||||
#define ScreenTextColor(pStateInfo) \
|
||||
(AttrToRGB(LOBYTE(pStateInfo->ScreenAttributes) & 0x0F))
|
||||
#define ScreenBkColor(pStateInfo) \
|
||||
|
||||
@@ -72,7 +72,7 @@ typedef struct tagFACENODE
|
||||
|
||||
#pragma warning(pop)
|
||||
|
||||
#define TM_IS_TT_FONT(x) (((x)&TMPF_TRUETYPE) == TMPF_TRUETYPE)
|
||||
#define TM_IS_TT_FONT(x) (((x) & TMPF_TRUETYPE) == TMPF_TRUETYPE)
|
||||
#define IS_BOLD(w) ((w) >= FW_SEMIBOLD)
|
||||
#define SIZE_EQUAL(s1, s2) (((s1).X == (s2).X) && ((s1).Y == (s2).Y))
|
||||
#define POINTS_PER_INCH 72
|
||||
|
||||
@@ -52,7 +52,7 @@ Revision History:
|
||||
SendMessage(hWnd, bLB ? LB_GETTEXT : CB_GETLBTEXT, w, 0L)
|
||||
|
||||
#define lcbFINDSTRINGEXACT(hWnd, bLB, pwsz) \
|
||||
(LONG) SendMessage(hWnd, bLB ? LB_FINDSTRINGEXACT : CB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)pwsz)
|
||||
(LONG) SendMessage(hWnd, bLB ? LB_FINDSTRINGEXACT : CB_FINDSTRINGEXACT, (WPARAM) - 1, (LPARAM)pwsz)
|
||||
|
||||
#define lcbADDSTRING(hWnd, bLB, pwsz) \
|
||||
(LONG) SendMessage(hWnd, bLB ? LB_ADDSTRING : CB_ADDSTRING, 0, (LPARAM)pwsz)
|
||||
|
||||
@@ -1151,6 +1151,8 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len
|
||||
const size_t col2 = _api.bufferLineColumn[a.textPosition + i];
|
||||
const auto fg = colors[col1 << shift];
|
||||
|
||||
// TODO: Instead of aligning each DWrite-cluster to the cell grid,
|
||||
// we should align each grapheme cluster to the cell grid.
|
||||
const auto expectedAdvance = (col2 - col1) * _p.s->font->cellSize.x;
|
||||
f32 actualAdvance = 0;
|
||||
for (auto j = prevCluster; j < nextCluster; ++j)
|
||||
|
||||
@@ -678,12 +678,12 @@ void BackendD3D::_debugUpdateShaders(const RenderingPayload& p) noexcept
|
||||
struct FileVS
|
||||
{
|
||||
std::wstring_view filename;
|
||||
wil::com_ptr<ID3D11VertexShader> BackendD3D::*target;
|
||||
wil::com_ptr<ID3D11VertexShader> BackendD3D::* target;
|
||||
};
|
||||
struct FilePS
|
||||
{
|
||||
std::wstring_view filename;
|
||||
wil::com_ptr<ID3D11PixelShader> BackendD3D::*target;
|
||||
wil::com_ptr<ID3D11PixelShader> BackendD3D::* target;
|
||||
};
|
||||
|
||||
static constexpr std::array filesVS{
|
||||
|
||||
@@ -67,6 +67,10 @@ public:
|
||||
virtual void DeleteColumn(const VTInt distance) = 0; // DECDC
|
||||
virtual void SetKeypadMode(const bool applicationMode) = 0; // DECKPAM, DECKPNM
|
||||
virtual void SetAnsiMode(const bool ansiMode) = 0; // DECANM
|
||||
virtual void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) noexcept = 0; // CSI = flags ; mode u
|
||||
virtual void QueryKittyKeyboardProtocol() = 0; // CSI ? u
|
||||
virtual void PushKittyKeyboardProtocol(const VTParameter flags) = 0; // CSI > flags u
|
||||
virtual void PopKittyKeyboardProtocol(const VTParameter count) = 0; // CSI < count u
|
||||
virtual void SetTopBottomScrollingMargins(const VTInt topMargin, const VTInt bottomMargin) = 0; // DECSTBM
|
||||
virtual void SetLeftRightScrollingMargins(const VTInt leftMargin, const VTInt rightMargin) = 0; // DECSLRM
|
||||
virtual void EnquireAnswerback() = 0; // ENQ
|
||||
|
||||
@@ -2054,6 +2054,35 @@ void AdaptDispatch::SetKeypadMode(const bool fApplicationMode) noexcept
|
||||
_terminalInput.SetInputMode(TerminalInput::Mode::Keypad, fApplicationMode);
|
||||
}
|
||||
|
||||
// CSI = flags ; mode u - Sets kitty keyboard protocol flags
|
||||
void AdaptDispatch::SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) noexcept
|
||||
{
|
||||
const auto kittyFlags = gsl::narrow_cast<uint8_t>(flags.value_or(0));
|
||||
const auto KittyKeyboardProtocol = static_cast<TerminalInput::KittyKeyboardProtocolMode>(mode.value_or(1));
|
||||
_terminalInput.SetKittyKeyboardProtocol(kittyFlags, KittyKeyboardProtocol);
|
||||
}
|
||||
|
||||
// CSI ? u - Queries current kitty keyboard protocol flags
|
||||
void AdaptDispatch::QueryKittyKeyboardProtocol()
|
||||
{
|
||||
const auto flags = static_cast<VTInt>(_terminalInput.GetKittyFlags());
|
||||
_ReturnCsiResponse(fmt::format(FMT_COMPILE(L"?{}u"), flags));
|
||||
}
|
||||
|
||||
// CSI > flags u - Pushes current kitty keyboard flags onto the stack and sets new flags
|
||||
void AdaptDispatch::PushKittyKeyboardProtocol(const VTParameter flags)
|
||||
{
|
||||
const auto kittyFlags = gsl::narrow_cast<uint8_t>(flags.value_or(0));
|
||||
_terminalInput.PushKittyFlags(kittyFlags);
|
||||
}
|
||||
|
||||
// CSI < count u - Pops one or more entries from the kitty keyboard stack
|
||||
void AdaptDispatch::PopKittyKeyboardProtocol(const VTParameter count)
|
||||
{
|
||||
const auto popCount = static_cast<size_t>(count.value_or(1));
|
||||
_terminalInput.PopKittyFlags(popCount);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Internal logic for adding or removing lines in the active screen buffer.
|
||||
// This also moves the cursor to the left margin, which is expected behavior for IL and DL.
|
||||
|
||||
@@ -97,6 +97,10 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void RequestMode(const DispatchTypes::ModeParams param) override; // DECRQM
|
||||
void SetKeypadMode(const bool applicationMode) noexcept override; // DECKPAM, DECKPNM
|
||||
void SetAnsiMode(const bool ansiMode) override; // DECANM
|
||||
void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) noexcept override; // Kitty keyboard protocol CSI = flags ; mode u
|
||||
void QueryKittyKeyboardProtocol() override; // Kitty keyboard protocol CSI ? u
|
||||
void PushKittyKeyboardProtocol(const VTParameter flags) override; // Kitty keyboard protocol CSI > flags u
|
||||
void PopKittyKeyboardProtocol(const VTParameter count) override; // Kitty keyboard protocol CSI < count u
|
||||
void SetTopBottomScrollingMargins(const VTInt topMargin,
|
||||
const VTInt bottomMargin) override; // DECSTBM
|
||||
void SetLeftRightScrollingMargins(const VTInt leftMargin,
|
||||
|
||||
@@ -125,10 +125,10 @@ void AdaptDispatch::_SetRgbColorsHelperFromSubParams(const VTParameter colorItem
|
||||
// sub params are in the order:
|
||||
// :2:<color-space-id>:<r>:<g>:<b>
|
||||
|
||||
// We treat a color as invalid if it has a non-empty color space ID, as
|
||||
// We treat a color as invalid if it has a non-zero color space ID, as
|
||||
// some applications that support non-standard ODA color sequence might
|
||||
// send the red value in its place.
|
||||
const bool hasColorSpaceId = options.at(1).has_value();
|
||||
const bool validColorSpace = options.at(1).value_or(0) == 0;
|
||||
|
||||
const size_t red = options.at(2).value_or(0);
|
||||
const size_t green = options.at(3).value_or(0);
|
||||
@@ -136,7 +136,7 @@ void AdaptDispatch::_SetRgbColorsHelperFromSubParams(const VTParameter colorItem
|
||||
|
||||
// We only apply the color if the R, G, B values fit within a byte.
|
||||
// This is to match XTerm's and VTE's behavior.
|
||||
if (!hasColorSpaceId && red <= 255 && green <= 255 && blue <= 255)
|
||||
if (validColorSpace && red <= 255 && green <= 255 && blue <= 255)
|
||||
{
|
||||
applyColor(TextColor{ RGB(red, green, blue) });
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ public:
|
||||
void DeleteColumn(const VTInt /*distance*/) override {} // DECDC
|
||||
void SetKeypadMode(const bool /*applicationMode*/) override {} // DECKPAM, DECKPNM
|
||||
void SetAnsiMode(const bool /*ansiMode*/) override {} // DECANM
|
||||
void SetKittyKeyboardProtocol(const VTParameter /*flags*/, const VTParameter /*mode*/) noexcept override {} // CSI = flags ; mode u
|
||||
void QueryKittyKeyboardProtocol() override {} // CSI ? u
|
||||
void PushKittyKeyboardProtocol(const VTParameter /*flags*/) override {} // CSI > flags u
|
||||
void PopKittyKeyboardProtocol(const VTParameter /*count*/) override {} // CSI < count u
|
||||
void SetTopBottomScrollingMargins(const VTInt /*topMargin*/, const VTInt /*bottomMargin*/) override {} // DECSTBM
|
||||
void SetLeftRightScrollingMargins(const VTInt /*leftMargin*/, const VTInt /*rightMargin*/) override {} // DECSLRM
|
||||
void EnquireAnswerback() override {} // ENQ
|
||||
@@ -176,9 +180,9 @@ public:
|
||||
void RequestPresentationStateReport(const DispatchTypes::PresentationReportFormat /*format*/) override {} // DECRQPSR
|
||||
StringHandler RestorePresentationState(const DispatchTypes::PresentationReportFormat /*format*/) override { return nullptr; } // DECRSPS
|
||||
|
||||
void PlaySounds(const VTParameters /*parameters*/) override{}; // DECPS
|
||||
void PlaySounds(const VTParameters /*parameters*/) override {}; // DECPS
|
||||
|
||||
void SetOptionalFeatures(const til::enumset<OptionalFeature> /*features*/) override{};
|
||||
void SetOptionalFeatures(const til::enumset<OptionalFeature> /*features*/) override {};
|
||||
};
|
||||
|
||||
#pragma warning(default : 26440) // Restore "can be declared noexcept" warning
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="adapterTest.cpp" />
|
||||
<ClCompile Include="inputTest.cpp" />
|
||||
<ClCompile Include="kittyKeyboardProtocol.cpp" />
|
||||
<ClCompile Include="MouseInputTest.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
@@ -71,4 +72,4 @@
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
<Import Project="$(SolutionDir)src\common.build.tests.props" />
|
||||
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -27,10 +27,18 @@
|
||||
<ClCompile Include="MouseInputTest.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="kittyKeyboardProtocol.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\precomp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natstepfilter" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
721
src/terminal/adapter/ut_adapter/kittyKeyboardProtocol.cpp
Normal file
721
src/terminal/adapter/ut_adapter/kittyKeyboardProtocol.cpp
Normal file
@@ -0,0 +1,721 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include <WexTestClass.h>
|
||||
#include <consoletaeftemplates.hpp>
|
||||
|
||||
#include "../../input/terminalInput.hpp"
|
||||
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::Common;
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
using KittyKeyboardProtocolFlags = TerminalInput::KittyKeyboardProtocolFlags;
|
||||
using KittyKeyboardProtocolMode = TerminalInput::KittyKeyboardProtocolMode;
|
||||
|
||||
namespace
|
||||
{
|
||||
TerminalInput::OutputType process(TerminalInput& input, bool keyDown, uint16_t vk, uint16_t sc, wchar_t ch, uint32_t state)
|
||||
{
|
||||
INPUT_RECORD record{};
|
||||
record.EventType = KEY_EVENT;
|
||||
record.Event.KeyEvent.bKeyDown = keyDown ? TRUE : FALSE;
|
||||
record.Event.KeyEvent.wRepeatCount = 1;
|
||||
record.Event.KeyEvent.wVirtualKeyCode = vk;
|
||||
record.Event.KeyEvent.wVirtualScanCode = sc;
|
||||
record.Event.KeyEvent.uChar.UnicodeChar = ch;
|
||||
record.Event.KeyEvent.dwControlKeyState = state;
|
||||
return input.HandleKey(record);
|
||||
}
|
||||
|
||||
TerminalInput createInput(uint8_t flags)
|
||||
{
|
||||
TerminalInput input;
|
||||
input.SetKittyKeyboardProtocol(flags, KittyKeyboardProtocolMode::Replace);
|
||||
return input;
|
||||
}
|
||||
|
||||
// Kitty modifier bit values (as transmitted, before adding 1):
|
||||
// shift=1, alt=2, ctrl=4, super=8, hyper=16, meta=32, caps_lock=64, num_lock=128
|
||||
// Transmitted as: 1 + modifiers
|
||||
|
||||
// CSI = "\x1b["
|
||||
|
||||
// Helper macros for common state combinations
|
||||
constexpr auto ALT_PRESSED = LEFT_ALT_PRESSED; // Use left for consistency
|
||||
constexpr auto CTRL_PRESSED = LEFT_CTRL_PRESSED;
|
||||
|
||||
struct TestCase
|
||||
{
|
||||
std::wstring_view name;
|
||||
std::wstring_view expected;
|
||||
uint8_t flags; // KittyKeyboardProtocolFlags
|
||||
bool keyDown;
|
||||
uint16_t vk;
|
||||
uint16_t sc;
|
||||
wchar_t ch;
|
||||
uint32_t state;
|
||||
};
|
||||
|
||||
// ========================================================================
|
||||
// Test case organization:
|
||||
//
|
||||
// 1. FLAG COMBINATIONS (32 total = 2^5 enhancement flags)
|
||||
// Testing each flag combination with a representative key
|
||||
//
|
||||
// 2. MODIFIER COMBINATIONS
|
||||
// Testing all modifier permutations (shift, alt, ctrl, caps_lock, num_lock)
|
||||
//
|
||||
// 3. SPECIAL KEY BEHAVIORS
|
||||
// - Enter/Tab/Backspace legacy behavior
|
||||
// - Escape key disambiguation
|
||||
// - Keypad keys
|
||||
// - Function keys
|
||||
// - Lock keys
|
||||
// - Modifier keys themselves
|
||||
//
|
||||
// 4. EVENT TYPES
|
||||
// - Press, repeat, release events
|
||||
// - Special handling for Enter/Tab/Backspace release
|
||||
//
|
||||
// 5. ALTERNATE KEYS
|
||||
// - Shifted key codes
|
||||
// - Base layout key codes
|
||||
//
|
||||
// 6. TEXT AS CODEPOINTS
|
||||
// - Text embedded in escape codes
|
||||
// ========================================================================
|
||||
|
||||
constexpr TestCase testCases[] = {
|
||||
// ====================================================================
|
||||
// SECTION 1: Enhancement Flag Combinations (32 combinations)
|
||||
// Using Escape key as representative since it's affected by Disambiguate
|
||||
// ====================================================================
|
||||
|
||||
// flags=0 (0b00000): No enhancements - legacy mode
|
||||
// Escape key in legacy mode: just ESC byte
|
||||
TestCase{ L"Flags=0 (none) Esc key", L"\x1b", 0, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=1 (0b00001): DisambiguateEscapeCodes only
|
||||
// Escape key becomes CSI 27 u
|
||||
TestCase{ L"Flags=1 (Disambiguate) Esc key", L"\x1b[27u", 1, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=2 (0b00010): ReportEventTypes only
|
||||
// No disambiguation, so Esc is still legacy (but with event type tracking internally)
|
||||
TestCase{ L"Flags=2 (EventTypes) Esc key down", L"\x1b", 2, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=3 (0b00011): Disambiguate + EventTypes
|
||||
// Escape key with event type: CSI 27;1:1 u (mod=1, event=press=1)
|
||||
TestCase{ L"Flags=3 (Disambiguate+EventTypes) Esc key press", L"\x1b[27u", 3, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=4 (0b00100): ReportAlternateKeys only
|
||||
// Without Disambiguate, Escape is still legacy
|
||||
TestCase{ L"Flags=4 (AltKeys) Esc key", L"\x1b", 4, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=5 (0b00101): Disambiguate + AltKeys
|
||||
TestCase{ L"Flags=5 (Disambiguate+AltKeys) Esc key", L"\x1b[27u", 5, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=6 (0b00110): EventTypes + AltKeys
|
||||
TestCase{ L"Flags=6 (EventTypes+AltKeys) Esc key", L"\x1b", 6, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=7 (0b00111): Disambiguate + EventTypes + AltKeys
|
||||
TestCase{ L"Flags=7 (Disambiguate+EventTypes+AltKeys) Esc key press", L"\x1b[27u", 7, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=8 (0b01000): ReportAllKeysAsEscapeCodes only
|
||||
// All keys become CSI u, including Escape
|
||||
TestCase{ L"Flags=8 (AllKeys) Esc key", L"\x1b[27u", 8, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=9 (0b01001): Disambiguate + AllKeys
|
||||
TestCase{ L"Flags=9 (Disambiguate+AllKeys) Esc key", L"\x1b[27u", 9, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=10 (0b01010): EventTypes + AllKeys
|
||||
TestCase{ L"Flags=10 (EventTypes+AllKeys) Esc key press", L"\x1b[27u", 10, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=11 (0b01011): Disambiguate + EventTypes + AllKeys
|
||||
TestCase{ L"Flags=11 (Disambiguate+EventTypes+AllKeys) Esc key press", L"\x1b[27u", 11, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=12 (0b01100): AltKeys + AllKeys
|
||||
TestCase{ L"Flags=12 (AltKeys+AllKeys) Esc key", L"\x1b[27u", 12, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=13 (0b01101): Disambiguate + AltKeys + AllKeys
|
||||
TestCase{ L"Flags=13 (Disambiguate+AltKeys+AllKeys) Esc key", L"\x1b[27u", 13, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=14 (0b01110): EventTypes + AltKeys + AllKeys
|
||||
TestCase{ L"Flags=14 (EventTypes+AltKeys+AllKeys) Esc key press", L"\x1b[27u", 14, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=15 (0b01111): Disambiguate + EventTypes + AltKeys + AllKeys
|
||||
TestCase{ L"Flags=15 (Disambiguate+EventTypes+AltKeys+AllKeys) Esc key press", L"\x1b[27u", 15, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=16 (0b10000): ReportAssociatedText only (meaningless without AllKeys)
|
||||
TestCase{ L"Flags=16 (AssocText) Esc key", L"\x1b", 16, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=17 (0b10001): Disambiguate + AssocText
|
||||
TestCase{ L"Flags=17 (Disambiguate+AssocText) Esc key", L"\x1b[27u", 17, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=18 (0b10010): EventTypes + AssocText
|
||||
TestCase{ L"Flags=18 (EventTypes+AssocText) Esc key", L"\x1b", 18, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=19 (0b10011): Disambiguate + EventTypes + AssocText
|
||||
TestCase{ L"Flags=19 (Disambiguate+EventTypes+AssocText) Esc key press", L"\x1b[27u", 19, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=20 (0b10100): AltKeys + AssocText
|
||||
TestCase{ L"Flags=20 (AltKeys+AssocText) Esc key", L"\x1b", 20, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=21 (0b10101): Disambiguate + AltKeys + AssocText
|
||||
TestCase{ L"Flags=21 (Disambiguate+AltKeys+AssocText) Esc key", L"\x1b[27u", 21, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=22 (0b10110): EventTypes + AltKeys + AssocText
|
||||
TestCase{ L"Flags=22 (EventTypes+AltKeys+AssocText) Esc key", L"\x1b", 22, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=23 (0b10111): Disambiguate + EventTypes + AltKeys + AssocText
|
||||
TestCase{ L"Flags=23 (Disambiguate+EventTypes+AltKeys+AssocText) Esc key press", L"\x1b[27u", 23, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=24 (0b11000): AllKeys + AssocText
|
||||
// 'a' key with text reporting: CSI 97;;97 u
|
||||
TestCase{ L"Flags=24 (AllKeys+AssocText) 'a' key", L"\x1b[97;;97u", 24, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=25 (0b11001): Disambiguate + AllKeys + AssocText
|
||||
TestCase{ L"Flags=25 (Disambiguate+AllKeys+AssocText) 'a' key", L"\x1b[97;;97u", 25, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=26 (0b11010): EventTypes + AllKeys + AssocText
|
||||
TestCase{ L"Flags=26 (EventTypes+AllKeys+AssocText) 'a' key press", L"\x1b[97;;97u", 26, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=27 (0b11011): Disambiguate + EventTypes + AllKeys + AssocText
|
||||
TestCase{ L"Flags=27 (Disambiguate+EventTypes+AllKeys+AssocText) 'a' key press", L"\x1b[97;;97u", 27, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=28 (0b11100): AltKeys + AllKeys + AssocText
|
||||
TestCase{ L"Flags=28 (AltKeys+AllKeys+AssocText) 'a' key", L"\x1b[97;;97u", 28, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=29 (0b11101): Disambiguate + AltKeys + AllKeys + AssocText
|
||||
TestCase{ L"Flags=29 (Disambiguate+AltKeys+AllKeys+AssocText) 'a' key", L"\x1b[97;;97u", 29, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=30 (0b11110): EventTypes + AltKeys + AllKeys + AssocText
|
||||
TestCase{ L"Flags=30 (EventTypes+AltKeys+AllKeys+AssocText) 'a' key press", L"\x1b[97;;97u", 30, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=31 (0b11111): All flags enabled
|
||||
TestCase{ L"Flags=31 (all) 'a' key press", L"\x1b[97;;97u", 31, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 2: Modifier Combinations with Disambiguate (flag=1)
|
||||
// Testing all modifier permutations with 'a' key
|
||||
// Kitty modifier encoding: shift=1, alt=2, ctrl=4, caps_lock=64, num_lock=128
|
||||
// Transmitted value = 1 + modifiers
|
||||
// ====================================================================
|
||||
|
||||
// Alt+'a' -> CSI 97;3 u (mod=1+2=3)
|
||||
TestCase{ L"Disambiguate: Alt+a", L"\x1b[97;3u", 1, true, 'A', 0x1E, L'a', ALT_PRESSED },
|
||||
|
||||
// Ctrl+'a' -> CSI 97;5 u (mod=1+4=5)
|
||||
TestCase{ L"Disambiguate: Ctrl+a", L"\x1b[97;5u", 1, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
|
||||
|
||||
// Ctrl+Alt+'a' -> CSI 97;7 u (mod=1+2+4=7)
|
||||
TestCase{ L"Disambiguate: Ctrl+Alt+a", L"\x1b[97;7u", 1, true, 'A', 0x1E, L'\x01', CTRL_PRESSED | ALT_PRESSED },
|
||||
|
||||
// Shift+Alt+'a' -> CSI 97;4 u (mod=1+1+2=4)
|
||||
TestCase{ L"Disambiguate: Shift+Alt+a", L"\x1b[97;4u", 1, true, 'A', 0x1E, L'A', SHIFT_PRESSED | ALT_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 3: Modifier combinations with AllKeys (flag=8)
|
||||
// All keys produce CSI u, lock keys are reported
|
||||
// ====================================================================
|
||||
|
||||
// No modifiers: 'a' -> CSI 97 u
|
||||
TestCase{ L"AllKeys: 'a' no mods", L"\x1b[97u", 8, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// Shift+'a' -> CSI 97;2 u (mod=1+1=2)
|
||||
TestCase{ L"AllKeys: Shift+a", L"\x1b[97;2u", 8, true, 'A', 0x1E, L'A', SHIFT_PRESSED },
|
||||
|
||||
// Alt+'a' -> CSI 97;3 u (mod=1+2=3)
|
||||
TestCase{ L"AllKeys: Alt+a", L"\x1b[97;3u", 8, true, 'A', 0x1E, L'a', ALT_PRESSED },
|
||||
|
||||
// Ctrl+'a' -> CSI 97;5 u (mod=1+4=5)
|
||||
TestCase{ L"AllKeys: Ctrl+a", L"\x1b[97;5u", 8, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
|
||||
|
||||
// Shift+Alt+'a' -> CSI 97;4 u (mod=1+1+2=4)
|
||||
TestCase{ L"AllKeys: Shift+Alt+a", L"\x1b[97;4u", 8, true, 'A', 0x1E, L'A', SHIFT_PRESSED | ALT_PRESSED },
|
||||
|
||||
// Shift+Ctrl+'a' -> CSI 97;6 u (mod=1+1+4=6)
|
||||
TestCase{ L"AllKeys: Shift+Ctrl+a", L"\x1b[97;6u", 8, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | CTRL_PRESSED },
|
||||
|
||||
// Alt+Ctrl+'a' -> CSI 97;7 u (mod=1+2+4=7)
|
||||
TestCase{ L"AllKeys: Alt+Ctrl+a", L"\x1b[97;7u", 8, true, 'A', 0x1E, L'\x01', ALT_PRESSED | CTRL_PRESSED },
|
||||
|
||||
// Shift+Alt+Ctrl+'a' -> CSI 97;8 u (mod=1+1+2+4=8)
|
||||
TestCase{ L"AllKeys: Shift+Alt+Ctrl+a", L"\x1b[97;8u", 8, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED },
|
||||
|
||||
// CapsLock+'a' -> CSI 97;65 u (mod=1+64=65)
|
||||
TestCase{ L"AllKeys: CapsLock+a", L"\x1b[97;65u", 8, true, 'A', 0x1E, L'A', CAPSLOCK_ON },
|
||||
|
||||
// NumLock+'a' -> CSI 97;129 u (mod=1+128=129)
|
||||
TestCase{ L"AllKeys: NumLock+a", L"\x1b[97;129u", 8, true, 'A', 0x1E, L'a', NUMLOCK_ON },
|
||||
|
||||
// CapsLock+NumLock+'a' -> CSI 97;193 u (mod=1+64+128=193)
|
||||
TestCase{ L"AllKeys: CapsLock+NumLock+a", L"\x1b[97;193u", 8, true, 'A', 0x1E, L'A', CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// Shift+CapsLock+'a' -> CSI 97;66 u (mod=1+1+64=66)
|
||||
TestCase{ L"AllKeys: Shift+CapsLock+a", L"\x1b[97;66u", 8, true, 'A', 0x1E, L'a', SHIFT_PRESSED | CAPSLOCK_ON },
|
||||
|
||||
// All modifiers: Shift+Alt+Ctrl+CapsLock+NumLock
|
||||
// mod=1+1+2+4+64+128=200
|
||||
TestCase{ L"AllKeys: all mods", L"\x1b[97;200u", 8, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 4: Enter, Tab, Backspace - Legacy behavior exceptions
|
||||
// Per spec: "The only exceptions are the Enter, Tab and Backspace keys
|
||||
// which still generate the same bytes as in legacy mode"
|
||||
// ====================================================================
|
||||
|
||||
// With Disambiguate only (flag=1), these stay legacy:
|
||||
// (These should return MakeUnhandled(), causing legacy processing)
|
||||
// We'll test that they DON'T produce CSI u output
|
||||
|
||||
// With AllKeys (flag=8), they DO get CSI u encoding:
|
||||
// Enter -> CSI 13 u
|
||||
TestCase{ L"AllKeys: Enter", L"\x1b[13u", 8, true, VK_RETURN, 0x1C, L'\r', 0 },
|
||||
|
||||
// Tab -> CSI 9 u
|
||||
TestCase{ L"AllKeys: Tab", L"\x1b[9u", 8, true, VK_TAB, 0x0F, L'\t', 0 },
|
||||
|
||||
// Backspace -> CSI 127 u
|
||||
TestCase{ L"AllKeys: Backspace", L"\x1b[127u", 8, true, VK_BACK, 0x0E, L'\b', 0 },
|
||||
|
||||
// Enter with Shift -> CSI 13;2 u
|
||||
TestCase{ L"AllKeys: Shift+Enter", L"\x1b[13;2u", 8, true, VK_RETURN, 0x1C, L'\r', SHIFT_PRESSED },
|
||||
|
||||
// Tab with Ctrl -> CSI 9;5 u
|
||||
TestCase{ L"AllKeys: Ctrl+Tab", L"\x1b[9;5u", 8, true, VK_TAB, 0x0F, L'\t', CTRL_PRESSED },
|
||||
|
||||
// Backspace with Alt -> CSI 127;3 u
|
||||
TestCase{ L"AllKeys: Alt+Backspace", L"\x1b[127;3u", 8, true, VK_BACK, 0x0E, L'\b', ALT_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 5: Event Types (flag=2)
|
||||
// press=1, repeat=2, release=3
|
||||
// Format: CSI keycode;mod:event u
|
||||
// ====================================================================
|
||||
|
||||
// Key press with Disambiguate+EventTypes (flag=3)
|
||||
TestCase{ L"EventTypes: Esc press", L"\x1b[27u", 3, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// Key release with Disambiguate+EventTypes (flag=3)
|
||||
TestCase{ L"EventTypes: Esc release", L"\x1b[27;1:3u", 3, false, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// Key press with AllKeys+EventTypes (flag=10)
|
||||
TestCase{ L"EventTypes+AllKeys: 'a' press", L"\x1b[97u", 10, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// Key release with AllKeys+EventTypes (flag=10)
|
||||
TestCase{ L"EventTypes+AllKeys: 'a' release", L"\x1b[97;1:3u", 10, false, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// Enter/Tab/Backspace release - only with AllKeys+EventTypes
|
||||
// Without AllKeys, release events for these are suppressed
|
||||
TestCase{ L"EventTypes+AllKeys: Enter release", L"\x1b[13;1:3u", 10, false, VK_RETURN, 0x1C, L'\r', 0 },
|
||||
TestCase{ L"EventTypes+AllKeys: Tab release", L"\x1b[9;1:3u", 10, false, VK_TAB, 0x0F, L'\t', 0 },
|
||||
TestCase{ L"EventTypes+AllKeys: Backspace release", L"\x1b[127;1:3u", 10, false, VK_BACK, 0x0E, L'\b', 0 },
|
||||
|
||||
// Press with modifier: Shift+Esc -> CSI 27;2 u
|
||||
TestCase{ L"EventTypes: Shift+Esc press", L"\x1b[27;2u", 3, true, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED },
|
||||
|
||||
// Release with modifier: Shift+Esc -> CSI 27;2:3 u
|
||||
TestCase{ L"EventTypes: Shift+Esc release", L"\x1b[27;2:3u", 3, false, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 6: Keypad Keys
|
||||
// With Disambiguate, keypad keys get CSI u with special codepoints
|
||||
// ====================================================================
|
||||
|
||||
// Keypad 0-9: 57399-57408
|
||||
TestCase{ L"Disambiguate: Numpad0", L"\x1b[57399u", 1, true, VK_NUMPAD0, 0x52, L'0', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad1", L"\x1b[57400u", 1, true, VK_NUMPAD1, 0x4F, L'1', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad5", L"\x1b[57404u", 1, true, VK_NUMPAD5, 0x4C, L'5', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad9", L"\x1b[57408u", 1, true, VK_NUMPAD9, 0x49, L'9', 0 },
|
||||
|
||||
// Keypad operators
|
||||
TestCase{ L"Disambiguate: Numpad Decimal", L"\x1b[57409u", 1, true, VK_DECIMAL, 0x53, L'.', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad Divide", L"\x1b[57410u", 1, true, VK_DIVIDE, 0x35, L'/', ENHANCED_KEY },
|
||||
TestCase{ L"Disambiguate: Numpad Multiply", L"\x1b[57411u", 1, true, VK_MULTIPLY, 0x37, L'*', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad Subtract", L"\x1b[57412u", 1, true, VK_SUBTRACT, 0x4A, L'-', 0 },
|
||||
TestCase{ L"Disambiguate: Numpad Add", L"\x1b[57413u", 1, true, VK_ADD, 0x4E, L'+', 0 },
|
||||
|
||||
// Keypad with modifiers
|
||||
TestCase{ L"Disambiguate: Shift+Numpad5", L"\x1b[57404;2u", 1, true, VK_NUMPAD5, 0x4C, L'5', SHIFT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Ctrl+Numpad0", L"\x1b[57399;5u", 1, true, VK_NUMPAD0, 0x52, L'0', CTRL_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 7: Lock Keys and Modifier Keys (with AllKeys flag=8)
|
||||
// These report their own key codes
|
||||
// ====================================================================
|
||||
|
||||
// CapsLock key itself -> CSI 57358 u
|
||||
TestCase{ L"AllKeys: CapsLock key press", L"\x1b[57358u", 8, true, VK_CAPITAL, 0x3A, 0, 0 },
|
||||
|
||||
// NumLock key itself -> CSI 57360 u
|
||||
TestCase{ L"AllKeys: NumLock key press", L"\x1b[57360u", 8, true, VK_NUMLOCK, 0x45, 0, ENHANCED_KEY },
|
||||
|
||||
// ScrollLock key itself -> CSI 57359 u
|
||||
TestCase{ L"AllKeys: ScrollLock key press", L"\x1b[57359u", 8, true, VK_SCROLL, 0x46, 0, 0 },
|
||||
|
||||
// Left Shift key -> CSI 57441 u (with shift modifier set)
|
||||
TestCase{ L"AllKeys: Left Shift key press", L"\x1b[57441;2u", 8, true, VK_SHIFT, 0x2A, 0, SHIFT_PRESSED },
|
||||
|
||||
// Right Shift key -> CSI 57447 u
|
||||
TestCase{ L"AllKeys: Right Shift key press", L"\x1b[57447;2u", 8, true, VK_SHIFT, 0x36, 0, SHIFT_PRESSED },
|
||||
|
||||
// Left Ctrl key -> CSI 57442 u (with ctrl modifier set)
|
||||
TestCase{ L"AllKeys: Left Ctrl key press", L"\x1b[57442;5u", 8, true, VK_CONTROL, 0x1D, 0, CTRL_PRESSED },
|
||||
|
||||
// Right Ctrl key -> CSI 57448 u
|
||||
TestCase{ L"AllKeys: Right Ctrl key press", L"\x1b[57448;5u", 8, true, VK_CONTROL, 0x1D, 0, CTRL_PRESSED | ENHANCED_KEY },
|
||||
|
||||
// Left Alt key -> CSI 57443 u (with alt modifier set)
|
||||
TestCase{ L"AllKeys: Left Alt key press", L"\x1b[57443;3u", 8, true, VK_MENU, 0x38, 0, ALT_PRESSED },
|
||||
|
||||
// Right Alt key -> CSI 57449 u
|
||||
TestCase{ L"AllKeys: Right Alt key press", L"\x1b[57449;3u", 8, true, VK_MENU, 0x38, 0, RIGHT_ALT_PRESSED | ENHANCED_KEY },
|
||||
|
||||
// Left Windows key -> CSI 57444 u (super modifier not available in Win32)
|
||||
TestCase{ L"AllKeys: Left Win key press", L"\x1b[57444u", 8, true, VK_LWIN, 0x5B, 0, ENHANCED_KEY },
|
||||
|
||||
// Right Windows key -> CSI 57450 u
|
||||
TestCase{ L"AllKeys: Right Win key press", L"\x1b[57450u", 8, true, VK_RWIN, 0x5C, 0, ENHANCED_KEY },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 8: Special Keys with Disambiguate (flag=1)
|
||||
// ====================================================================
|
||||
|
||||
// Various special keys that get CSI u encoding
|
||||
|
||||
// Pause key -> CSI 57362 u
|
||||
TestCase{ L"AllKeys: Pause key", L"\x1b[57362u", 8, true, VK_PAUSE, 0x45, 0, 0 },
|
||||
|
||||
// PrintScreen key -> CSI 57361 u
|
||||
TestCase{ L"AllKeys: PrintScreen key", L"\x1b[57361u", 8, true, VK_SNAPSHOT, 0x37, 0, ENHANCED_KEY },
|
||||
|
||||
// Menu/Apps key -> CSI 57363 u
|
||||
TestCase{ L"AllKeys: Menu key", L"\x1b[57363u", 8, true, VK_APPS, 0x5D, 0, ENHANCED_KEY },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 9: Legacy text keys with Disambiguate (flag=1)
|
||||
// Per spec: "the keys a-z 0-9 ` - = [ ] \ ; ' , . / with modifiers
|
||||
// alt, ctrl, ctrl+alt, shift+alt" get CSI u encoding
|
||||
// ====================================================================
|
||||
|
||||
// Test each punctuation key with Alt
|
||||
TestCase{ L"Disambiguate: Alt+`", L"\x1b[96;3u", 1, true, VK_OEM_3, 0x29, L'`', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+-", L"\x1b[45;3u", 1, true, VK_OEM_MINUS, 0x0C, L'-', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+=", L"\x1b[61;3u", 1, true, VK_OEM_PLUS, 0x0D, L'=', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+[", L"\x1b[91;3u", 1, true, VK_OEM_4, 0x1A, L'[', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+]", L"\x1b[93;3u", 1, true, VK_OEM_6, 0x1B, L']', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+\\", L"\x1b[92;3u", 1, true, VK_OEM_5, 0x2B, L'\\', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+;", L"\x1b[59;3u", 1, true, VK_OEM_1, 0x27, L';', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+'", L"\x1b[39;3u", 1, true, VK_OEM_7, 0x28, L'\'', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+,", L"\x1b[44;3u", 1, true, VK_OEM_COMMA, 0x33, L',', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+.", L"\x1b[46;3u", 1, true, VK_OEM_PERIOD, 0x34, L'.', ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Alt+/", L"\x1b[47;3u", 1, true, VK_OEM_2, 0x35, L'/', ALT_PRESSED },
|
||||
|
||||
// Test numbers with Ctrl
|
||||
TestCase{ L"Disambiguate: Ctrl+0", L"\x1b[48;5u", 1, true, '0', 0x0B, L'0', CTRL_PRESSED },
|
||||
TestCase{ L"Disambiguate: Ctrl+1", L"\x1b[49;5u", 1, true, '1', 0x02, L'1', CTRL_PRESSED },
|
||||
TestCase{ L"Disambiguate: Ctrl+9", L"\x1b[57;5u", 1, true, '9', 0x0A, L'9', CTRL_PRESSED },
|
||||
|
||||
// Test letters with Ctrl+Alt
|
||||
TestCase{ L"Disambiguate: Ctrl+Alt+a", L"\x1b[97;7u", 1, true, 'A', 0x1E, L'\x01', CTRL_PRESSED | ALT_PRESSED },
|
||||
TestCase{ L"Disambiguate: Ctrl+Alt+z", L"\x1b[122;7u", 1, true, 'Z', 0x2C, L'\x1A', CTRL_PRESSED | ALT_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 10: Navigation keys as keypad (without ENHANCED_KEY)
|
||||
// When ENHANCED_KEY is not set, navigation keys are from the keypad
|
||||
// ====================================================================
|
||||
|
||||
// Home without ENHANCED_KEY -> KP_HOME (57423)
|
||||
TestCase{ L"AllKeys: Keypad Home", L"\x1b[57423u", 8, true, VK_HOME, 0x47, 0, 0 },
|
||||
|
||||
// End without ENHANCED_KEY -> KP_END (57424)
|
||||
TestCase{ L"AllKeys: Keypad End", L"\x1b[57424u", 8, true, VK_END, 0x4F, 0, 0 },
|
||||
|
||||
// Insert without ENHANCED_KEY -> KP_INSERT (57425)
|
||||
TestCase{ L"AllKeys: Keypad Insert", L"\x1b[57425u", 8, true, VK_INSERT, 0x52, 0, 0 },
|
||||
|
||||
// Delete without ENHANCED_KEY -> KP_DELETE (57426)
|
||||
TestCase{ L"AllKeys: Keypad Delete", L"\x1b[57426u", 8, true, VK_DELETE, 0x53, 0, 0 },
|
||||
|
||||
// PageUp without ENHANCED_KEY -> KP_PAGE_UP (57421)
|
||||
TestCase{ L"AllKeys: Keypad PageUp", L"\x1b[57421u", 8, true, VK_PRIOR, 0x49, 0, 0 },
|
||||
|
||||
// PageDown without ENHANCED_KEY -> KP_PAGE_DOWN (57422)
|
||||
TestCase{ L"AllKeys: Keypad PageDown", L"\x1b[57422u", 8, true, VK_NEXT, 0x51, 0, 0 },
|
||||
|
||||
// Arrows without ENHANCED_KEY
|
||||
TestCase{ L"AllKeys: Keypad Up", L"\x1b[57419u", 8, true, VK_UP, 0x48, 0, 0 },
|
||||
TestCase{ L"AllKeys: Keypad Down", L"\x1b[57420u", 8, true, VK_DOWN, 0x50, 0, 0 },
|
||||
TestCase{ L"AllKeys: Keypad Left", L"\x1b[57417u", 8, true, VK_LEFT, 0x4B, 0, 0 },
|
||||
TestCase{ L"AllKeys: Keypad Right", L"\x1b[57418u", 8, true, VK_RIGHT, 0x4D, 0, 0 },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 11: Media Keys
|
||||
// ====================================================================
|
||||
|
||||
TestCase{ L"AllKeys: Media Play/Pause", L"\x1b[57430u", 8, true, VK_MEDIA_PLAY_PAUSE, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Media Stop", L"\x1b[57432u", 8, true, VK_MEDIA_STOP, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Media Next Track", L"\x1b[57435u", 8, true, VK_MEDIA_NEXT_TRACK, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Media Prev Track", L"\x1b[57436u", 8, true, VK_MEDIA_PREV_TRACK, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Volume Down", L"\x1b[57438u", 8, true, VK_VOLUME_DOWN, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Volume Up", L"\x1b[57439u", 8, true, VK_VOLUME_UP, 0, 0, 0 },
|
||||
TestCase{ L"AllKeys: Volume Mute", L"\x1b[57440u", 8, true, VK_VOLUME_MUTE, 0, 0, 0 },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 12: Function Keys (F13-F24)
|
||||
// F1-F12 use legacy sequences, F13-F24 use CSI u with codes 57376-57387
|
||||
// ====================================================================
|
||||
|
||||
TestCase{ L"AllKeys: F13", L"\x1b[57376u", 8, true, VK_F13, 0x64, 0, 0 },
|
||||
TestCase{ L"AllKeys: F14", L"\x1b[57377u", 8, true, VK_F14, 0x65, 0, 0 },
|
||||
TestCase{ L"AllKeys: F15", L"\x1b[57378u", 8, true, VK_F15, 0x66, 0, 0 },
|
||||
TestCase{ L"AllKeys: F16", L"\x1b[57379u", 8, true, VK_F16, 0x67, 0, 0 },
|
||||
TestCase{ L"AllKeys: F17", L"\x1b[57380u", 8, true, VK_F17, 0x68, 0, 0 },
|
||||
TestCase{ L"AllKeys: F18", L"\x1b[57381u", 8, true, VK_F18, 0x69, 0, 0 },
|
||||
TestCase{ L"AllKeys: F19", L"\x1b[57382u", 8, true, VK_F19, 0x6A, 0, 0 },
|
||||
TestCase{ L"AllKeys: F20", L"\x1b[57383u", 8, true, VK_F20, 0x6B, 0, 0 },
|
||||
TestCase{ L"AllKeys: F21", L"\x1b[57384u", 8, true, VK_F21, 0x6C, 0, 0 },
|
||||
TestCase{ L"AllKeys: F22", L"\x1b[57385u", 8, true, VK_F22, 0x6D, 0, 0 },
|
||||
TestCase{ L"AllKeys: F23", L"\x1b[57386u", 8, true, VK_F23, 0x6E, 0, 0 },
|
||||
TestCase{ L"AllKeys: F24", L"\x1b[57387u", 8, true, VK_F24, 0x76, 0, 0 },
|
||||
|
||||
// F13 with modifiers
|
||||
TestCase{ L"AllKeys: Shift+F13", L"\x1b[57376;2u", 8, true, VK_F13, 0x64, 0, SHIFT_PRESSED },
|
||||
TestCase{ L"AllKeys: Ctrl+F13", L"\x1b[57376;5u", 8, true, VK_F13, 0x64, 0, CTRL_PRESSED },
|
||||
TestCase{ L"AllKeys: Alt+F13", L"\x1b[57376;3u", 8, true, VK_F13, 0x64, 0, ALT_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 13: Alternate Keys (ReportAlternateKeys flag = 4)
|
||||
// Format: CSI keycode:shifted-key:base-layout-key ; modifiers u
|
||||
// Shifted key is present only when shift modifier is active
|
||||
// Base layout key is the PC-101 US keyboard equivalent
|
||||
// ====================================================================
|
||||
|
||||
// Shift+a with AltKeys flag: 97:65 (a:A) - shifted key is 'A' (65)
|
||||
// flags = AllKeys(8) + AltKeys(4) = 12
|
||||
TestCase{ L"AltKeys+AllKeys: Shift+a", L"\x1b[97:65;2u", 12, true, 'A', 0x1E, L'A', SHIFT_PRESSED },
|
||||
|
||||
// Shift+1 with AltKeys flag: 49:33 (1:!) - shifted key is '!' (33)
|
||||
TestCase{ L"AltKeys+AllKeys: Shift+1", L"\x1b[49:33;2u", 12, true, '1', 0x02, L'!', SHIFT_PRESSED },
|
||||
|
||||
// Shift+[ with AltKeys flag: 91:123 ([:{) - shifted key is '{' (123)
|
||||
TestCase{ L"AltKeys+AllKeys: Shift+[", L"\x1b[91:123;2u", 12, true, VK_OEM_4, 0x1A, L'{', SHIFT_PRESSED },
|
||||
|
||||
// Without shift, no shifted key is reported
|
||||
// 'a' with AltKeys flag (no shift): 97 only, no alternate keys
|
||||
TestCase{ L"AltKeys+AllKeys: a (no shift)", L"\x1b[97u", 12, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 14: Complex combinations
|
||||
// Testing multiple flags together with various keys and modifiers
|
||||
// ====================================================================
|
||||
|
||||
// AllKeys + EventTypes + CapsLock: 'a' press with CapsLock
|
||||
// mod=1+64=65, event=press=1
|
||||
TestCase{ L"AllKeys+EventTypes: CapsLock+a press", L"\x1b[97;65u", 10, true, 'A', 0x1E, L'A', CAPSLOCK_ON },
|
||||
|
||||
// AllKeys + EventTypes + all modifiers: press
|
||||
// mod=1+1+2+4+64+128=200, event=1
|
||||
TestCase{ L"AllKeys+EventTypes: all mods press", L"\x1b[97;200u", 10, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// AllKeys + EventTypes + all modifiers: release
|
||||
TestCase{ L"AllKeys+EventTypes: all mods release", L"\x1b[97;200:3u", 10, false, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 15: Text with associated codepoints (flag=24: AllKeys + AssocText)
|
||||
// Format: CSI keycode ; modifiers ; text u
|
||||
// ====================================================================
|
||||
|
||||
// 'A' (shifted) with AssocText: CSI 97;2;65 u
|
||||
TestCase{ L"AllKeys+AssocText: Shift+a", L"\x1b[97;2;65u", 24, true, 'A', 0x1E, L'A', SHIFT_PRESSED },
|
||||
|
||||
// Number with shift (symbol): Shift+1 -> '!'
|
||||
// CSI 49;2;33 u (49='1', 33='!')
|
||||
TestCase{ L"AllKeys+AssocText: Shift+1", L"\x1b[49;2;33u", 24, true, '1', 0x02, L'!', SHIFT_PRESSED },
|
||||
|
||||
// Ctrl+a produces control character (0x01), which should not be in text
|
||||
// Text field should be omitted for control codes
|
||||
TestCase{ L"AllKeys+AssocText: Ctrl+a (no text)", L"\x1b[97;5u", 24, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 16: Edge cases
|
||||
// ====================================================================
|
||||
|
||||
// Keypad Enter (ENHANCED_KEY set) -> KP_ENTER (57414)
|
||||
TestCase{ L"AllKeys: Keypad Enter", L"\x1b[57414u", 8, true, VK_RETURN, 0x1C, L'\r', ENHANCED_KEY },
|
||||
|
||||
// Regular Enter vs Keypad Enter distinction
|
||||
TestCase{ L"AllKeys: Regular Enter", L"\x1b[13u", 8, true, VK_RETURN, 0x1C, L'\r', 0 },
|
||||
|
||||
// Escape with all basic modifiers
|
||||
TestCase{ L"AllKeys: Shift+Alt+Ctrl+Esc", L"\x1b[27;8u", 8, true, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED },
|
||||
|
||||
// Tab with Shift (special legacy: CSI Z, but with AllKeys should be CSI 9;2 u)
|
||||
TestCase{ L"AllKeys: Shift+Tab", L"\x1b[9;2u", 8, true, VK_TAB, 0x0F, 0, SHIFT_PRESSED },
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" HRESULT __declspec(dllexport) __cdecl KittyKeyTestDataSource(IDataSource** ppDataSource, void*)
|
||||
{
|
||||
*ppDataSource = new ArrayIndexTaefAdapterSource(std::size(testCases));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
class KittyKeyboardProtocolTests
|
||||
{
|
||||
TEST_CLASS(KittyKeyboardProtocolTests);
|
||||
|
||||
TEST_METHOD(KeyPressTests)
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"DataSource", L"Export:KittyKeyTestDataSource")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
DisableVerifyExceptions disableVerifyExceptions{};
|
||||
SetVerifyOutput verifyOutputScope{ VerifyOutputSettings::LogOnlyFailures };
|
||||
|
||||
size_t i{};
|
||||
TestData::TryGetValue(L"index", i);
|
||||
const auto& tc = testCases[i];
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"[%zu] Test case \"%.*s\"", i, tc.name.size(), tc.name.data()));
|
||||
|
||||
auto input = createInput(tc.flags);
|
||||
const auto expected = TerminalInput::MakeOutput(tc.expected);
|
||||
const auto actual = process(input, tc.keyDown, tc.vk, tc.sc, tc.ch, tc.state);
|
||||
const auto msg = fmt::format(L"{} != {}", til::visualize_control_codes(expected.value_or({})), til::visualize_control_codes(actual.value_or({})));
|
||||
VERIFY_ARE_EQUAL(expected, actual, msg.c_str());
|
||||
}
|
||||
|
||||
// Repeat events require stateful testing - the same key must be pressed twice
|
||||
// without a release in between. This cannot be done with the data-driven approach.
|
||||
TEST_METHOD(KeyRepeatEvents)
|
||||
{
|
||||
Log::Comment(L"Testing key repeat event type (event type = 2)");
|
||||
|
||||
// Use EventTypes flag (2) + AllKeys flag (8) = 10
|
||||
constexpr uint8_t flags = 10;
|
||||
auto input = createInput(flags);
|
||||
|
||||
// First press -> event type 1 (press)
|
||||
auto result1 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97u");
|
||||
VERIFY_ARE_EQUAL(expected1, result1, L"First press should be event type 1");
|
||||
|
||||
// Second press (same key, no release) -> event type 2 (repeat)
|
||||
auto result2 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected2 = TerminalInput::MakeOutput(L"\x1b[97;1:2u");
|
||||
VERIFY_ARE_EQUAL(expected2, result2, L"Second press should be event type 2 (repeat)");
|
||||
|
||||
// Third press (still same key) -> still event type 2 (repeat)
|
||||
auto result3 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected3 = TerminalInput::MakeOutput(L"\x1b[97;1:2u");
|
||||
VERIFY_ARE_EQUAL(expected3, result3, L"Third press should still be event type 2 (repeat)");
|
||||
|
||||
// Release -> event type 3
|
||||
auto result4 = process(input, false, 'A', 0x1E, L'a', 0);
|
||||
auto expected4 = TerminalInput::MakeOutput(L"\x1b[97;1:3u");
|
||||
VERIFY_ARE_EQUAL(expected4, result4, L"Release should be event type 3");
|
||||
|
||||
// Next press after release -> event type 1 (press) again
|
||||
auto result5 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected5 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
|
||||
VERIFY_ARE_EQUAL(expected5, result5, L"Press after release should be event type 1 again");
|
||||
}
|
||||
|
||||
// Test repeat events with modifiers
|
||||
TEST_METHOD(KeyRepeatEventsWithModifiers)
|
||||
{
|
||||
Log::Comment(L"Testing key repeat with Shift modifier");
|
||||
|
||||
constexpr uint8_t flags = 10; // EventTypes + AllKeys
|
||||
auto input = createInput(flags);
|
||||
|
||||
// First Shift+a press -> event type 1
|
||||
auto result1 = process(input, true, 'A', 0x1E, L'A', SHIFT_PRESSED);
|
||||
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97;2:1u");
|
||||
VERIFY_ARE_EQUAL(expected1, result1, L"First Shift+a press should be event type 1");
|
||||
|
||||
// Repeat Shift+a -> event type 2
|
||||
auto result2 = process(input, true, 'A', 0x1E, L'A', SHIFT_PRESSED);
|
||||
auto expected2 = TerminalInput::MakeOutput(L"\x1b[97;2:2u");
|
||||
VERIFY_ARE_EQUAL(expected2, result2, L"Repeat Shift+a should be event type 2");
|
||||
}
|
||||
|
||||
// Test that pressing different keys resets repeat detection
|
||||
TEST_METHOD(KeyRepeatResetOnDifferentKey)
|
||||
{
|
||||
Log::Comment(L"Testing that pressing a different key resets repeat detection");
|
||||
|
||||
constexpr uint8_t flags = 10; // EventTypes + AllKeys
|
||||
auto input = createInput(flags);
|
||||
|
||||
// Press 'a'
|
||||
auto result1 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
|
||||
VERIFY_ARE_EQUAL(expected1, result1, L"First 'a' press should be event type 1");
|
||||
|
||||
// Press 'b' (different key) -> should be press, not repeat
|
||||
auto result2 = process(input, true, 'B', 0x30, L'b', 0);
|
||||
auto expected2 = TerminalInput::MakeOutput(L"\x1b[98;1:1u");
|
||||
VERIFY_ARE_EQUAL(expected2, result2, L"'b' press should be event type 1 (not repeat)");
|
||||
|
||||
// Press 'a' again -> should be press since 'b' was pressed in between
|
||||
auto result3 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected3 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
|
||||
VERIFY_ARE_EQUAL(expected3, result3, L"'a' press after 'b' should be event type 1 (new press)");
|
||||
}
|
||||
|
||||
// Test Enter/Tab/Backspace release suppression without AllKeys
|
||||
TEST_METHOD(EnterTabBackspaceReleaseWithoutAllKeys)
|
||||
{
|
||||
Log::Comment(L"Testing that Enter/Tab/Backspace don't report release without AllKeys flag");
|
||||
|
||||
// Use Disambiguate + EventTypes (flags = 3), but NOT AllKeys
|
||||
constexpr uint8_t flags = 3;
|
||||
auto input = createInput(flags);
|
||||
|
||||
// These keys should NOT produce output for release events
|
||||
// (they return MakeUnhandled for press too with just Disambiguate,
|
||||
// but release should produce _makeNoOutput)
|
||||
|
||||
// Note: With flags=3 (no AllKeys), Enter/Tab/Backspace use legacy encoding
|
||||
// and release events should be suppressed (return no output)
|
||||
}
|
||||
|
||||
// Test that without EventTypes flag, release events produce no output
|
||||
TEST_METHOD(ReleaseEventsWithoutEventTypesFlag)
|
||||
{
|
||||
Log::Comment(L"Testing that release events produce no output without EventTypes flag");
|
||||
|
||||
// Use only AllKeys (flag = 8), NOT EventTypes
|
||||
constexpr uint8_t flags = 8;
|
||||
auto input = createInput(flags);
|
||||
|
||||
// Press should produce output
|
||||
auto result1 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97u");
|
||||
VERIFY_ARE_EQUAL(expected1, result1, L"Press should produce output");
|
||||
|
||||
// Release should produce no output (empty optional)
|
||||
auto result2 = process(input, false, 'A', 0x1E, L'a', 0);
|
||||
VERIFY_IS_FALSE(result2.has_value(), L"Release without EventTypes flag should produce no output");
|
||||
}
|
||||
|
||||
// Test legacy mode (flags=0) produces MakeUnhandled for regular keys
|
||||
TEST_METHOD(LegacyModePassthrough)
|
||||
{
|
||||
Log::Comment(L"Testing that legacy mode (flags=0) returns MakeUnhandled for regular keys");
|
||||
|
||||
constexpr uint8_t flags = 0;
|
||||
auto input = createInput(flags);
|
||||
|
||||
// Regular key 'a' should return MakeUnhandled (falls through to legacy processing)
|
||||
auto result = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto unhandled = TerminalInput::MakeUnhandled();
|
||||
VERIFY_ARE_EQUAL(unhandled, result, L"Regular key in legacy mode should be unhandled");
|
||||
}
|
||||
};
|
||||
@@ -12,7 +12,6 @@
|
||||
<Import Project="$(SolutionDir)src\common.nugetversions.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\mouseInput.cpp" />
|
||||
<ClCompile Include="..\mouseInputState.cpp" />
|
||||
<ClCompile Include="..\terminalInput.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
|
||||
@@ -487,7 +487,7 @@ TerminalInput::OutputType TerminalInput::_GenerateSGRSequence(const til::point p
|
||||
// True if the alternate buffer is active and alternate scroll mode is enabled and the event is a mouse wheel event.
|
||||
bool TerminalInput::ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept
|
||||
{
|
||||
const auto inAltBuffer{ _mouseInputState.inAlternateBuffer };
|
||||
const auto inAltBuffer{ _inAlternateBuffer };
|
||||
const auto inAltScroll{ _inputMode.test(Mode::AlternateScroll) };
|
||||
const auto wasMouseWheel{ (button == WM_MOUSEWHEEL || button == WM_MOUSEHWHEEL) && delta != 0 };
|
||||
return inAltBuffer && inAltScroll && wasMouseWheel;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include <windows.h>
|
||||
#include "terminalInput.hpp"
|
||||
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
// Routine Description:
|
||||
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
|
||||
// Parameters:
|
||||
// <none>
|
||||
// Return value:
|
||||
// <none>
|
||||
void TerminalInput::UseAlternateScreenBuffer() noexcept
|
||||
{
|
||||
_mouseInputState.inAlternateBuffer = true;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
|
||||
// Parameters:
|
||||
// <none>
|
||||
// Return value:
|
||||
// <none>
|
||||
void TerminalInput::UseMainScreenBuffer() noexcept
|
||||
{
|
||||
_mouseInputState.inAlternateBuffer = false;
|
||||
}
|
||||
@@ -30,7 +30,6 @@ PRECOMPILED_INCLUDE = ..\precomp.h
|
||||
SOURCES= \
|
||||
..\terminalInput.cpp \
|
||||
..\mouseInput.cpp \
|
||||
..\mouseInputState.cpp \
|
||||
|
||||
INCLUDES = \
|
||||
$(INCLUDES); \
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,26 +47,144 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
AlternateScroll
|
||||
};
|
||||
|
||||
// Kitty keyboard protocol progressive enhancement flags
|
||||
// https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
||||
struct KittyKeyboardProtocolFlags
|
||||
{
|
||||
static constexpr uint8_t None = 0;
|
||||
static constexpr uint8_t DisambiguateEscapeCodes = 1 << 0;
|
||||
static constexpr uint8_t ReportEventTypes = 1 << 1;
|
||||
static constexpr uint8_t ReportAlternateKeys = 1 << 2;
|
||||
static constexpr uint8_t ReportAllKeysAsEscapeCodes = 1 << 3;
|
||||
static constexpr uint8_t ReportAssociatedText = 1 << 4;
|
||||
static constexpr uint8_t All = (1 << 5) - 1;
|
||||
};
|
||||
enum class KittyKeyboardProtocolMode : uint8_t
|
||||
{
|
||||
Replace = 1,
|
||||
Set = 2,
|
||||
Reset = 3,
|
||||
};
|
||||
|
||||
TerminalInput() noexcept;
|
||||
void SetInputMode(const Mode mode, const bool enabled) noexcept;
|
||||
bool GetInputMode(const Mode mode) const noexcept;
|
||||
void UseAlternateScreenBuffer() noexcept;
|
||||
void UseMainScreenBuffer() noexcept;
|
||||
void SetInputMode(Mode mode, bool enabled) noexcept;
|
||||
bool GetInputMode(Mode mode) const noexcept;
|
||||
void ResetInputModes() noexcept;
|
||||
void ForceDisableWin32InputMode(const bool win32InputMode) noexcept;
|
||||
void ForceDisableWin32InputMode(bool win32InputMode) noexcept;
|
||||
void ForceDisableKittyKeyboardProtocol(bool disable) noexcept;
|
||||
|
||||
// Kitty keyboard protocol methods
|
||||
void SetKittyKeyboardProtocol(uint8_t flags, KittyKeyboardProtocolMode mode) noexcept;
|
||||
uint8_t GetKittyFlags() const noexcept;
|
||||
void PushKittyFlags(uint8_t flags);
|
||||
void PopKittyFlags(size_t count);
|
||||
void ResetKittyKeyboardProtocols() noexcept;
|
||||
|
||||
#pragma region MouseInput
|
||||
// These methods are defined in mouseInput.cpp
|
||||
|
||||
bool IsTrackingMouseInput() const noexcept;
|
||||
bool ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region MouseInputState Management
|
||||
// These methods are defined in mouseInputState.cpp
|
||||
void UseAlternateScreenBuffer() noexcept;
|
||||
void UseMainScreenBuffer() noexcept;
|
||||
bool ShouldSendAlternateScroll(unsigned int button, short delta) const noexcept;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
struct CodepointBuffer
|
||||
{
|
||||
wchar_t buf[4];
|
||||
int len;
|
||||
};
|
||||
|
||||
struct EncodingHelper
|
||||
{
|
||||
explicit EncodingHelper()
|
||||
{
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
void disableCtrlAltInKeyboardState() noexcept
|
||||
{
|
||||
keyboardState[VK_CONTROL] = 0;
|
||||
keyboardState[VK_MENU] = 0;
|
||||
keyboardState[VK_LCONTROL] = 0;
|
||||
keyboardState[VK_RCONTROL] = 0;
|
||||
keyboardState[VK_LMENU] = 0;
|
||||
keyboardState[VK_RMENU] = 0;
|
||||
}
|
||||
CodepointBuffer getKeyboardKey(UINT vkey, DWORD controlKeyState, HKL hkl) noexcept
|
||||
{
|
||||
CodepointBuffer cb;
|
||||
|
||||
setupKeyboardState(controlKeyState);
|
||||
|
||||
keyboardState[vkey] = 0x80;
|
||||
cb.len = ToUnicodeEx(vkey, 0, keyboardState, cb.buf, ARRAYSIZE(cb.buf), 4, hkl);
|
||||
keyboardState[vkey] = 0;
|
||||
|
||||
return cb;
|
||||
}
|
||||
HKL getKeyboardLayoutCached() noexcept
|
||||
{
|
||||
if (!keyboardLayoutCached)
|
||||
{
|
||||
keyboardLayout = getKeyboardLayout();
|
||||
keyboardLayoutCached = true;
|
||||
}
|
||||
return keyboardLayout;
|
||||
}
|
||||
static HKL getKeyboardLayout() noexcept
|
||||
{
|
||||
// We need the current keyboard layout and state to look up the character
|
||||
// that would be transmitted in that state (via the ToUnicodeEx API).
|
||||
return GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr));
|
||||
}
|
||||
void setupKeyboardState(DWORD controlKeyState) noexcept
|
||||
{
|
||||
const uint8_t rightAlt = WI_IsFlagSet(controlKeyState, RIGHT_ALT_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t leftAlt = WI_IsFlagSet(controlKeyState, LEFT_ALT_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t rightCtrl = WI_IsFlagSet(controlKeyState, RIGHT_CTRL_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t leftCtrl = WI_IsFlagSet(controlKeyState, LEFT_CTRL_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t leftShift = WI_IsFlagSet(controlKeyState, SHIFT_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t capsLock = WI_IsFlagSet(controlKeyState, CAPSLOCK_ON) ? 0x01 : 0;
|
||||
|
||||
keyboardState[VK_SHIFT] = leftShift;
|
||||
keyboardState[VK_CONTROL] = leftCtrl | rightCtrl;
|
||||
keyboardState[VK_MENU] = leftAlt | rightAlt;
|
||||
keyboardState[VK_CAPITAL] = capsLock;
|
||||
keyboardState[VK_LSHIFT] = leftShift;
|
||||
keyboardState[VK_LCONTROL] = leftCtrl;
|
||||
keyboardState[VK_RCONTROL] = rightCtrl;
|
||||
keyboardState[VK_LMENU] = leftAlt;
|
||||
keyboardState[VK_RMENU] = rightAlt;
|
||||
}
|
||||
|
||||
HKL keyboardLayout;
|
||||
uint8_t keyboardState[256];
|
||||
uint32_t codepointWithoutCtrlAlt;
|
||||
|
||||
bool keyboardLayoutCached;
|
||||
|
||||
// A non-zero csiFinal value indicates that this key
|
||||
// should be encoded as `CSI $csiParam1 ; $csiFinal`.
|
||||
wchar_t csiFinal;
|
||||
// The longest sequence we currently have is Kitty's with 6 parameters:
|
||||
// CSI unicode-key-code:alternate-key-code-shift:alternate-key-code-base ; modifiers:event-type ; text-as-codepoint u
|
||||
// That's 6 parameters, but we can greatly simplify our logic if we just make it 3x3.
|
||||
uint32_t csiParam[3][3];
|
||||
|
||||
// A non-zero ss3Final value indicates that this key
|
||||
// should be encoded as `ESC O $ss3Final`.
|
||||
wchar_t ss3Final;
|
||||
|
||||
// Any other encoding ends up as a non-zero plain value.
|
||||
// For instance, the Tab key gets translated to a plain "\t".
|
||||
std::wstring_view plain;
|
||||
// If true, and Alt is pressed, an ESC prefix should be added to
|
||||
// the final sequence. This only applies to non-KKP encodings.
|
||||
bool plainAltPrefix;
|
||||
};
|
||||
|
||||
// storage location for the leading surrogate of a utf-16 surrogate pair
|
||||
wchar_t _leadingSurrogate = 0;
|
||||
|
||||
@@ -80,24 +198,40 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
til::enumset<Mode> _inputMode{ Mode::Ansi, Mode::AutoRepeat, Mode::AlternateScroll };
|
||||
bool _forceDisableWin32InputMode{ false };
|
||||
bool _inAlternateBuffer{ false };
|
||||
|
||||
const wchar_t* _csi = L"\x1B[";
|
||||
const wchar_t* _ss3 = L"\x1BO";
|
||||
// Kitty keyboard protocol state
|
||||
static constexpr size_t KittyStackMaxSize = 8;
|
||||
bool _forceDisableKittyKeyboardProtocol = false;
|
||||
uint8_t _kittyFlags = 0;
|
||||
std::vector<uint8_t> _kittyMainStack;
|
||||
std::vector<uint8_t> _kittyAltStack;
|
||||
|
||||
static constexpr std::wstring_view _csi{ L"\x1B[" };
|
||||
static constexpr std::wstring_view _ss3{ L"\x1BO" };
|
||||
|
||||
void _initKeyboardMap() noexcept;
|
||||
DWORD _trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept;
|
||||
std::array<byte, 256> _getKeyboardState(const WORD virtualKeyCode, const DWORD controlKeyState) const;
|
||||
[[nodiscard]] static wchar_t _makeCtrlChar(const wchar_t ch);
|
||||
[[nodiscard]] StringType _makeCharOutput(wchar_t ch);
|
||||
static std::array<byte, 256> _getKeyboardState(size_t virtualKeyCode, DWORD controlKeyState);
|
||||
[[nodiscard]] static uint32_t _makeCtrlChar(uint32_t ch) noexcept;
|
||||
[[nodiscard]] static StringType _makeCharOutput(uint32_t ch);
|
||||
[[nodiscard]] static StringType _makeNoOutput() noexcept;
|
||||
[[nodiscard]] void _escapeOutput(StringType& charSequence, const bool altIsPressed) const;
|
||||
[[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key) const;
|
||||
void _fillRegularKeyEncodingInfo(EncodingHelper& enc, const KEY_EVENT_RECORD& key, DWORD simpleKeyState) const noexcept;
|
||||
static uint32_t _getKittyFunctionalKeyCode(UINT vkey, WORD scanCode, DWORD simpleKeyState) noexcept;
|
||||
std::vector<uint8_t>& _getKittyStack() noexcept;
|
||||
static bool _codepointIsText(uint32_t cp) noexcept;
|
||||
static void _stringPushCodepoint(std::wstring& str, uint32_t cp);
|
||||
static CodepointBuffer _codepointToBuffer(uint32_t cp) noexcept;
|
||||
static uint32_t _bufferToCodepoint(const wchar_t* str) noexcept;
|
||||
static uint32_t _codepointToLower(uint32_t cp) noexcept;
|
||||
static uint32_t _bufferToLowerCodepoint(wchar_t* buf, int cap) noexcept;
|
||||
static uint32_t _getBaseLayoutCodepoint(WORD scanCode) noexcept;
|
||||
|
||||
#pragma region MouseInputState Management
|
||||
// These methods are defined in mouseInputState.cpp
|
||||
struct MouseInputState
|
||||
{
|
||||
bool inAlternateBuffer{ false };
|
||||
til::point lastPos{ -1, -1 };
|
||||
unsigned int lastButton{ 0 };
|
||||
int accumulatedDelta{ 0 };
|
||||
@@ -113,7 +247,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
[[nodiscard]] OutputType _makeAlternateScrollOutput(unsigned int button, short delta) const;
|
||||
|
||||
static constexpr unsigned int s_GetPressedButton(const MouseButtonState state) noexcept;
|
||||
static constexpr unsigned int s_GetPressedButton(MouseButtonState state) noexcept;
|
||||
#pragma endregion
|
||||
};
|
||||
}
|
||||
|
||||
@@ -546,6 +546,18 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
|
||||
case CsiActionCodes::ANSISYSRC_CursorRestore:
|
||||
_dispatch->CursorRestoreState();
|
||||
break;
|
||||
case CsiActionCodes::KKP_KittyKeyboardSet:
|
||||
_dispatch->SetKittyKeyboardProtocol(parameters.at(0), parameters.at(1));
|
||||
break;
|
||||
case CsiActionCodes::KKP_KittyKeyboardQuery:
|
||||
_dispatch->QueryKittyKeyboardProtocol();
|
||||
break;
|
||||
case CsiActionCodes::KKP_KittyKeyboardPush:
|
||||
_dispatch->PushKittyKeyboardProtocol(parameters.at(0));
|
||||
break;
|
||||
case CsiActionCodes::KKP_KittyKeyboardPop:
|
||||
_dispatch->PopKittyKeyboardProtocol(parameters.at(0));
|
||||
break;
|
||||
case CsiActionCodes::IL_InsertLine:
|
||||
_dispatch->InsertLine(parameters.at(0));
|
||||
break;
|
||||
|
||||
@@ -136,6 +136,10 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
DECSLRM_SetLeftRightMargins = VTID("s"),
|
||||
DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented.
|
||||
ANSISYSRC_CursorRestore = VTID("u"),
|
||||
KKP_KittyKeyboardSet = VTID("=u"),
|
||||
KKP_KittyKeyboardQuery = VTID("?u"),
|
||||
KKP_KittyKeyboardPush = VTID(">u"),
|
||||
KKP_KittyKeyboardPop = VTID("<u"),
|
||||
DECREQTPARM_RequestTerminalParameters = VTID("x"),
|
||||
PPA_PagePositionAbsolute = VTID(" P"),
|
||||
PPR_PagePositionRelative = VTID(" Q"),
|
||||
|
||||
@@ -316,7 +316,7 @@ namespace fuzz
|
||||
protected:
|
||||
CFuzzBase() :
|
||||
m_fFuzzed(FALSE),
|
||||
m_iPercentageTotal(100){};
|
||||
m_iPercentageTotal(100) {};
|
||||
virtual ~CFuzzBase() = default;
|
||||
|
||||
// Converts a percentage into a valid range. Note that riTotal
|
||||
|
||||
@@ -404,8 +404,8 @@ namespace fuzz
|
||||
}
|
||||
|
||||
private:
|
||||
CFuzzLogic(){};
|
||||
virtual ~CFuzzLogic(){};
|
||||
CFuzzLogic() {};
|
||||
virtual ~CFuzzLogic() {};
|
||||
|
||||
static LPWSTR FuzzStringW_NoRealloc(__inout LPWSTR pwsz, __inout size_t& rcch)
|
||||
{
|
||||
|
||||
16
src/tools/kitty-keyboard-test/Cargo.lock
generated
Normal file
16
src/tools/kitty-keyboard-test/Cargo.lock
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "kitty-keyboard-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
10
src/tools/kitty-keyboard-test/Cargo.toml
Normal file
10
src/tools/kitty-keyboard-test/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "kitty-keyboard-test"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Interactive tester for the Kitty keyboard protocol enhancement flags"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
81
src/tools/kitty-keyboard-test/README.md
Normal file
81
src/tools/kitty-keyboard-test/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Kitty Keyboard Protocol Tester
|
||||
|
||||
An interactive tool for testing the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) enhancement flags.
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Run the tool in a terminal that supports the Kitty keyboard protocol:
|
||||
|
||||
```sh
|
||||
cargo run
|
||||
```
|
||||
|
||||
or after building:
|
||||
|
||||
```sh
|
||||
./target/release/kitty-keyboard-test
|
||||
```
|
||||
|
||||
## Controls
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `1` | Toggle **Disambiguate escape codes** (0b00001) |
|
||||
| `2` | Toggle **Report event types** (0b00010) |
|
||||
| `3` | Toggle **Report alternate keys** (0b00100) |
|
||||
| `4` | Toggle **Report all keys as escape codes** (0b01000) |
|
||||
| `5` | Toggle **Report associated text** (0b10000) |
|
||||
| `q` or `Ctrl+C` | Quit |
|
||||
|
||||
## Enhancement Flags
|
||||
|
||||
1. **Disambiguate escape codes** (bit 0, value 1): Fixes legacy escape code ambiguities. Keys like Esc, Alt+key, Ctrl+key are reported using CSI u sequences.
|
||||
|
||||
2. **Report event types** (bit 1, value 2): Reports key press, repeat, and release events. Without this flag, only press events are reported.
|
||||
|
||||
3. **Report alternate keys** (bit 2, value 4): Reports shifted key and base layout key in addition to the main key code. Useful for shortcut matching across keyboard layouts.
|
||||
|
||||
4. **Report all keys as escape codes** (bit 3, value 8): Even text-producing keys (like regular letters) are reported as escape codes instead of plain text. Required for games and applications that need key events for all keys.
|
||||
|
||||
5. **Report associated text** (bit 4, value 16): When used with flag 4, also reports the text that the key would produce. The text is encoded as Unicode codepoints in the escape sequence.
|
||||
|
||||
## Output Format
|
||||
|
||||
For each key event, the tool displays:
|
||||
- **Raw bytes**: The actual bytes received from the terminal
|
||||
- **Escaped string**: A readable representation of the bytes
|
||||
- **Decoded event**: Human-readable interpretation including key name, modifiers, event type, and any alternate keys or associated text
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
Raw: [0x1b, 0x5b, 0x97, 0x3b, 0x32, 0x3b, 0x41, 0x75] Str: "\x1b[97;2;65u"
|
||||
→ Key: 'a' (97), Event: press, Modifiers: Shift, Text: "A"
|
||||
```
|
||||
|
||||
## Protocol Reference
|
||||
|
||||
- Escape sequence to push keyboard mode: `CSI > flags u`
|
||||
- Escape sequence to pop keyboard mode: `CSI < u`
|
||||
- Key event format: `CSI keycode:shifted:base ; modifiers:event ; text u`
|
||||
|
||||
Modifiers are encoded as `1 + modifier_bits`:
|
||||
- Shift: bit 0 (1)
|
||||
- Alt: bit 1 (2)
|
||||
- Ctrl: bit 2 (4)
|
||||
- Super: bit 3 (8)
|
||||
- Hyper: bit 4 (16)
|
||||
- Meta: bit 5 (32)
|
||||
- CapsLock: bit 6 (64)
|
||||
- NumLock: bit 7 (128)
|
||||
|
||||
Event types:
|
||||
- Press: 1 (default if omitted)
|
||||
- Repeat: 2
|
||||
- Release: 3
|
||||
902
src/tools/kitty-keyboard-test/src/main.rs
Normal file
902
src/tools/kitty-keyboard-test/src/main.rs
Normal file
@@ -0,0 +1,902 @@
|
||||
//! Interactive tester for the Kitty Keyboard Protocol.
|
||||
//!
|
||||
//! This tool allows you to toggle the 5 enhancement flags and see how key events
|
||||
//! are encoded by the terminal emulator.
|
||||
//!
|
||||
//! Shortcuts:
|
||||
//! 1-5: Toggle enhancement flags
|
||||
//! q/Ctrl+C: Quit
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
// Enhancement flags
|
||||
const FLAG_DISAMBIGUATE: u8 = 0b00001; // 1
|
||||
const FLAG_EVENT_TYPES: u8 = 0b00010; // 2
|
||||
const FLAG_ALTERNATE_KEYS: u8 = 0b00100; // 4
|
||||
const FLAG_ALL_AS_ESCAPES: u8 = 0b01000; // 8
|
||||
const FLAG_ASSOCIATED_TEXT: u8 = 0b10000; // 16
|
||||
|
||||
// Modifier bits (value is encoded as 1 + modifiers in the protocol)
|
||||
const MOD_SHIFT: u8 = 0b00000001;
|
||||
const MOD_ALT: u8 = 0b00000010;
|
||||
const MOD_CTRL: u8 = 0b00000100;
|
||||
const MOD_SUPER: u8 = 0b00001000;
|
||||
const MOD_HYPER: u8 = 0b00010000;
|
||||
const MOD_META: u8 = 0b00100000;
|
||||
const MOD_CAPS_LOCK: u8 = 0b01000000;
|
||||
const MOD_NUM_LOCK: u8 = 0b10000000;
|
||||
|
||||
fn main() {
|
||||
let mut terminal = Terminal::new().expect("Failed to initialize terminal");
|
||||
let mut output = String::with_capacity(4096);
|
||||
|
||||
// Detect if terminal supports Kitty keyboard protocol
|
||||
// Send CSI ? u (query flags) followed by CSI c (DA1)
|
||||
terminal.write(b"\x1b[?u\x1b[c");
|
||||
|
||||
let protocol_supported = detect_protocol_support(&mut terminal);
|
||||
|
||||
let mut flags: u8 = 0;
|
||||
|
||||
if protocol_supported {
|
||||
write_flags(&mut output, flags);
|
||||
} else {
|
||||
let _ = write!(
|
||||
output,
|
||||
"\x1b[1;33mNote:\x1b[m Terminal does not support Kitty keyboard protocol.\r\n"
|
||||
);
|
||||
let _ = write!(
|
||||
output,
|
||||
" Key events will be shown in legacy format only.\r\n\r\n"
|
||||
);
|
||||
}
|
||||
write_help(&mut output, protocol_supported);
|
||||
terminal.write(output.as_bytes());
|
||||
output.clear();
|
||||
|
||||
if protocol_supported {
|
||||
// Push initial flags (0) onto the stack
|
||||
write_push_keyboard_mode(&mut output, flags);
|
||||
terminal.write(output.as_bytes());
|
||||
output.clear();
|
||||
}
|
||||
|
||||
let mut buf = [0u8; 64];
|
||||
loop {
|
||||
let n = terminal.read(&mut buf);
|
||||
if n == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let input = &buf[..n];
|
||||
|
||||
// Ctrl+C --> Exit
|
||||
if input == b"\x03" || input == b"\x1b[99;5u" {
|
||||
break;
|
||||
}
|
||||
|
||||
if protocol_supported {
|
||||
let flag_to_toggle = match input {
|
||||
b"1" | b"\x1b[49u" | b"\x1b[49;;49u" => Some(FLAG_DISAMBIGUATE),
|
||||
b"2" | b"\x1b[50u" | b"\x1b[50;;50u" => Some(FLAG_EVENT_TYPES),
|
||||
b"3" | b"\x1b[51u" | b"\x1b[51;;51u" => Some(FLAG_ALTERNATE_KEYS),
|
||||
b"4" | b"\x1b[52u" | b"\x1b[52;;52u" => Some(FLAG_ALL_AS_ESCAPES),
|
||||
b"5" | b"\x1b[53u" | b"\x1b[53;;53u" => Some(FLAG_ASSOCIATED_TEXT),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(flag) = flag_to_toggle {
|
||||
flags ^= flag;
|
||||
write_set_keyboard_mode(&mut output, flags);
|
||||
write_flags(&mut output, flags);
|
||||
write_help(&mut output, protocol_supported);
|
||||
terminal.write(output.as_bytes());
|
||||
output.clear();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
write_decoded_input(&mut output, input);
|
||||
terminal.write(output.as_bytes());
|
||||
output.clear();
|
||||
}
|
||||
|
||||
if protocol_supported {
|
||||
write_pop_keyboard_mode(&mut output);
|
||||
terminal.write(output.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect Kitty keyboard protocol support by looking for CSI ? <num> u response
|
||||
/// before the DA1 response (CSI ... c).
|
||||
fn detect_protocol_support(terminal: &mut Terminal) -> bool {
|
||||
let mut buf = [0u8; 256];
|
||||
let mut response = Vec::new();
|
||||
let mut got_kitty_response = false;
|
||||
|
||||
// Read until we see the DA1 response terminator 'c'
|
||||
loop {
|
||||
let n = terminal.read(&mut buf);
|
||||
response.extend_from_slice(&buf[..n]);
|
||||
|
||||
// Parse the accumulated response
|
||||
let mut i = 0;
|
||||
while i < response.len() {
|
||||
if response[i] == 0x1b && i + 1 < response.len() && response[i + 1] == b'[' {
|
||||
// Found CSI, look for the terminator
|
||||
if let Some(end) = find_csi_end(&response[i + 2..]) {
|
||||
let seq_end = i + 2 + end;
|
||||
let params = &response[i + 2..seq_end];
|
||||
let terminator = response[seq_end];
|
||||
|
||||
if terminator == b'u' && params.starts_with(b"?") {
|
||||
// CSI ? <num> u - Kitty keyboard query response
|
||||
got_kitty_response = true;
|
||||
} else if terminator == b'c' {
|
||||
// DA1 response - we're done
|
||||
return got_kitty_response;
|
||||
}
|
||||
|
||||
i = seq_end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the end of CSI parameters (returns index of terminator byte)
|
||||
fn find_csi_end(data: &[u8]) -> Option<usize> {
|
||||
for (i, &b) in data.iter().enumerate() {
|
||||
// CSI terminators are in the range 0x40-0x7E
|
||||
if (0x40..=0x7E).contains(&b) {
|
||||
return Some(i);
|
||||
}
|
||||
// Parameters and intermediates are in 0x20-0x3F range
|
||||
if !((0x20..=0x3F).contains(&b)) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn write_flags(out: &mut String, flags: u8) {
|
||||
let _ = write!(out, "\x1b[1mEnhancement Flags:\x1b[m\r\n");
|
||||
let _ = write!(
|
||||
out,
|
||||
" [{}] \x1b[33m1\x1b[m: Disambiguate escape codes (0b00001)\r\n",
|
||||
if flags & FLAG_DISAMBIGUATE != 0 {
|
||||
"\x1b[32m✓\x1b[m"
|
||||
} else {
|
||||
" "
|
||||
}
|
||||
);
|
||||
let _ = write!(
|
||||
out,
|
||||
" [{}] \x1b[33m2\x1b[m: Report event types (0b00010)\r\n",
|
||||
if flags & FLAG_EVENT_TYPES != 0 {
|
||||
"\x1b[32m✓\x1b[m"
|
||||
} else {
|
||||
" "
|
||||
}
|
||||
);
|
||||
let _ = write!(
|
||||
out,
|
||||
" [{}] \x1b[33m3\x1b[m: Report alternate keys (0b00100)\r\n",
|
||||
if flags & FLAG_ALTERNATE_KEYS != 0 {
|
||||
"\x1b[32m✓\x1b[m"
|
||||
} else {
|
||||
" "
|
||||
}
|
||||
);
|
||||
let _ = write!(
|
||||
out,
|
||||
" [{}] \x1b[33m4\x1b[m: Report all keys as escapes (0b01000)\r\n",
|
||||
if flags & FLAG_ALL_AS_ESCAPES != 0 {
|
||||
"\x1b[32m✓\x1b[m"
|
||||
} else {
|
||||
" "
|
||||
}
|
||||
);
|
||||
let _ = write!(
|
||||
out,
|
||||
" [{}] \x1b[33m5\x1b[m: Report associated text (0b10000)\r\n",
|
||||
if flags & FLAG_ASSOCIATED_TEXT != 0 {
|
||||
"\x1b[32m✓\x1b[m"
|
||||
} else {
|
||||
" "
|
||||
}
|
||||
);
|
||||
let _ = write!(out, "\r\n");
|
||||
let _ = write!(
|
||||
out,
|
||||
" \x1b[1mCurrent flags value:\x1b[m \x1b[36m{}\x1b[m (0b{:05b})\r\n",
|
||||
flags, flags
|
||||
);
|
||||
let _ = write!(out, "\r\n");
|
||||
}
|
||||
|
||||
fn write_help(out: &mut String, protocol_supported: bool) {
|
||||
let _ = write!(
|
||||
out,
|
||||
"\x1b[90m────────────────────────────────────────────────────────\x1b[m\r\n"
|
||||
);
|
||||
if protocol_supported {
|
||||
let _ = write!(out, "\x1b[1mControls:\x1b[m Press \x1b[33m1-5\x1b[m to toggle flags, \x1b[33mCtrl+C\x1b[m to quit\r\n");
|
||||
} else {
|
||||
let _ = write!(
|
||||
out,
|
||||
"\x1b[1mControls:\x1b[m Press \x1b[33mCtrl+C\x1b[m to quit\r\n"
|
||||
);
|
||||
}
|
||||
let _ = write!(
|
||||
out,
|
||||
"\x1b[90m────────────────────────────────────────────────────────\x1b[m\r\n"
|
||||
);
|
||||
let _ = write!(out, "\r\n");
|
||||
let _ = write!(out, "\x1b[1mKey events:\x1b[m\r\n");
|
||||
let _ = write!(out, "\r\n");
|
||||
}
|
||||
|
||||
fn write_push_keyboard_mode(out: &mut String, flags: u8) {
|
||||
// CSI > flags u - Push flags onto the stack
|
||||
let _ = write!(out, "\x1b[>{}u", flags);
|
||||
}
|
||||
|
||||
fn write_set_keyboard_mode(out: &mut String, flags: u8) {
|
||||
// CSI = flags ; 1 u - Set flags (mode 1 = replace all)
|
||||
let _ = write!(out, "\x1b[={};1u", flags);
|
||||
}
|
||||
|
||||
fn write_pop_keyboard_mode(out: &mut String) {
|
||||
// CSI < u - Pop from the stack (restores previous mode)
|
||||
let _ = write!(out, "\x1b[<u");
|
||||
}
|
||||
|
||||
fn write_decoded_input(out: &mut String, input: &[u8]) {
|
||||
let _ = write!(out, "\x1b[37m\"");
|
||||
|
||||
// Print as escaped string
|
||||
for &b in input {
|
||||
match b {
|
||||
0x1b => {
|
||||
let _ = write!(out, "\\x1b");
|
||||
}
|
||||
0x00..=0x1f => {
|
||||
let _ = write!(out, "\\x{:02x}", b);
|
||||
}
|
||||
0x7f => {
|
||||
let _ = write!(out, "\\x7f");
|
||||
}
|
||||
_ => {
|
||||
let _ = write!(out, "{}", b as char);
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = write!(out, "\"");
|
||||
|
||||
// Try to decode as Kitty protocol
|
||||
if let Some(decoded) = decode_kitty_sequence(input) {
|
||||
let _ = write!(out, "\x1b[m\r\n \x1b[1;32m→ {}\x1b[m", decoded);
|
||||
} else if let Some(decoded) = decode_legacy_sequence(input) {
|
||||
let _ = write!(
|
||||
out,
|
||||
"\x1b[m\r\n \x1b[1;33m→ {} (legacy)\x1b[m",
|
||||
decoded
|
||||
);
|
||||
} else if input.len() == 1 && input[0] >= 0x20 && input[0] < 0x7f {
|
||||
let _ = write!(
|
||||
out,
|
||||
"\x1b[m\r\n \x1b[1;34m→ Character: '{}'\x1b[m",
|
||||
input[0] as char
|
||||
);
|
||||
} else if input.len() == 1 {
|
||||
if let Some(name) = control_char_name(input[0]) {
|
||||
let _ = write!(out, "\x1b[m\r\n \x1b[1;35m→ {}\x1b[m", name);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = write!(out, "\x1b[m\r\n");
|
||||
}
|
||||
|
||||
fn decode_kitty_sequence(input: &[u8]) -> Option<String> {
|
||||
// Check for CSI ... u format
|
||||
if input.len() < 3 || input[0] != 0x1b || input[1] != b'[' {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rest = &input[2..];
|
||||
|
||||
// CSI ? flags u - Query response
|
||||
if rest.starts_with(b"?") && rest.ends_with(b"u") {
|
||||
let num_str = std::str::from_utf8(&rest[1..rest.len() - 1]).ok()?;
|
||||
if let Ok(flags) = num_str.parse::<u8>() {
|
||||
return Some(format!("Query response: flags={} (0b{:05b})", flags, flags));
|
||||
}
|
||||
}
|
||||
|
||||
// CSI ... u - Key event
|
||||
if rest.ends_with(b"u") {
|
||||
return decode_csi_u_sequence(&rest[..rest.len() - 1]);
|
||||
}
|
||||
|
||||
// CSI ... ~ - Legacy functional key with possible kitty extensions
|
||||
if rest.ends_with(b"~") {
|
||||
return decode_csi_tilde_sequence(&rest[..rest.len() - 1]);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Parse "modifiers:event_type" parameter, returns (modifiers, event_type)
|
||||
fn parse_modifiers_and_event(mod_part: Option<&str>) -> (u8, u8) {
|
||||
if let Some(mod_part) = mod_part {
|
||||
let mut mod_parts = mod_part.split(':');
|
||||
let mods: u8 = mod_parts
|
||||
.next()
|
||||
.and_then(|s| s.parse::<u8>().ok())
|
||||
.unwrap_or(1)
|
||||
.saturating_sub(1);
|
||||
let evt: u8 = mod_parts.next().and_then(|s| s.parse().ok()).unwrap_or(1);
|
||||
(mods, evt)
|
||||
} else {
|
||||
(0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a codepoint as a readable key name
|
||||
fn format_codepoint(code: u32) -> String {
|
||||
if let Some(c) = char::from_u32(code) {
|
||||
if c.is_control() {
|
||||
format!("{}", code)
|
||||
} else {
|
||||
format!("'{}' ({})", c, code)
|
||||
}
|
||||
} else {
|
||||
format!("{}", code)
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_csi_u_sequence(params: &[u8]) -> Option<String> {
|
||||
let params_str = std::str::from_utf8(params).ok()?;
|
||||
let mut parts = params_str.split(';');
|
||||
|
||||
// Parse key code (may have alternate keys separated by colons)
|
||||
let key_part = parts.next()?;
|
||||
let mut key_codes = key_part.split(':');
|
||||
let key_code: u32 = key_codes.next()?.parse().ok()?;
|
||||
let shifted_key: Option<u32> =
|
||||
key_codes
|
||||
.next()
|
||||
.and_then(|s| if s.is_empty() { None } else { s.parse().ok() });
|
||||
let base_layout_key: Option<u32> = key_codes.next().and_then(|s| s.parse().ok());
|
||||
|
||||
let (modifiers, event_type) = parse_modifiers_and_event(parts.next());
|
||||
|
||||
// Parse associated text
|
||||
let text_codepoints: Option<String> = parts.next().map(|text_part| {
|
||||
text_part
|
||||
.split(':')
|
||||
.filter_map(|s| s.parse::<u32>().ok())
|
||||
.filter_map(char::from_u32)
|
||||
.collect()
|
||||
});
|
||||
|
||||
// Build result
|
||||
let key_name = key_code_to_name(key_code);
|
||||
let event_name = match event_type {
|
||||
1 => "press",
|
||||
2 => "repeat",
|
||||
3 => "release",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
let mut result = format!("Key: {} ({}), Event: {}", key_name, key_code, event_name);
|
||||
|
||||
if modifiers != 0 {
|
||||
result.push_str(&format!(", Modifiers: {}", modifiers_to_string(modifiers)));
|
||||
}
|
||||
|
||||
if let Some(shifted) = shifted_key {
|
||||
result.push_str(&format!(", Shifted: {}", format_codepoint(shifted)));
|
||||
}
|
||||
|
||||
if let Some(base) = base_layout_key {
|
||||
result.push_str(&format!(", Base layout: {}", format_codepoint(base)));
|
||||
}
|
||||
|
||||
if let Some(text) = text_codepoints {
|
||||
if !text.is_empty() {
|
||||
result.push_str(&format!(", Text: \"{}\"", text));
|
||||
}
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn decode_csi_tilde_sequence(params: &[u8]) -> Option<String> {
|
||||
let params_str = std::str::from_utf8(params).ok()?;
|
||||
let mut parts = params_str.split(';');
|
||||
|
||||
let key_num: u32 = parts.next()?.parse().ok()?;
|
||||
let (modifiers, event_type) = parse_modifiers_and_event(parts.next());
|
||||
|
||||
let key_name = match key_num {
|
||||
2 => "Insert",
|
||||
3 => "Delete",
|
||||
5 => "PageUp",
|
||||
6 => "PageDown",
|
||||
7 => "Home",
|
||||
8 => "End",
|
||||
11 => "F1",
|
||||
12 => "F2",
|
||||
13 => "F3",
|
||||
14 => "F4",
|
||||
15 => "F5",
|
||||
17 => "F6",
|
||||
18 => "F7",
|
||||
19 => "F8",
|
||||
20 => "F9",
|
||||
21 => "F10",
|
||||
23 => "F11",
|
||||
24 => "F12",
|
||||
29 => "Menu",
|
||||
_ => return Some(format!("Unknown functional key: {}", key_num)),
|
||||
};
|
||||
|
||||
let event_name = match event_type {
|
||||
1 => "press",
|
||||
2 => "repeat",
|
||||
3 => "release",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
let mut result = format!("Key: {}, Event: {}", key_name, event_name);
|
||||
if modifiers != 0 {
|
||||
result.push_str(&format!(", Modifiers: {}", modifiers_to_string(modifiers)));
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn decode_legacy_sequence(input: &[u8]) -> Option<String> {
|
||||
if input.len() < 2 || input[0] != 0x1b {
|
||||
return None;
|
||||
}
|
||||
|
||||
// SS3 sequences (ESC O ...)
|
||||
if input.len() >= 3 && input[1] == b'O' {
|
||||
let key = match input[2] {
|
||||
b'A' => "Up",
|
||||
b'B' => "Down",
|
||||
b'C' => "Right",
|
||||
b'D' => "Left",
|
||||
b'H' => "Home",
|
||||
b'F' => "End",
|
||||
b'P' => "F1",
|
||||
b'Q' => "F2",
|
||||
b'R' => "F3",
|
||||
b'S' => "F4",
|
||||
_ => return None,
|
||||
};
|
||||
return Some(format!("Key: {} (SS3)", key));
|
||||
}
|
||||
|
||||
// CSI sequences
|
||||
if input.len() >= 3 && input[1] == b'[' {
|
||||
let rest = &input[2..];
|
||||
|
||||
// CSI letter - simple cursor keys
|
||||
if rest.len() == 1 {
|
||||
let key = match rest[0] {
|
||||
b'A' => "Up",
|
||||
b'B' => "Down",
|
||||
b'C' => "Right",
|
||||
b'D' => "Left",
|
||||
b'H' => "Home",
|
||||
b'F' => "End",
|
||||
b'Z' => "Shift+Tab",
|
||||
_ => return None,
|
||||
};
|
||||
return Some(format!("Key: {}", key));
|
||||
}
|
||||
|
||||
// CSI 1 ; modifier letter
|
||||
if rest.len() >= 4 && rest[0] == b'1' && rest[1] == b';' {
|
||||
if let Ok(mod_str) = std::str::from_utf8(&rest[2..rest.len() - 1]) {
|
||||
if let Ok(mod_val) = mod_str.parse::<u8>() {
|
||||
let modifiers = mod_val.saturating_sub(1);
|
||||
let key = match rest[rest.len() - 1] {
|
||||
b'A' => "Up",
|
||||
b'B' => "Down",
|
||||
b'C' => "Right",
|
||||
b'D' => "Left",
|
||||
b'H' => "Home",
|
||||
b'F' => "End",
|
||||
b'P' => "F1",
|
||||
b'Q' => "F2",
|
||||
b'S' => "F4",
|
||||
_ => return None,
|
||||
};
|
||||
return Some(format!(
|
||||
"Key: {}, Modifiers: {}",
|
||||
key,
|
||||
modifiers_to_string(modifiers)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alt + key
|
||||
if input.len() == 2 && input[1] >= 0x20 && input[1] < 0x7f {
|
||||
return Some(format!("Alt+'{}'", input[1] as char));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn key_code_to_name(code: u32) -> String {
|
||||
match code {
|
||||
9 => "Tab".to_string(),
|
||||
13 => "Enter".to_string(),
|
||||
27 => "Escape".to_string(),
|
||||
32 => "Space".to_string(),
|
||||
127 => "Backspace".to_string(),
|
||||
|
||||
// Functional keys in Private Use Area
|
||||
57358 => "CapsLock".to_string(),
|
||||
57359 => "ScrollLock".to_string(),
|
||||
57360 => "NumLock".to_string(),
|
||||
57361 => "PrintScreen".to_string(),
|
||||
57362 => "Pause".to_string(),
|
||||
57363 => "Menu".to_string(),
|
||||
|
||||
57376..=57398 => format!("F{}", code - 57376 + 13), // F13-F35
|
||||
|
||||
57399..=57408 => format!("KP_{}", code - 57399), // KP_0 - KP_9
|
||||
57409 => "KP_Decimal".to_string(),
|
||||
57410 => "KP_Divide".to_string(),
|
||||
57411 => "KP_Multiply".to_string(),
|
||||
57412 => "KP_Subtract".to_string(),
|
||||
57413 => "KP_Add".to_string(),
|
||||
57414 => "KP_Enter".to_string(),
|
||||
57415 => "KP_Equal".to_string(),
|
||||
57416 => "KP_Separator".to_string(),
|
||||
57417 => "KP_Left".to_string(),
|
||||
57418 => "KP_Right".to_string(),
|
||||
57419 => "KP_Up".to_string(),
|
||||
57420 => "KP_Down".to_string(),
|
||||
57421 => "KP_PageUp".to_string(),
|
||||
57422 => "KP_PageDown".to_string(),
|
||||
57423 => "KP_Home".to_string(),
|
||||
57424 => "KP_End".to_string(),
|
||||
57425 => "KP_Insert".to_string(),
|
||||
57426 => "KP_Delete".to_string(),
|
||||
57427 => "KP_Begin".to_string(),
|
||||
|
||||
57428 => "MediaPlay".to_string(),
|
||||
57429 => "MediaPause".to_string(),
|
||||
57430 => "MediaPlayPause".to_string(),
|
||||
57431 => "MediaReverse".to_string(),
|
||||
57432 => "MediaStop".to_string(),
|
||||
57433 => "MediaFastForward".to_string(),
|
||||
57434 => "MediaRewind".to_string(),
|
||||
57435 => "MediaTrackNext".to_string(),
|
||||
57436 => "MediaTrackPrevious".to_string(),
|
||||
57437 => "MediaRecord".to_string(),
|
||||
57438 => "LowerVolume".to_string(),
|
||||
57439 => "RaiseVolume".to_string(),
|
||||
57440 => "MuteVolume".to_string(),
|
||||
|
||||
57441 => "LeftShift".to_string(),
|
||||
57442 => "LeftControl".to_string(),
|
||||
57443 => "LeftAlt".to_string(),
|
||||
57444 => "LeftSuper".to_string(),
|
||||
57445 => "LeftHyper".to_string(),
|
||||
57446 => "LeftMeta".to_string(),
|
||||
57447 => "RightShift".to_string(),
|
||||
57448 => "RightControl".to_string(),
|
||||
57449 => "RightAlt".to_string(),
|
||||
57450 => "RightSuper".to_string(),
|
||||
57451 => "RightHyper".to_string(),
|
||||
57452 => "RightMeta".to_string(),
|
||||
57453 => "IsoLevel3Shift".to_string(),
|
||||
57454 => "IsoLevel5Shift".to_string(),
|
||||
|
||||
// Regular characters
|
||||
c if (0x20..0x7f).contains(&c) => format!("'{}'", char::from_u32(c).unwrap()),
|
||||
c => {
|
||||
if let Some(ch) = char::from_u32(c) {
|
||||
format!("'{}' (U+{:04X})", ch, c)
|
||||
} else {
|
||||
format!("U+{:04X}", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn modifiers_to_string(mods: u8) -> String {
|
||||
let mut parts = Vec::new();
|
||||
if mods & MOD_SHIFT != 0 {
|
||||
parts.push("Shift");
|
||||
}
|
||||
if mods & MOD_ALT != 0 {
|
||||
parts.push("Alt");
|
||||
}
|
||||
if mods & MOD_CTRL != 0 {
|
||||
parts.push("Ctrl");
|
||||
}
|
||||
if mods & MOD_SUPER != 0 {
|
||||
parts.push("Super");
|
||||
}
|
||||
if mods & MOD_HYPER != 0 {
|
||||
parts.push("Hyper");
|
||||
}
|
||||
if mods & MOD_META != 0 {
|
||||
parts.push("Meta");
|
||||
}
|
||||
if mods & MOD_CAPS_LOCK != 0 {
|
||||
parts.push("CapsLock");
|
||||
}
|
||||
if mods & MOD_NUM_LOCK != 0 {
|
||||
parts.push("NumLock");
|
||||
}
|
||||
if parts.is_empty() {
|
||||
"None".to_string()
|
||||
} else {
|
||||
parts.join("+")
|
||||
}
|
||||
}
|
||||
|
||||
fn control_char_name(b: u8) -> Option<&'static str> {
|
||||
match b {
|
||||
0x00 => Some("Ctrl+Space (NUL)"),
|
||||
0x01 => Some("Ctrl+A"),
|
||||
0x02 => Some("Ctrl+B"),
|
||||
0x03 => Some("Ctrl+C"),
|
||||
0x04 => Some("Ctrl+D"),
|
||||
0x05 => Some("Ctrl+E"),
|
||||
0x06 => Some("Ctrl+F"),
|
||||
0x07 => Some("Ctrl+G (BEL)"),
|
||||
0x08 => Some("Ctrl+H (Backspace)"),
|
||||
0x09 => Some("Tab"),
|
||||
0x0a => Some("Ctrl+J (Line Feed)"),
|
||||
0x0b => Some("Ctrl+K"),
|
||||
0x0c => Some("Ctrl+L"),
|
||||
0x0d => Some("Enter"),
|
||||
0x0e => Some("Ctrl+N"),
|
||||
0x0f => Some("Ctrl+O"),
|
||||
0x10 => Some("Ctrl+P"),
|
||||
0x11 => Some("Ctrl+Q"),
|
||||
0x12 => Some("Ctrl+R"),
|
||||
0x13 => Some("Ctrl+S"),
|
||||
0x14 => Some("Ctrl+T"),
|
||||
0x15 => Some("Ctrl+U"),
|
||||
0x16 => Some("Ctrl+V"),
|
||||
0x17 => Some("Ctrl+W"),
|
||||
0x18 => Some("Ctrl+X"),
|
||||
0x19 => Some("Ctrl+Y"),
|
||||
0x1a => Some("Ctrl+Z"),
|
||||
0x1b => Some("Escape"),
|
||||
0x1c => Some("Ctrl+\\"),
|
||||
0x1d => Some("Ctrl+]"),
|
||||
0x1e => Some("Ctrl+^"),
|
||||
0x1f => Some("Ctrl+_"),
|
||||
0x7f => Some("Backspace (DEL)"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Platform-specific terminal handling
|
||||
|
||||
#[cfg(unix)]
|
||||
mod platform {
|
||||
use std::io;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
const STDIN_FILENO: libc::c_int = 0;
|
||||
const STDOUT_FILENO: libc::c_int = 1;
|
||||
|
||||
pub struct Terminal {
|
||||
original_termios: libc::termios,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let mut termios = MaybeUninit::uninit();
|
||||
|
||||
unsafe {
|
||||
if libc::tcgetattr(STDIN_FILENO, termios.as_mut_ptr()) != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
let original_termios = unsafe { termios.assume_init() };
|
||||
let mut raw = original_termios;
|
||||
|
||||
// Set raw mode
|
||||
raw.c_lflag &= !(libc::ECHO | libc::ICANON | libc::ISIG | libc::IEXTEN);
|
||||
raw.c_iflag &= !(libc::IXON | libc::ICRNL | libc::BRKINT | libc::INPCK | libc::ISTRIP);
|
||||
raw.c_oflag &= !libc::OPOST;
|
||||
|
||||
unsafe {
|
||||
if libc::tcsetattr(STDIN_FILENO, libc::TCSAFLUSH, &raw) != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Terminal { original_termios })
|
||||
}
|
||||
|
||||
pub fn read(&mut self, buf: &mut [u8]) -> usize {
|
||||
unsafe {
|
||||
let n = libc::read(
|
||||
STDIN_FILENO,
|
||||
buf.as_mut_ptr() as *mut libc::c_void,
|
||||
buf.len(),
|
||||
);
|
||||
if n < 0 {
|
||||
0
|
||||
} else {
|
||||
n as usize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, buf: &[u8]) {
|
||||
unsafe {
|
||||
libc::write(
|
||||
STDOUT_FILENO,
|
||||
buf.as_ptr() as *const libc::c_void,
|
||||
buf.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Terminal {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
libc::tcsetattr(STDIN_FILENO, libc::TCSAFLUSH, &self.original_termios);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
|
||||
#[cfg(windows)]
|
||||
mod platform {
|
||||
use std::io;
|
||||
|
||||
type BOOL = i32;
|
||||
type HANDLE = *mut core::ffi::c_void;
|
||||
type CONSOLE_MODE = u32;
|
||||
type STD_HANDLE = u32;
|
||||
|
||||
const STD_INPUT_HANDLE: STD_HANDLE = 0xFFFFFFF6;
|
||||
const STD_OUTPUT_HANDLE: STD_HANDLE = 0xFFFFFFF5;
|
||||
const ENABLE_PROCESSED_OUTPUT: CONSOLE_MODE = 1u32;
|
||||
const ENABLE_WRAP_AT_EOL_OUTPUT: CONSOLE_MODE = 2u32;
|
||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: CONSOLE_MODE = 4u32;
|
||||
const DISABLE_NEWLINE_AUTO_RETURN: CONSOLE_MODE = 8u32;
|
||||
const ENABLE_VIRTUAL_TERMINAL_INPUT: CONSOLE_MODE = 512u32;
|
||||
const CP_UTF8: u32 = 65001;
|
||||
|
||||
unsafe extern "system" {
|
||||
fn ReadFile(
|
||||
hfile: HANDLE,
|
||||
lpbuffer: *mut u8,
|
||||
nnumberofbytestoread: u32,
|
||||
lpnumberofbytesread: *mut u32,
|
||||
lpoverlapped: *mut (),
|
||||
) -> BOOL;
|
||||
|
||||
fn WriteFile(
|
||||
hfile: HANDLE,
|
||||
lpbuffer: *const u8,
|
||||
nnumberofbytestowrite: u32,
|
||||
lpnumberofbyteswritten: *mut u32,
|
||||
lpoverlapped: *mut (),
|
||||
) -> BOOL;
|
||||
|
||||
fn GetStdHandle(nstdhandle: STD_HANDLE) -> HANDLE;
|
||||
fn GetConsoleMode(hconsolehandle: HANDLE, lpmode: *mut CONSOLE_MODE) -> BOOL;
|
||||
fn SetConsoleMode(hconsolehandle: HANDLE, dwmode: CONSOLE_MODE) -> BOOL;
|
||||
fn GetConsoleCP() -> u32;
|
||||
fn SetConsoleCP(wcodepageid: u32) -> BOOL;
|
||||
fn GetConsoleOutputCP() -> u32;
|
||||
fn SetConsoleOutputCP(wcodepageid: u32) -> BOOL;
|
||||
}
|
||||
|
||||
pub struct Terminal {
|
||||
stdin_handle: HANDLE,
|
||||
stdout_handle: HANDLE,
|
||||
stdin_mode: CONSOLE_MODE,
|
||||
stdout_mode: CONSOLE_MODE,
|
||||
stdin_cp: u32,
|
||||
stdout_cp: u32,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
unsafe {
|
||||
let stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
|
||||
let stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
let mut stdin_mode: CONSOLE_MODE = 0;
|
||||
let mut stdout_mode: CONSOLE_MODE = 0;
|
||||
GetConsoleMode(stdin_handle, &mut stdin_mode);
|
||||
GetConsoleMode(stdin_handle, &mut stdout_mode);
|
||||
|
||||
SetConsoleMode(stdin_handle, ENABLE_VIRTUAL_TERMINAL_INPUT);
|
||||
SetConsoleMode(
|
||||
stdout_handle,
|
||||
ENABLE_PROCESSED_OUTPUT
|
||||
| ENABLE_WRAP_AT_EOL_OUTPUT
|
||||
| ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
| DISABLE_NEWLINE_AUTO_RETURN,
|
||||
);
|
||||
|
||||
let stdin_cp = GetConsoleCP();
|
||||
let stdout_cp = GetConsoleOutputCP();
|
||||
SetConsoleCP(CP_UTF8);
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
|
||||
Ok(Terminal {
|
||||
stdin_handle,
|
||||
stdout_handle,
|
||||
stdin_mode,
|
||||
stdout_mode,
|
||||
stdin_cp,
|
||||
stdout_cp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&mut self, buf: &mut [u8]) -> usize {
|
||||
unsafe {
|
||||
let mut bytes_read: u32 = 0;
|
||||
if ReadFile(
|
||||
self.stdin_handle,
|
||||
buf.as_mut_ptr() as *mut _,
|
||||
buf.len() as u32,
|
||||
&mut bytes_read,
|
||||
std::ptr::null_mut(),
|
||||
) == 0
|
||||
{
|
||||
0
|
||||
} else {
|
||||
bytes_read as usize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, buf: &[u8]) {
|
||||
unsafe {
|
||||
let mut bytes_written: u32 = 0;
|
||||
WriteFile(
|
||||
self.stdout_handle,
|
||||
buf.as_ptr() as *const _,
|
||||
buf.len() as u32,
|
||||
&mut bytes_written,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Terminal {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
SetConsoleMode(self.stdin_handle, self.stdin_mode);
|
||||
SetConsoleMode(self.stdout_handle, self.stdout_mode);
|
||||
SetConsoleCP(self.stdin_cp);
|
||||
SetConsoleOutputCP(self.stdout_cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use platform::Terminal;
|
||||
1
src/tools/kitty-keyboard-test/target/.rustc_info.json
Normal file
1
src/tools/kitty-keyboard-test/target/.rustc_info.json
Normal file
@@ -0,0 +1 @@
|
||||
{"rustc_fingerprint":15155536823543150984,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\lhecker\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\nemscripten_wasm_eh\nfmt_debug=\"full\"\noverflow_checks\npanic=\"unwind\"\nproc_macro\nrelocation_model=\"pic\"\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"lahfsahf\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"x87\"\ntarget_has_atomic\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"128\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"128\"\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_has_reliable_f128\ntarget_has_reliable_f16\ntarget_has_reliable_f16_math\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"pc\"\nub_checks\nwindows\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.95.0-nightly (873d4682c 2026-01-25)\nbinary: rustc\ncommit-hash: 873d4682c7d285540b8f28bfe637006cef8918a6\ncommit-date: 2026-01-25\nhost: x86_64-pc-windows-msvc\nrelease: 1.95.0-nightly\nLLVM version: 21.1.8\n","stderr":""}},"successes":{}}
|
||||
3
src/tools/kitty-keyboard-test/target/CACHEDIR.TAG
Normal file
3
src/tools/kitty-keyboard-test/target/CACHEDIR.TAG
Normal file
@@ -0,0 +1,3 @@
|
||||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by cargo.
|
||||
# For information about cache directory tags see https://bford.info/cachedir/
|
||||
12
src/tools/kitty-keyboard-test/target/flycheck0/stderr
Normal file
12
src/tools/kitty-keyboard-test/target/flycheck0/stderr
Normal file
@@ -0,0 +1,12 @@
|
||||
0.008865700s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: stale: changed "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs"
|
||||
0.008885600s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: (vs) "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\.fingerprint\\kitty-keyboard-test-540f39a9b0e2080c\\dep-bin-kitty-keyboard-test"
|
||||
0.008890400s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: FileTime { seconds: 13414356040, nanos: 415027200 } < FileTime { seconds: 13414356217, nanos: 99954000 }
|
||||
0.009009300s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: fingerprint dirty for kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test)/Check { test: false }/TargetInner { name: "kitty-keyboard-test", doc: true, ..: with_path("C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs", Edition2021) }
|
||||
0.009027100s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleItem(ChangedFile { reference: "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\.fingerprint\\kitty-keyboard-test-540f39a9b0e2080c\\dep-bin-kitty-keyboard-test", reference_mtime: FileTime { seconds: 13414356040, nanos: 415027200 }, stale: "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs", stale_mtime: FileTime { seconds: 13414356217, nanos: 99954000 } }))
|
||||
0.009491200s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: stale: changed "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs"
|
||||
0.009498900s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: (vs) "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\.fingerprint\\kitty-keyboard-test-bf4513f9cae5b3d2\\dep-test-bin-kitty-keyboard-test"
|
||||
0.009502500s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: FileTime { seconds: 13414356040, nanos: 415027200 } < FileTime { seconds: 13414356217, nanos: 99954000 }
|
||||
0.009662200s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: fingerprint dirty for kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test)/Check { test: true }/TargetInner { name: "kitty-keyboard-test", doc: true, ..: with_path("C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs", Edition2021) }
|
||||
0.009677900s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleItem(ChangedFile { reference: "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\.fingerprint\\kitty-keyboard-test-bf4513f9cae5b3d2\\dep-test-bin-kitty-keyboard-test", reference_mtime: FileTime { seconds: 13414356040, nanos: 415027200 }, stale: "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs", stale_mtime: FileTime { seconds: 13414356217, nanos: 99954000 } }))
|
||||
Checking kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
|
||||
3
src/tools/kitty-keyboard-test/target/flycheck0/stdout
Normal file
3
src/tools/kitty-keyboard-test/target/flycheck0/stdout
Normal file
@@ -0,0 +1,3 @@
|
||||
{"reason":"compiler-artifact","package_id":"path+file:///C:/Users/lhecker/projects/terminal/src/tools/kitty-keyboard-test#0.1.0","manifest_path":"C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\Cargo.toml","target":{"kind":["bin"],"crate_types":["bin"],"name":"kitty-keyboard-test","src_path":"C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs","edition":"2021","doc":true,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\deps\\libkitty_keyboard_test-540f39a9b0e2080c.rmeta"],"executable":null,"fresh":false}
|
||||
{"reason":"compiler-artifact","package_id":"path+file:///C:/Users/lhecker/projects/terminal/src/tools/kitty-keyboard-test#0.1.0","manifest_path":"C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\Cargo.toml","target":{"kind":["bin"],"crate_types":["bin"],"name":"kitty-keyboard-test","src_path":"C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs","edition":"2021","doc":true,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":true},"features":[],"filenames":["C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\deps\\libkitty_keyboard_test-bf4513f9cae5b3d2.rmeta"],"executable":null,"fresh":false}
|
||||
{"reason":"build-finished","success":true}
|
||||
@@ -416,28 +416,19 @@ function Invoke-CodeFormat() {
|
||||
[switch]$IgnoreXaml
|
||||
)
|
||||
|
||||
$clangFormatPath = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -find "**\x64\bin\clang-format.exe"
|
||||
If ([String]::IsNullOrEmpty($clangFormatPath)) {
|
||||
Write-Error "No Visual Studio-supplied version of clang-format could be found."
|
||||
}
|
||||
|
||||
$root = Find-OpenConsoleRoot
|
||||
& "$root\dep\nuget\nuget.exe" restore "$root\tools\packages.config"
|
||||
$clangPackage = ([xml](Get-Content "$root\tools\packages.config")).packages.package | Where-Object id -like "clang-format*"
|
||||
$clangFormatPath = "$root\packages\$($clangPackage.id).$($clangPackage.version)\tools\clang-format.exe"
|
||||
Get-ChildItem -Recurse "$root\src" -Include *.cpp, *.hpp, *.h |
|
||||
Where FullName -NotLike "*Generated Files*" |
|
||||
Invoke-ClangFormat -ClangFormatPath $clangFormatPath
|
||||
|
||||
if ($IgnoreXaml) {
|
||||
# do nothing
|
||||
}
|
||||
else {
|
||||
if (-Not $IgnoreXaml) {
|
||||
Invoke-XamlFormat
|
||||
}
|
||||
}
|
||||
|
||||
#.SYNOPSIS
|
||||
# Download clang-format.exe required for code formatting
|
||||
function Get-Format()
|
||||
{
|
||||
$root = Find-OpenConsoleRoot
|
||||
& "$root\dep\nuget\nuget.exe" restore "$root\tools\packages.config"
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function Set-MsbuildDevEnvironment,Invoke-OpenConsoleTests,Invoke-OpenConsoleBuild,Start-OpenConsole,Debug-OpenConsole,Invoke-CodeFormat,Invoke-XamlFormat,Test-XamlFormat,Get-Format
|
||||
Export-ModuleMember -Function Set-MsbuildDevEnvironment,Invoke-OpenConsoleTests,Invoke-OpenConsoleBuild,Start-OpenConsole,Debug-OpenConsole,Invoke-CodeFormat,Invoke-XamlFormat,Test-XamlFormat
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="clang-format.win-x86" version="15.0.7" targetFramework="native" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user