Compare commits

...

16 Commits

Author SHA1 Message Date
Leonard Hecker
940e8d5c2a wip 2026-02-04 00:44:16 +01:00
Leonard Hecker
173f897751 Merge remote-tracking branch 'origin/main' into dev/lhecker/11509-kitty-keyboard-protocol-wip 2026-02-03 20:05:36 +01:00
Leonard Hecker
d875ff4d6c wip 2026-02-03 20:04:56 +01:00
Leonard Hecker
ad501c9d92 wip 2026-02-03 00:07:02 +01:00
Dustin L. Howett
89d82638ab Use the version of clang-format that comes with Visual Studio (#19821)
This pull request also (necessarily) reformats the code to match the
current version.

Visual Studio 2022 17.14 comes with Clang 19's `clang-format`.
2026-02-02 13:45:11 -06:00
Carlos Zamora
a449899789 Add IconPicker to New Tab Menu folders in SUI (#19591)
## Summary of the Pull Request
This PR pulls out the icon picker used in Profiles_Base.xaml to be its
own control. Then it's reused as a way to set an icon on folders in the
new tab menu.

As a part of pulling out the icon picker into its own control, some
minor code-health polish was added (i.e. lazy loading icon types and
built in icons).

The new tab menu didn't have a `NavigateToPageArgs`, so I took the
declaration from #19519 and moved it here. I chose to do that instead of
creating a `NavigateToNewTabMenuArgs` since that's more strict and it
would be removed as a part of #19519 anyways.

Aside from that, the PR is pretty straightforward.

## References and Relevant Issues
Part of #18281

## Validation Steps Performed
- Profile icon picker
   - [x] set none
   - [x] set built-in icon
   - [x] set emoji
   - [x] set file
   - [x] loads icons properly (for all of the scenarios above)
- New tab menu folder icon picker
   - [x] set none
   - [x] set built-in icon
   - [x] set emoji
   - [x] set file
   - [x] loads icons properly (for all of the scenarios above)
2026-02-02 11:35:42 -08:00
Leonard Hecker
cb88820fa6 wip 2026-02-02 14:12:35 +01:00
Leonard Hecker
a65bff17d8 wip 2026-02-02 14:02:11 +01:00
Dustin L. Howett
cdf4bd9209 Revert "Disable PGO for nightly builds (#17749)" (#19816)
This reverts commit 408f3e2bfd.

Now that we have #19810, it works again!
2026-01-29 23:01:45 +00:00
Leonard Hecker
5d0e8c238c Spel 2026-01-30 00:01:10 +01:00
Leonard Hecker
07792774f6 Implement the Kitty Keyboard Protocol 2026-01-29 23:58:10 +01:00
Dustin L. Howett
8f3d1d8d01 build: make the PGO build work again; upgrade nuget (#19810)
Two main changes:

- the build was failing for ARM64 because the build agents run out of
memory downloading artifacts; this is a known issue that nobody cares to
fix (and it pertains to using the x64 emulated agent host on native
arm64 machines)
   - deleting the PDBs shrinks the build output by 2500MB :P
- we had to switch to a new type of identity for publishing nuget
packages to our cross-org feed

I've verified that we get counts _and_ can even produce final PGO count
packages for both architectures!
2026-01-29 12:44:36 -08:00
Dustin L. Howett
3ec372c176 vpack: ensure that we put the right version number in (#19793)
I just submitted a vpack to Windows and it turned out to be version
"0.0.5". Whoops.
2026-01-28 12:27:05 -06:00
Leonard Hecker
71409f84f7 Avoid scrolling when the search is open (#19775)
When text scrolls in and out of view, depending on the length
of the scrollback and latency of input, `GetSearchHighlightFocused`
would change which would trigger a `ScrollToSearchHighlight` call.

This PR changes the behavior such that only actively changing the
search mask triggers a search (typing text or pressing enter).

Closes #19754

## Validation Steps Performed
* Brining text in and out of view doesn't scroll 
* Toggling the aA button scrolls results into view 
2026-01-23 18:33:18 -06:00
PankajBhojwani
6723ca2239 Delete the KeyChordViewModel if we leave edit mode with no keys (#19778)
There was an issue where if the user adds a new keybinding and then hits
"cancel changes", the KeyChordViewModel is left in the keybinding list
with an empty value. Nothing gets saved to the json, but visually there
was an empty keychord box left behind. This commit fixes that.

## Validation Steps Performed
Adding a new keybinding and cancelling changes deletes the keybinding.
2026-01-23 18:25:43 -06:00
Vallabh Mahajan
c8549bebed sgr: consider ITU T.416 colors with color space 0 valid (#19768)
This PR fixes an issue where SGR color sequences containing a Color
Space ID of 0 (e.g., \e[38:2:0:r:g:bm) were incorrectly rejected as
invalid.

According to ITU T.416, the Color Space ID parameter is defined, and a
value of 0 is implementation-defined. It is widely accepted by other
terminal emulators (like XTerm) as the default RGB color space. This
change modifies the check to explicitly allow an ID of 0 while still
rejecting other non-standard IDs, ensuring compatibility with tools that
emit fully qualified T.416 sequences.

Closes #19547
2026-01-23 17:21:55 +00:00
94 changed files with 4172 additions and 1134 deletions

View File

@@ -866,6 +866,7 @@ KILLACTIVE
KILLFOCUS
kinda
KIYEOK
KKP
KLF
KLMNO
KOK
@@ -885,6 +886,7 @@ LBUTTONDOWN
LBUTTONUP
lcb
lci
LCMAP
LCONTROL
LCTRL
lcx

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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);

View File

@@ -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; };

View File

@@ -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 },

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -55,7 +55,8 @@ namespace Microsoft.Terminal.Control
Boolean GoForward;
Boolean CaseSensitive;
Boolean RegularExpression;
Boolean ResetOnly;
Boolean ExecuteSearch;
Boolean ScrollIntoView;
Int32 ScrollOffset;
};

View File

@@ -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));
}

View File

@@ -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)

View File

@@ -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; };

View File

@@ -98,6 +98,7 @@ void Terminal::UpdateSettings(ICoreSettings settings)
}
_getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput());
_getTerminalInput().ForceDisableKittyKeyboardProtocol(!settings.AllowKittyKeyboardMode());
if (settings.TabColor() == nullptr)
{

View File

@@ -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();

View File

@@ -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()

View 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));
}
}

View 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);
}

View 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; };
}
}

View 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>

View File

@@ -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);

View File

@@ -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;

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -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);
};

View File

@@ -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

View File

@@ -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>

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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 :

View File

@@ -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);

View File

@@ -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));
}
}

View File

@@ -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();
};
};

View File

@@ -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; };
}
}

View File

@@ -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>

View File

@@ -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}"

View File

@@ -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:\ -&gt; /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:\ -&gt; /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:\ -&gt; /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:\ -&gt; 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>

View File

@@ -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) \

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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) \

View File

@@ -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>

View File

@@ -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;

View File

@@ -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) \
}
}

View File

@@ -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,

View File

@@ -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");

View File

@@ -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.

View File

@@ -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))
{

View File

@@ -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

View File

@@ -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

View File

@@ -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) \

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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{

View File

@@ -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

View File

@@ -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.

View File

@@ -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,

View File

@@ -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) });
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View 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");
}
};

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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
};
}

View File

@@ -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;

View File

@@ -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"),

View File

@@ -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

View File

@@ -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
View 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"

View 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"

View 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

View 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;

View 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":{}}

View 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/

View 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

View 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}

View File

@@ -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

View File

@@ -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>