Merge branch 'main' of https://github.com/microsoft/terminal into dev/pabhoj/cursor_light

This commit is contained in:
Pankaj Bhojwani 2021-08-16 10:39:26 -07:00
commit 8c183b4125
185 changed files with 4983 additions and 1045 deletions

View file

@ -2,11 +2,13 @@ ACCEPTFILES
ACCESSDENIED
alignas
alignof
APPLYTOSUBMENUS
bitfield
bitfields
BUILDBRANCH
BUILDMSG
BUILDNUMBER
BYPOSITION
charconv
CLASSNOTAVAILABLE
cmdletbinding
@ -78,7 +80,11 @@ llu
localtime
lround
LSHIFT
MENUCOMMAND
MENUDATA
MENUINFO
memicmp
mptt
mov
msappx
MULTIPLEUSE
@ -93,6 +99,7 @@ NOCHANGEDIR
NOPROGRESS
NOREDIRECTIONBITMAP
NOREPEAT
NOTIFYBYPOS
NOTIFYICON
NOTIFYICONDATA
ntprivapi
@ -133,12 +140,14 @@ SRWLOCK
STDCPP
STDMETHOD
strchr
strcpy
streambuf
strtoul
Stubless
Subheader
Subpage
syscall
TASKBARCREATED
TBPF
THEMECHANGED
tlg

View file

@ -169,6 +169,7 @@ brandings
BRK
Browsable
bsearch
Bspace
bstr
BTNFACE
buf
@ -270,10 +271,12 @@ cmder
CMDEXT
Cmdlet
cmdline
cmh
CMOUSEBUTTONS
cmp
cmpeq
cmt
cmw
cmyk
CNL
cnt
@ -406,11 +409,13 @@ csbiex
csharp
CSHORT
CSIDL
Cspace
csproj
Csr
csrmsg
CSRSS
csrutil
css
cstdarg
cstddef
cstdio
@ -509,6 +514,8 @@ DECAWM
DECCKM
DECCOLM
DECDHL
decdld
DECDLD
DECDWL
DECEKBD
DECID
@ -781,6 +788,7 @@ flyout
fmodern
fmtarg
fmtid
FNV
FOLDERID
FONTCHANGE
fontdlg
@ -789,6 +797,7 @@ FONTENUMPROC
FONTFACE
FONTFAMILY
FONTHEIGHT
FONTINFO
fontlist
FONTOK
FONTSIZE
@ -902,6 +911,7 @@ github
gitlab
gle
globals
GLYPHENTRY
gmail
GMEM
GNUC
@ -950,6 +960,7 @@ hdrstop
HEIGHTSCROLL
hfile
hfont
hfontresource
hglobal
hhh
HHmm
@ -1272,6 +1283,7 @@ locsrc
locstudio
Loewen
LOGFONT
LOGFONTA
LOGFONTW
logissue
lowercased
@ -1458,6 +1470,7 @@ Mul
multiline
munged
munges
murmurhash
mutex
mutexes
muxes
@ -1935,6 +1948,7 @@ realloc
reamapping
rects
redef
redefinable
Redir
redirector
redist
@ -1980,6 +1994,7 @@ rfc
rftp
rgb
rgba
RGBCOLOR
rgbi
rgci
rgfae
@ -2149,6 +2164,7 @@ SIGDN
SINGLEFLAG
SINGLETHREADED
siup
sixel
SIZEBOX
sizeof
SIZESCROLL
@ -2253,6 +2269,7 @@ SWMR
SWP
swprintf
SYMED
symlink
SYNCPAINT
sys
syscalls
@ -2754,6 +2771,7 @@ WTo
wtof
wtoi
WTs
WTSOFTFONT
wtw
wtypes
Wubi
@ -2783,6 +2801,7 @@ xes
xff
XFile
XFORM
xIcon
XManifest
XMath
XMFLOAT
@ -2815,6 +2834,7 @@ YCast
YCENTER
YCount
YDPI
yIcon
yml
YOffset
YPosition

View file

@ -154,6 +154,17 @@
"description": "Sets how the background image aligns to the boundaries of the window when unfocused. Possible values: \"center\", \"left\", \"top\", \"right\", \"bottom\", \"topLeft\", \"topRight\", \"bottomLeft\", \"bottomRight\"",
"type": "string"
},
"intenseTextStyle": {
"default": "all",
"description": "Controls how 'intense' text is rendered. Values are \"bold\", \"bright\", \"all\" and \"none\"",
"enum": [
"none",
"bold",
"bright",
"all"
],
"type": "string"
},
"experimental.retroTerminalEffect": {
"description": "When set to true, enable retro terminal effects when unfocused. This is an experimental feature, and its continued existence is not guaranteed.",
"type": "boolean"
@ -240,6 +251,7 @@
"identifyWindows",
"moveFocus",
"movePane",
"swapPane",
"moveTab",
"newTab",
"newWindow",
@ -494,6 +506,23 @@
"type": "integer",
"default": 0,
"description": "Which tab to switch to, with the first being 0"
}
}
}
],
"required": [ "index" ]
},
"MovePaneAction": {
"description": "Arguments corresponding to a Move Pane Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "movePane" },
"index": {
"type": "integer",
"default": 0,
"description": "Which tab to move the pane to, with the first being 0"
}
}
}
@ -517,13 +546,13 @@
],
"required": [ "direction" ]
},
"MovePaneAction": {
"description": "Arguments corresponding to a Move Pane Action",
"SwapPaneAction": {
"description": "Arguments corresponding to a Swap Pane Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "movePane" },
"action": { "type": "string", "pattern": "swapPane" },
"direction": {
"$ref": "#/definitions/FocusDirection",
"default": "left",
@ -984,6 +1013,7 @@
{ "$ref": "#/definitions/SwitchToTabAction" },
{ "$ref": "#/definitions/MoveFocusAction" },
{ "$ref": "#/definitions/MovePaneAction" },
{ "$ref": "#/definitions/SwapPaneAction" },
{ "$ref": "#/definitions/ResizePaneAction" },
{ "$ref": "#/definitions/SendInputAction" },
{ "$ref": "#/definitions/SplitPaneAction" },
@ -1195,6 +1225,16 @@
"minimum": 0,
"type": [ "integer", "string" ],
"deprecated": true
},
"minimizeToTray": {
"default": "false",
"description": "When set to true, minimizing a Terminal window will no longer appear in the taskbar. Instead, a Terminal icon will appear in the system tray through which the user can access their windows.",
"type": "boolean"
},
"alwaysShowTrayIcon": {
"default": "false",
"description": "When set to true, the Terminal's tray icon will always be shown in the system tray.",
"type": "boolean"
},
"actions": {
"description": "Properties are specific to each custom action.",

View file

@ -95,16 +95,18 @@ bool TextAttribute::IsLegacy() const noexcept
// - defaultFgColor: the default foreground color rgb value.
// - defaultBgColor: the default background color rgb value.
// - reverseScreenMode: true if the screen mode is reversed.
// - blinkingIsFaint: true if blinking should be interpreted as faint.
// - blinkingIsFaint: true if blinking should be interpreted as faint. (defaults to false)
// - boldIsBright: true if "bold" should be interpreted as bright. (defaults to true)
// Return Value:
// - the foreground and background colors that should be displayed.
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const std::array<COLORREF, 256>& colorTable,
const COLORREF defaultFgColor,
const COLORREF defaultBgColor,
const bool reverseScreenMode,
const bool blinkingIsFaint) const noexcept
const bool blinkingIsFaint,
const bool boldIsBright) const noexcept
{
auto fg = _foreground.GetColor(colorTable, defaultFgColor, IsBold());
auto fg = _foreground.GetColor(colorTable, defaultFgColor, boldIsBright && IsBold());
auto bg = _background.GetColor(colorTable, defaultBgColor);
if (IsFaint() || (IsBlinking() && blinkingIsFaint))
{

View file

@ -68,7 +68,8 @@ public:
const COLORREF defaultFgColor,
const COLORREF defaultBgColor,
const bool reverseScreenMode = false,
const bool blinkingIsFaint = false) const noexcept;
const bool blinkingIsFaint = false,
const bool boldIsBright = true) const noexcept;
bool IsLeadingByte() const noexcept;
bool IsTrailingByte() const noexcept;

View file

@ -22,6 +22,7 @@ class TextAttributeTests
TEST_METHOD(TestTextAttributeColorGetters);
TEST_METHOD(TestReverseDefaultColors);
TEST_METHOD(TestRoundtripDefaultColors);
TEST_METHOD(TestBoldAsBright);
std::array<COLORREF, 256> _colorTable;
COLORREF _defaultFg = RGB(1, 2, 3);
@ -263,3 +264,56 @@ void TextAttributeTests::TestRoundtripDefaultColors()
// Reset the legacy default colors to white on black.
TextAttribute::SetLegacyDefaultAttributes(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
void TextAttributeTests::TestBoldAsBright()
{
const COLORREF darkBlack = til::at(_colorTable, 0);
const COLORREF brightBlack = til::at(_colorTable, 8);
const COLORREF darkGreen = til::at(_colorTable, 2);
TextAttribute attr{};
// verify that calculated foreground/background are the same as the direct
// values when not bold
VERIFY_IS_FALSE(attr.IsBold());
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
// with bold set, calculated foreground/background values shouldn't change for the default colors.
attr.SetBold(true);
VERIFY_IS_TRUE(attr.IsBold());
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
attr.SetIndexedForeground(0);
VERIFY_IS_TRUE(attr.IsBold());
Log::Comment(L"Foreground should be bright black when bold is bright is enabled");
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
Log::Comment(L"Foreground should be dark black when bold is bright is disabled");
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
attr.SetIndexedBackground(2);
VERIFY_IS_TRUE(attr.IsBold());
Log::Comment(L"background should be unaffected by 'bold is bright'");
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
attr.SetBold(false);
VERIFY_IS_FALSE(attr.IsBold());
Log::Comment(L"when not bold, 'bold is bright' changes nothing");
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
Log::Comment(L"When set to a bright color, and bold, 'bold is bright' changes nothing");
attr.SetBold(true);
attr.SetIndexedForeground(8);
VERIFY_IS_TRUE(attr.IsBold());
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
}

View file

@ -41,6 +41,10 @@ namespace SettingsModelLocalTests
TEST_METHOD(LayerKeybindings);
TEST_METHOD(UnbindKeybindings);
TEST_METHOD(LayerScancodeKeybindings);
TEST_METHOD(TestExplicitUnbind);
TEST_METHOD(TestArbitraryArgs);
TEST_METHOD(TestSplitPaneArgs);
@ -95,23 +99,33 @@ namespace SettingsModelLocalTests
VirtualKeyModifiers::Control | VirtualKeyModifiers::Menu | VirtualKeyModifiers::Shift | VirtualKeyModifiers::Windows,
255,
0,
L"ctrl+shift+alt+win+vk(255)",
L"win+ctrl+alt+shift+vk(255)",
},
testCase{
VirtualKeyModifiers::Windows,
VirtualKeyModifiers::Control | VirtualKeyModifiers::Menu | VirtualKeyModifiers::Shift | VirtualKeyModifiers::Windows,
0,
123,
L"ctrl+shift+alt+win+sc(123)",
L"win+ctrl+alt+shift+sc(123)",
},
};
for (const auto& tc : testCases)
{
KeyChord expectedKeyChord{ tc.modifiers, tc.vkey, tc.scanCode };
const auto actualString = KeyChordSerialization::ToString(expectedKeyChord);
Log::Comment(NoThrowString().Format(L"Testing case:\"%s\"", tc.expected.data()));
const auto actualString = KeyChordSerialization::ToString({ tc.modifiers, tc.vkey, tc.scanCode });
VERIFY_ARE_EQUAL(tc.expected, actualString);
auto expectedVkey = tc.vkey;
if (!expectedVkey)
{
expectedVkey = MapVirtualKeyW(tc.scanCode, MAPVK_VSC_TO_VK_EX);
}
const auto actualKeyChord = KeyChordSerialization::FromString(actualString);
VERIFY_ARE_EQUAL(expectedKeyChord, actualKeyChord);
VERIFY_ARE_EQUAL(tc.modifiers, actualKeyChord.Modifiers());
VERIFY_ARE_EQUAL(expectedVkey, actualKeyChord.Vkey());
VERIFY_ARE_EQUAL(tc.scanCode, actualKeyChord.ScanCode());
}
}
@ -232,6 +246,31 @@ namespace SettingsModelLocalTests
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
}
void KeyBindingsTests::TestExplicitUnbind()
{
const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
const std::string bindings1String{ R"([ { "command": "unbound", "keys": ["ctrl+c"] } ])" };
const std::string bindings2String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
const KeyChord keyChord{ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 };
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings0Json);
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings1Json);
VERIFY_IS_TRUE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings2Json);
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
}
void KeyBindingsTests::TestArbitraryArgs()
{
const std::string bindings0String{ R"([
@ -722,4 +761,31 @@ namespace SettingsModelLocalTests
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P'), 0 }, kbd);
}
}
void KeyBindingsTests::LayerScancodeKeybindings()
{
Log::Comment(L"Layering a keybinding with a character literal on top of"
L" an equivalent sc() key should replace it.");
// Wrap the first one in `R"!(...)!"` because it has `()` internally.
const std::string bindings0String{ R"!([ { "command": "quakeMode", "keys":"win+sc(41)" } ])!" };
const std::string bindings1String{ R"([ { "keys": "win+`", "command": { "action": "globalSummon", "monitor": "any" } } ])" };
const std::string bindings2String{ R"([ { "keys": "ctrl+shift+`", "command": { "action": "quakeMode" } } ])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size(), L"Layering the second action should replace the first one.");
actionMap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
}
}

View file

@ -56,7 +56,7 @@ namespace TerminalAppLocalTests
TEST_METHOD(ParseComboCommandlineIntoArgs);
TEST_METHOD(ParseFocusTabArgs);
TEST_METHOD(ParseMoveFocusArgs);
TEST_METHOD(ParseMovePaneArgs);
TEST_METHOD(ParseSwapPaneArgs);
TEST_METHOD(ParseArgumentsWithParsingTerminators);
TEST_METHOD(ParseFocusPaneArgs);
@ -1208,14 +1208,9 @@ namespace TerminalAppLocalTests
}
}
void CommandlineTest::ParseMovePaneArgs()
void CommandlineTest::ParseSwapPaneArgs()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
END_TEST_METHOD_PROPERTIES()
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `mp` instead of `move-pane`");
const wchar_t* subcommand = useShortForm ? L"mp" : L"move-pane";
const wchar_t* subcommand = L"swap-pane";
{
AppCommandlineArgs appArgs{};
@ -1236,9 +1231,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
}
@ -1253,9 +1248,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
}
@ -1270,9 +1265,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Up, myArgs.Direction());
}
@ -1287,9 +1282,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Down, myArgs.Direction());
}
@ -1311,16 +1306,16 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
actionAndArgs = appArgs._startupActions.at(2);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
}

View file

@ -82,7 +82,7 @@ namespace TerminalAppLocalTests
TEST_METHOD(MoveFocusFromZoomedPane);
TEST_METHOD(CloseZoomedPane);
TEST_METHOD(MovePanes);
TEST_METHOD(SwapPanes);
TEST_METHOD(NextMRUTab);
TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder);
@ -821,7 +821,7 @@ namespace TerminalAppLocalTests
VERIFY_SUCCEEDED(result);
}
void TabTests::MovePanes()
void TabTests::SwapPanes()
{
auto page = _commonSetup();
@ -914,10 +914,10 @@ namespace TerminalAppLocalTests
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Left };
SwapPaneArgs args{ FocusDirection::Left };
ActionEventArgs eventArgs{ args };
page->_HandleMovePane(nullptr, eventArgs);
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
@ -945,10 +945,10 @@ namespace TerminalAppLocalTests
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Up };
SwapPaneArgs args{ FocusDirection::Up };
ActionEventArgs eventArgs{ args };
page->_HandleMovePane(nullptr, eventArgs);
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
@ -976,10 +976,10 @@ namespace TerminalAppLocalTests
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Right };
SwapPaneArgs args{ FocusDirection::Right };
ActionEventArgs eventArgs{ args };
page->_HandleMovePane(nullptr, eventArgs);
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
@ -1007,10 +1007,10 @@ namespace TerminalAppLocalTests
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Down };
SwapPaneArgs args{ FocusDirection::Down };
ActionEventArgs eventArgs{ args };
page->_HandleMovePane(nullptr, eventArgs);
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);

View file

@ -28,8 +28,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
CATCH_LOG();
}
// This is a private constructor to be used in unit tests, where we don't
// want each Monarch to necessarily use the current PID.
// This constructor is intended to be used in unit tests,
// but we need to make it public in order to use make_self
// in the tests. It's not exposed through the idl though
// so it's not _truly_ fully public which should be acceptable.
Monarch::Monarch(const uint64_t testPID) :
_ourPID{ testPID }
{
@ -78,6 +80,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
peasant.IdentifyWindowsRequested({ this, &Monarch::_identifyWindows });
peasant.RenameRequested({ this, &Monarch::_renameRequested });
peasant.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
peasant.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
_peasants[newPeasantsId] = peasant;
TraceLoggingWrite(g_hRemotingProvider,
@ -201,6 +206,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_clearOldMruEntries(id);
}
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_lookupPeasantIdForName",
TraceLoggingWideString(std::wstring{ name }.c_str(), "name", "the name we're looking for"),
TraceLoggingUInt64(result, "peasantID", "the ID of the peasant with that name"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
return result;
}
@ -732,24 +743,55 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
try
{
args.FoundMatch(false);
// If a WindowID is provided from the args, use that first.
uint64_t windowId = 0;
// If no name was provided, then just summon the MRU window.
if (searchedForName.empty())
if (args.WindowID())
{
// Use the value of the `desktop` arg to determine if we should
// limit to the current desktop (desktop:onCurrent) or not
// (desktop:any or desktop:toCurrent)
windowId = _getMostRecentPeasantID(args.OnCurrentDesktop(), false);
windowId = args.WindowID().Value();
}
else
{
// Try to find a peasant that currently has this name
windowId = _lookupPeasantIdForName(searchedForName);
// If no name was provided, then just summon the MRU window.
if (searchedForName.empty())
{
// Use the value of the `desktop` arg to determine if we should
// limit to the current desktop (desktop:onCurrent) or not
// (desktop:any or desktop:toCurrent)
windowId = _getMostRecentPeasantID(args.OnCurrentDesktop(), false);
}
else
{
// Try to find a peasant that currently has this name
windowId = _lookupPeasantIdForName(searchedForName);
}
}
if (auto targetPeasant{ _getPeasant(windowId) })
{
targetPeasant.Summon(args.SummonBehavior());
args.FoundMatch(true);
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonWindow_Success",
TraceLoggingWideString(searchedForName.c_str(), "searchedForName", "The name of the window we tried to summon"),
TraceLoggingUInt64(windowId, "peasantID", "The id of the window we tried to summon"),
TraceLoggingBoolean(args.OnCurrentDesktop(), "OnCurrentDesktop", "true iff the window needs to be on the current virtual desktop"),
TraceLoggingBoolean(args.SummonBehavior().MoveToCurrentDesktop(), "MoveToCurrentDesktop", "if true, move the window to the current virtual desktop"),
TraceLoggingBoolean(args.SummonBehavior().ToggleVisibility(), "ToggleVisibility", "true if we should toggle the visibility of the window"),
TraceLoggingUInt32(args.SummonBehavior().DropdownDuration(), "DropdownDuration", "the duration to dropdown the window"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonWindow_NoPeasant",
TraceLoggingWideString(searchedForName.c_str(), "searchedForName", "The name of the window we tried to summon"),
TraceLoggingUInt64(windowId, "peasantID", "The id of the window we tried to summon"),
TraceLoggingBoolean(args.OnCurrentDesktop(), "OnCurrentDesktop", "true iff the window needs to be on the current virtual desktop"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
catch (...)
@ -762,4 +804,56 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
// Method Description:
// - This method creates a map of peasant IDs to peasant names
// while removing dead peasants.
// Arguments:
// - <none>
// Return Value:
// - A map of peasant IDs to their names.
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> Monarch::GetPeasantNames()
{
auto names = winrt::single_threaded_map<uint64_t, winrt::hstring>();
std::vector<uint64_t> peasantsToErase{};
for (const auto& [id, p] : _peasants)
{
try
{
names.Insert(id, p.WindowName());
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
peasantsToErase.push_back(id);
}
}
// Remove the dead peasants we came across while iterating.
for (const auto& id : peasantsToErase)
{
_peasants.erase(id);
_clearOldMruEntries(id);
}
return names.GetView();
}
void Monarch::SummonAllWindows()
{
auto callback = [](auto&& p, auto&& /*id*/) {
SummonWindowBehavior args{};
args.ToggleVisibility(false);
p.Summon(args);
};
auto onError = [](auto&& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonAll_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not summon"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forAllPeasantsIgnoringTheDead(callback, onError);
}
}

View file

@ -41,6 +41,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
struct Monarch : public MonarchT<Monarch>
{
Monarch();
Monarch(const uint64_t testPID);
~Monarch();
uint64_t GetPID();
@ -51,10 +52,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
Monarch(const uint64_t testPID);
uint64_t _ourPID;
uint64_t _nextPeasantID{ 1 };

View file

@ -28,6 +28,7 @@ namespace Microsoft.Terminal.Remoting
Boolean FoundMatch;
SummonWindowBehavior SummonBehavior;
Windows.Foundation.IReference<UInt64> WindowID;
}
@ -40,6 +41,11 @@ namespace Microsoft.Terminal.Remoting
void HandleActivatePeasant(WindowActivatedArgs args);
void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames { get; };
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
};
}

View file

@ -20,8 +20,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
}
// This is a private constructor to be used in unit tests, where we don't
// want each Peasant to necessarily use the current PID.
// This constructor is intended to be used in unit tests,
// but we need to make it public in order to use make_self
// in the tests. It's not exposed through the idl though
// so it's not _truly_ fully public which should be acceptable.
Peasant::Peasant(const uint64_t testPID) :
_ourPID{ testPID }
{
@ -31,6 +33,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
_id = id;
}
uint64_t Peasant::GetID()
{
return _id;
@ -222,4 +225,36 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestShowTrayIcon()
{
try
{
_ShowTrayIconRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestShowTrayIcon",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestHideTrayIcon()
{
try
{
_HideTrayIconRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestHideTrayIcon",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}

View file

@ -28,6 +28,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void RequestIdentifyWindows();
void DisplayWindowId();
void RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
void RequestShowTrayIcon();
void RequestHideTrayIcon();
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
@ -40,6 +42,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RenameRequestArgs);
TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
Peasant(const uint64_t testPID);

View file

@ -64,6 +64,8 @@ namespace Microsoft.Terminal.Remoting
void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
void Summon(SummonWindowBehavior behavior);
void RequestShowTrayIcon();
void RequestHideTrayIcon();
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
@ -71,6 +73,8 @@ namespace Microsoft.Terminal.Remoting
event Windows.Foundation.TypedEventHandler<Object, Object> DisplayWindowIdRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameRequestArgs> RenameRequested;
event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
};
[default_interface] runtimeclass Peasant : IPeasant

View file

@ -34,6 +34,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
WINRT_PROPERTY(bool, FoundMatch, false);
WINRT_PROPERTY(bool, OnCurrentDesktop, false);
WINRT_PROPERTY(SummonWindowBehavior, SummonBehavior);
WINRT_PROPERTY(Windows::Foundation::IReference<uint64_t>, WindowID);
};
}

View file

@ -254,6 +254,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// window, and when the current monarch dies.
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
_monarch.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
_monarch.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
_BecameMonarchHandlers(*this, nullptr);
}
@ -509,4 +511,54 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_monarch.SummonWindow(args);
}
void WindowManager::SummonAllWindows()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
_monarch.SummonAllWindows();
}
}
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> WindowManager::GetPeasantNames()
{
// We should only get called when we're the monarch since the monarch
// is the only one that knows about all peasants.
return _monarch.GetPeasantNames();
}
// Method Description:
// - Ask the monarch to show a tray icon.
// Arguments:
// - <none>
// Return Value:
// - <none>
void WindowManager::RequestShowTrayIcon()
{
_peasant.RequestShowTrayIcon();
}
// Method Description:
// - Ask the monarch to hide its tray icon.
// Arguments:
// - <none>
// Return Value:
// - <none>
void WindowManager::RequestHideTrayIcon()
{
_peasant.RequestHideTrayIcon();
}
bool WindowManager::DoesQuakeWindowExist()
{
const auto names = GetPeasantNames();
for (const auto [id, name] : names)
{
if (name == QuakeWindowName)
{
return true;
}
}
return false;
}
}

View file

@ -40,8 +40,17 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
bool IsMonarch();
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
void RequestShowTrayIcon();
void RequestHideTrayIcon();
bool DoesQuakeWindowExist();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
bool _shouldCreateWindow{ false };

View file

@ -12,7 +12,14 @@ namespace Microsoft.Terminal.Remoting
IPeasant CurrentWindow();
Boolean IsMonarch { get; };
void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
void RequestShowTrayIcon();
void RequestHideTrayIcon();
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames();
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
};
}

View file

@ -143,6 +143,20 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_HandleMovePane(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (args == nullptr)
{
args.Handled(false);
}
else if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
{
auto moved = _MovePane(realArgs.TabIndex());
args.Handled(moved);
}
}
void TerminalPage::_HandleSplitPane(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
@ -323,10 +337,10 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_HandleMovePane(const IInspectable& /*sender*/,
void TerminalPage::_HandleSwapPane(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
if (const auto& realArgs = args.ActionArgs().try_as<SwapPaneArgs>())
{
if (realArgs.Direction() == FocusDirection::None)
{
@ -335,7 +349,7 @@ namespace winrt::TerminalApp::implementation
}
else
{
_MovePane(realArgs.Direction());
_SwapPane(realArgs.Direction());
args.Handled(true);
}
}

View file

@ -193,6 +193,7 @@ void AppCommandlineArgs::_buildParser()
_buildFocusTabParser();
_buildMoveFocusParser();
_buildMovePaneParser();
_buildSwapPaneParser();
_buildFocusPaneParser();
}
@ -297,6 +298,43 @@ void AppCommandlineArgs::_buildSplitPaneParser()
setupSubcommand(_newPaneCommand);
setupSubcommand(_newPaneShort);
}
// Method Description:
// - Adds the `move-pane` subcommand and related options to the commandline parser.
// - Additionally adds the `mp` subcommand, which is just a shortened version of `move-pane`
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_buildMovePaneParser()
{
_movePaneCommand = _app.add_subcommand("move-pane", RS_A(L"CmdMovePaneDesc"));
_movePaneShort = _app.add_subcommand("mp", RS_A(L"CmdMPDesc"));
auto setupSubcommand = [this](auto* subcommand) {
subcommand->add_option("-t,--tab",
_movePaneTabIndex,
RS_A(L"CmdMovePaneTabArgDesc"));
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
subcommand->callback([&, this]() {
// Build the action from the values we've parsed on the commandline.
ActionAndArgs movePaneAction{};
if (_movePaneTabIndex >= 0)
{
movePaneAction.Action(ShortcutAction::MovePane);
MovePaneArgs args{ static_cast<unsigned int>(_movePaneTabIndex) };
movePaneAction.Args(args);
_startupActions.push_back(movePaneAction);
}
});
};
setupSubcommand(_movePaneCommand);
setupSubcommand(_movePaneShort);
}
// Method Description:
// - Adds the `focus-tab` subcommand and related options to the commandline parser.
@ -400,16 +438,14 @@ void AppCommandlineArgs::_buildMoveFocusParser()
}
// Method Description:
// - Adds the `move-pane` subcommand and related options to the commandline parser.
// - Additionally adds the `mp` subcommand, which is just a shortened version of `move-pane`
// - Adds the `swap-pane` subcommand and related options to the commandline parser.
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_buildMovePaneParser()
void AppCommandlineArgs::_buildSwapPaneParser()
{
_movePaneCommand = _app.add_subcommand("move-pane", RS_A(L"CmdMovePaneDesc"));
_movePaneShort = _app.add_subcommand("mp", RS_A(L"CmdMPDesc"));
_swapPaneCommand = _app.add_subcommand("swap-pane", RS_A(L"CmdSwapPaneDesc"));
auto setupSubcommand = [this](auto* subcommand) {
std::map<std::string, FocusDirection> map = {
@ -420,8 +456,8 @@ void AppCommandlineArgs::_buildMovePaneParser()
};
auto* directionOpt = subcommand->add_option("direction",
_movePaneDirection,
RS_A(L"CmdMovePaneDirectionArgDesc"));
_swapPaneDirection,
RS_A(L"CmdSwapPaneDirectionArgDesc"));
directionOpt->transform(CLI::CheckedTransformer(map, CLI::ignore_case));
directionOpt->required();
@ -430,12 +466,12 @@ void AppCommandlineArgs::_buildMovePaneParser()
// that `this` will still be safe - this function just lets us know this
// command was parsed.
subcommand->callback([&, this]() {
if (_movePaneDirection != FocusDirection::None)
if (_swapPaneDirection != FocusDirection::None)
{
MovePaneArgs args{ _movePaneDirection };
SwapPaneArgs args{ _swapPaneDirection };
ActionAndArgs actionAndArgs{};
actionAndArgs.Action(ShortcutAction::MovePane);
actionAndArgs.Action(ShortcutAction::SwapPane);
actionAndArgs.Args(args);
_startupActions.push_back(std::move(actionAndArgs));
@ -443,8 +479,7 @@ void AppCommandlineArgs::_buildMovePaneParser()
});
};
setupSubcommand(_movePaneCommand);
setupSubcommand(_movePaneShort);
setupSubcommand(_swapPaneCommand);
}
// Method Description:
@ -625,6 +660,7 @@ bool AppCommandlineArgs::_noCommandsProvided()
*_moveFocusShort ||
*_movePaneCommand ||
*_movePaneShort ||
*_swapPaneCommand ||
*_focusPaneCommand ||
*_focusPaneShort ||
*_newPaneShort.subcommand ||
@ -653,12 +689,13 @@ void AppCommandlineArgs::_resetStateToDefault()
_splitPaneSize = 0.5f;
_splitDuplicate = false;
_movePaneTabIndex = -1;
_focusTabIndex = -1;
_focusNextTab = false;
_focusPrevTab = false;
_moveFocusDirection = FocusDirection::None;
_movePaneDirection = FocusDirection::None;
_swapPaneDirection = FocusDirection::None;
_focusPaneTarget = -1;

View file

@ -84,6 +84,7 @@ private:
CLI::App* _moveFocusShort;
CLI::App* _movePaneCommand;
CLI::App* _movePaneShort;
CLI::App* _swapPaneCommand;
CLI::App* _focusPaneCommand;
CLI::App* _focusPaneShort;
@ -97,7 +98,7 @@ private:
bool _suppressApplicationTitle{ false };
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _moveFocusDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _movePaneDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _swapPaneDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
// _commandline will contain the command line with which we'll be spawning a new terminal
std::vector<std::string> _commandline;
@ -107,6 +108,7 @@ private:
bool _splitDuplicate{ false };
float _splitPaneSize{ 0.5f };
int _movePaneTabIndex{ -1 };
int _focusTabIndex{ -1 };
bool _focusNextTab{ false };
bool _focusPrevTab{ false };
@ -132,6 +134,7 @@ private:
void _buildFocusTabParser();
void _buildMoveFocusParser();
void _buildMovePaneParser();
void _buildSwapPaneParser();
void _buildFocusPaneParser();
bool _noCommandsProvided();
void _resetStateToDefault();

View file

@ -21,6 +21,11 @@ namespace winrt::TerminalApp::implementation
return false;
}
bool AppKeyBindings::IsKeyChordExplicitlyUnbound(const KeyChord& kc)
{
return _actionMap.IsKeyChordExplicitlyUnbound(kc);
}
void AppKeyBindings::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
{
_dispatch = dispatch;

View file

@ -20,6 +20,7 @@ namespace winrt::TerminalApp::implementation
AppKeyBindings() = default;
bool TryKeyChord(winrt::Microsoft::Terminal::Control::KeyChord const& kc);
bool IsKeyChordExplicitlyUnbound(winrt::Microsoft::Terminal::Control::KeyChord const& kc);
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);

View file

@ -203,12 +203,16 @@ namespace winrt::TerminalApp::implementation
_isElevated = _isUserAdmin();
_root = winrt::make_self<TerminalPage>();
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(_root->Dispatcher(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
if (auto self{ weakSelf.get() })
{
self->_ReloadSettings();
}
});
_languageProfileNotifier = winrt::make_self<LanguageProfileNotifier>([this]() {
_reloadSettings->Run();
});
}
// Method Description:
@ -1125,28 +1129,11 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Gets the taskbar state value from the last active control
// Return Value:
// - The taskbar state of the last active control
uint64_t AppLogic::GetLastActiveControlTaskbarState()
winrt::TerminalApp::TaskbarState AppLogic::TaskbarState()
{
if (_root)
{
return _root->GetLastActiveControlTaskbarState();
}
return {};
}
// Method Description:
// - Gets the taskbar progress value from the last active control
// Return Value:
// - The taskbar progress of the last active control
uint64_t AppLogic::GetLastActiveControlTaskbarProgress()
{
if (_root)
{
return _root->GetLastActiveControlTaskbarProgress();
return _root->TaskbarState();
}
return {};
}
@ -1455,4 +1442,39 @@ namespace winrt::TerminalApp::implementation
return _root->IsQuakeWindow();
}
bool AppLogic::GetMinimizeToTray()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
}
return _settings.GlobalSettings().MinimizeToTray();
}
else
{
return false;
}
}
bool AppLogic::GetAlwaysShowTrayIcon()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
}
return _settings.GlobalSettings().AlwaysShowTrayIcon();
}
else
{
return false;
}
}
}

View file

@ -5,8 +5,9 @@
#include "AppLogic.g.h"
#include "FindTargetWindowResult.g.h"
#include "TerminalPage.h"
#include "Jumplist.h"
#include "LanguageProfileNotifier.h"
#include "TerminalPage.h"
#include <inc/cppwinrt_utils.h>
#include <ThrottledFunc.h>
@ -89,8 +90,10 @@ namespace winrt::TerminalApp::implementation
void WindowCloseButtonClicked();
uint64_t GetLastActiveControlTaskbarState();
uint64_t GetLastActiveControlTaskbarProgress();
winrt::TerminalApp::TaskbarState TaskbarState();
bool GetMinimizeToTray();
bool GetAlwaysShowTrayIcon();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
@ -110,12 +113,8 @@ namespace winrt::TerminalApp::implementation
// ALSO: If you add any UIElements as roots here, make sure they're
// updated in _ApplyTheme. The root currently is _root.
winrt::com_ptr<TerminalPage> _root{ nullptr };
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
wil::unique_folder_change_reader_nothrow _reader;
std::shared_ptr<ThrottledFuncTrailing<>> _reloadSettings;
til::throttled_func_trailing<> _reloadState;
winrt::hstring _settingsLoadExceptionText;
HRESULT _settingsLoadedResult = S_OK;
bool _loadedInitialSettings = false;
@ -124,6 +123,15 @@ namespace winrt::TerminalApp::implementation
::TerminalApp::AppCommandlineArgs _appArgs;
::TerminalApp::AppCommandlineArgs _settingsAppArgs;
std::shared_ptr<ThrottledFuncTrailing<>> _reloadSettings;
til::throttled_func_trailing<> _reloadState;
// These fields invoke _reloadSettings and must be destroyed before _reloadSettings.
// (C++ destroys members in reverse-declaration-order.)
winrt::com_ptr<LanguageProfileNotifier> _languageProfileNotifier;
wil::unique_folder_change_reader_nothrow _reader;
static TerminalApp::FindTargetWindowResult _doFindTargetWindow(winrt::array_view<const hstring> args,
const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior);

View file

@ -68,8 +68,10 @@ namespace TerminalApp
void TitlebarClicked();
void WindowCloseButtonClicked();
UInt64 GetLastActiveControlTaskbarState();
UInt64 GetLastActiveControlTaskbarProgress();
TaskbarState TaskbarState{ get; };
Boolean GetMinimizeToTray();
Boolean GetAlwaysShowTrayIcon();
FindTargetWindowResult FindTargetWindow(String[] args);

View file

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "LanguageProfileNotifier.h"
using namespace winrt::TerminalApp::implementation;
LanguageProfileNotifier::LanguageProfileNotifier(std::function<void()>&& callback) :
_callback{ std::move(callback) },
_currentKeyboardLayout{ GetKeyboardLayout(0) }
{
const auto manager = wil::CoCreateInstance<ITfThreadMgr>(CLSID_TF_ThreadMgr);
_source = manager.query<ITfSource>();
if (FAILED(_source->AdviseSink(IID_ITfInputProcessorProfileActivationSink, static_cast<ITfInputProcessorProfileActivationSink*>(this), &_cookie)))
{
_cookie = TF_INVALID_COOKIE;
THROW_LAST_ERROR();
}
}
LanguageProfileNotifier::~LanguageProfileNotifier()
{
if (_cookie != TF_INVALID_COOKIE)
{
_source->UnadviseSink(_cookie);
}
}
STDMETHODIMP LanguageProfileNotifier::OnActivated(DWORD /*dwProfileType*/, LANGID /*langid*/, REFCLSID /*clsid*/, REFGUID /*catid*/, REFGUID /*guidProfile*/, HKL hkl, DWORD /*dwFlags*/)
{
if (hkl && hkl != _currentKeyboardLayout)
{
_currentKeyboardLayout = hkl;
try
{
_callback();
}
CATCH_RETURN();
}
return S_OK;
}

View file

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace winrt::TerminalApp::implementation
{
class LanguageProfileNotifier : public winrt::implements<LanguageProfileNotifier, ITfInputProcessorProfileActivationSink>
{
public:
explicit LanguageProfileNotifier(std::function<void()>&& callback);
~LanguageProfileNotifier();
STDMETHODIMP OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid, REFGUID guidProfile, HKL hkl, DWORD dwFlags);
private:
std::function<void()> _callback;
wil::com_ptr<ITfSource> _source;
DWORD _cookie = TF_INVALID_COOKIE;
HKL _currentKeyboardLayout;
};
}

View file

@ -220,7 +220,7 @@ bool Pane::ResizePane(const ResizeDirection& direction)
// direction, we'll return false. This will indicate to our parent that they
// should try and move the focus themselves. In this way, the focus can move
// up and down the tree to the correct pane.
// - This method is _very_ similar to MovePane. Both are trying to find the
// - This method is _very_ similar to SwapPane. Both are trying to find the
// right pane to move (focus) in a direction.
// Arguments:
// - direction: The direction to move the focus in.
@ -595,7 +595,7 @@ Pane::FocusNeighborSearch Pane::_FindFocusAndNeighbor(const FocusDirection& dire
// - direction: The direction to move the focused pane in.
// Return Value:
// - true if we or a child handled this pane move request.
bool Pane::MovePane(const FocusDirection& direction)
bool Pane::SwapPane(const FocusDirection& direction)
{
// If we're a leaf, do nothing. We can't possibly swap anything.
if (_IsLeaf())
@ -614,7 +614,7 @@ bool Pane::MovePane(const FocusDirection& direction)
// and its neighbor must necessarily be contained within the same child.
if (!DirectionMatchesSplit(direction, _splitState))
{
return _firstChild->MovePane(direction) || _secondChild->MovePane(direction);
return _firstChild->SwapPane(direction) || _secondChild->SwapPane(direction);
}
// Since the direction is the same as our split, it is possible that we must
@ -1011,6 +1011,85 @@ void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const GU
}
}
// Method Description:
// - Attempts to add the provided pane as a split of the current pane.
// Arguments:
// - pane: the new pane to add
// - splitType: How the pane should be attached
// Return Value:
// - the new reference to the child created from the current pane.
std::shared_ptr<Pane> Pane::AttachPane(std::shared_ptr<Pane> pane, SplitState splitType)
{
// Splice the new pane into the tree
const auto [first, _] = _Split(splitType, .5, pane);
// If the new pane has a child that was the focus, re-focus it
// to steal focus from the currently focused pane.
if (pane->_HasFocusedChild())
{
pane->WalkTree([](auto p) {
if (p->_lastActive)
{
p->_FocusFirstChild();
return true;
}
return false;
});
}
return first;
}
// Method Description:
// - Attempts to find the parent of the target pane,
// if found remove the pane from the tree and return it.
// - If the removed pane was (or contained the focus) the first sibling will
// gain focus.
// Arguments:
// - pane: the pane to detach
// Return Value:
// - The removed pane, if found.
std::shared_ptr<Pane> Pane::DetachPane(std::shared_ptr<Pane> pane)
{
// We can't remove a pane if we only have a reference to a leaf, even if we
// are the pane.
if (_IsLeaf())
{
return nullptr;
}
// Check if either of our children matches the search
const auto isFirstChild = _firstChild == pane;
const auto isSecondChild = _secondChild == pane;
if (isFirstChild || isSecondChild)
{
// Keep a reference to the child we are removing
auto detached = isFirstChild ? _firstChild : _secondChild;
// Remove the child from the tree, replace the current node with the
// other child.
_CloseChild(isFirstChild);
detached->_borders = Borders::None;
detached->_UpdateBorders();
// Trigger the detached event on each child
detached->WalkTree([](auto pane) {
pane->_PaneDetachedHandlers(pane);
return false;
});
return detached;
}
if (const auto detached = _firstChild->DetachPane(pane))
{
return detached;
}
return _secondChild->DetachPane(pane);
}
// Method Description:
// - Closes one of our children. In doing so, takes the control from the other
// child, and makes this pane a leaf node again.
@ -1073,12 +1152,10 @@ void Pane::_CloseChild(const bool closeFirst)
// them.
_lastActive = _firstChild->_lastActive || _secondChild->_lastActive;
// Remove all the ui elements of our children. This'll make sure we can
// re-attach the TermControl to our Grid.
_firstChild->_root.Children().Clear();
_secondChild->_root.Children().Clear();
_firstChild->_border.Child(nullptr);
_secondChild->_border.Child(nullptr);
// Remove all the ui elements of the remaining child. This'll make sure
// we can re-attach the TermControl to our Grid.
remainingChild->_root.Children().Clear();
remainingChild->_border.Child(nullptr);
// Reset our UI:
_root.Children().Clear();
@ -1125,17 +1202,8 @@ void Pane::_CloseChild(const bool closeFirst)
}
else
{
// Determine which border flag we gave to the child when we first split
// it, so that we can take just that flag away from them.
Borders clearBorderFlag = Borders::None;
if (_splitState == SplitState::Horizontal)
{
clearBorderFlag = closeFirst ? Borders::Top : Borders::Bottom;
}
else if (_splitState == SplitState::Vertical)
{
clearBorderFlag = closeFirst ? Borders::Left : Borders::Right;
}
// Find what borders need to persist after we close the child
auto remainingBorders = _GetCommonBorders();
// First stash away references to the old panes and their tokens
const auto oldFirstToken = _firstClosedToken;
@ -1192,13 +1260,9 @@ void Pane::_CloseChild(const bool closeFirst)
_root.Children().Append(_firstChild->GetRootElement());
_root.Children().Append(_secondChild->GetRootElement());
// Take the flag away from the children that they inherited from their
// parent, and update their borders to visually match
WI_ClearAllFlags(_firstChild->_borders, clearBorderFlag);
WI_ClearAllFlags(_secondChild->_borders, clearBorderFlag);
_UpdateBorders();
_firstChild->_UpdateBorders();
_secondChild->_UpdateBorders();
// Propagate the new borders down to the children.
_borders = remainingBorders;
_ApplySplitDefinitions();
// If the closed child was focused, transfer the focus to it's first sibling.
if (closedChild->_lastActive)
@ -1757,7 +1821,8 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::Split(SplitState s
return { nullptr, nullptr };
}
return _Split(splitType, splitSize, profile, control);
auto newPane = std::make_shared<Pane>(profile, control);
return _Split(splitType, splitSize, newPane);
}
// Method Description:
@ -1827,14 +1892,13 @@ SplitState Pane::_convertAutomaticSplitState(const SplitState& splitType) const
// creates a new Pane to host the control, registers event handlers.
// Arguments:
// - splitType: what type of split we should create.
// - profile: The profile GUID to associate with the newly created pane.
// - control: A TermControl to use in the new pane.
// - splitSize: what fraction of the pane the new pane should get
// - newPane: the pane to add as a child
// Return Value:
// - The two newly created Panes
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState splitType,
const float splitSize,
const GUID& profile,
const TermControl& control)
std::shared_ptr<Pane> newPane)
{
if (splitType == SplitState::None)
{
@ -1874,7 +1938,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
_firstChild->_connectionState = std::exchange(_connectionState, ConnectionState::NotConnected);
_profile = std::nullopt;
_control = { nullptr };
_secondChild = std::make_shared<Pane>(profile, control);
_secondChild = newPane;
_CreateRowColDefinitions();
@ -2548,6 +2612,30 @@ bool Pane::ContainsReadOnly() const
return _IsLeaf() ? _control.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly());
}
// Method Description:
// - If we're a parent, place the taskbar state for all our leaves into the
// provided vector.
// - If we're a leaf, place our own state into the vector.
// Arguments:
// - states: a vector that will receive all the states of all leaves in the tree
// Return Value:
// - <none>
void Pane::CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& states)
{
if (_IsLeaf())
{
auto tbState{ winrt::make<winrt::TerminalApp::implementation::TaskbarState>(_control.TaskbarState(),
_control.TaskbarProgress()) };
states.push_back(tbState);
}
else
{
_firstChild->CollectTaskbarStates(states);
_secondChild->CollectTaskbarStates(states);
}
}
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DEFINE_EVENT(Pane, LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DEFINE_EVENT(Pane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
DEFINE_EVENT(Pane, Detached, _PaneDetachedHandlers, winrt::delegate<std::shared_ptr<Pane>>);

View file

@ -21,6 +21,7 @@
#pragma once
#include "../../cascadia/inc/cppwinrt_utils.h"
#include "TaskbarState.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
@ -28,6 +29,11 @@ namespace TerminalAppLocalTests
class TabTests;
};
namespace winrt::TerminalApp::implementation
{
struct TerminalTab;
}
enum class Borders : int
{
None = 0x0,
@ -62,7 +68,7 @@ public:
void Relayout();
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
bool NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool MovePane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool SwapPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second);
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
@ -80,6 +86,10 @@ public:
void Shutdown();
void Close();
std::shared_ptr<Pane> AttachPane(std::shared_ptr<Pane> pane,
winrt::Microsoft::Terminal::Settings::Model::SplitState splitType);
std::shared_ptr<Pane> DetachPane(std::shared_ptr<Pane> pane);
int GetLeafPaneCount() const noexcept;
void Maximize(std::shared_ptr<Pane> zoomedPane);
@ -92,10 +102,38 @@ public:
bool ContainsReadOnly() const;
// Method Description:
// - A helper method for ad-hoc recursion on a pane tree. Walks the pane
// tree, calling a predicate on each pane in a depth-first pattern.
// - If the predicate returns true, recursion is stopped early.
// Arguments:
// - f: The function to be applied to each pane.
// Return Value:
// - true if the predicate returned true on any pane.
template<typename F>
//requires std::predicate<F, std::shared_ptr<Pane>>
bool WalkTree(F f)
{
if (f(shared_from_this()))
{
return true;
}
if (!_IsLeaf())
{
return _firstChild->WalkTree(f) || _secondChild->WalkTree(f);
}
return false;
}
void CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& states);
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
DECLARE_EVENT(Detached, _PaneDetachedHandlers, winrt::delegate<std::shared_ptr<Pane>>);
private:
struct PanePoint;
@ -140,8 +178,7 @@ private:
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize,
const GUID& profile,
const winrt::Microsoft::Terminal::Control::TermControl& control);
std::shared_ptr<Pane> newPane);
void _CreateRowColDefinitions();
void _ApplySplitDefinitions();
@ -271,5 +308,6 @@ private:
void _AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode);
};
friend struct winrt::TerminalApp::implementation::TerminalTab;
friend class ::TerminalAppLocalTests::TabTests;
};

View file

@ -282,6 +282,16 @@
<data name="CmdFocusTabTargetArgDesc" xml:space="preserve">
<value>Move focus the tab at the given index</value>
</data>
<data name="CmdMovePaneTabArgDesc" xml:space="preserve">
<value>Move focused pane to the tab at the given index</value>
</data>
<data name="CmdMovePaneDesc" xml:space="preserve">
<value>Move focused pane to another tab</value>
</data>
<data name="CmdMPDesc" xml:space="preserve">
<value>An alias for the "move-pane" subcommand.</value>
<comment>{Locked="\"move-pane\""}</comment>
</data>
<data name="CmdSplitPaneSizeArgDesc" xml:space="preserve">
<value>Specify the size as a percentage of the parent pane. Valid values are between (0,1), exclusive.</value>
</data>
@ -359,14 +369,10 @@
<data name="CmdMoveFocusDirectionArgDesc" xml:space="preserve">
<value>The direction to move focus in</value>
</data>
<data name="CmdMovePaneDesc" xml:space="preserve">
<data name="CmdSwapPaneDesc" xml:space="preserve">
<value>Swap the focused pane with the adjacent pane in the specified direction</value>
</data>
<data name="CmdMPDesc" xml:space="preserve">
<value>An alias for the "move-pane" subcommand.</value>
<comment>{Locked="\"move-pane\""}</comment>
</data>
<data name="CmdMovePaneDirectionArgDesc" xml:space="preserve">
<data name="CmdSwapPaneDirectionArgDesc" xml:space="preserve">
<value>The direction to move the focused pane in</value>
</data>
<data name="CmdFocusDesc" xml:space="preserve">
@ -647,6 +653,14 @@
<data name="CommandPaletteMenuItem" xml:space="preserve">
<value>Command Palette</value>
</data>
<data name="TrayIconFocusTerminal" xml:space="preserve">
<value>Focus Terminal</value>
<comment>This is displayed as a label for the context menu item that focuses the terminal.</comment>
</data>
<data name="TrayIconWindowSubmenu" xml:space="preserve">
<value>Windows</value>
<comment>This is displayed as a label for the context menu item that holds the submenu of available windows.</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Dev" xml:space="preserve">
<value>Open in Windows Terminal (Dev)</value>
<comment>{Locked} The dev build will never be seen in multiple languages</comment>

View file

@ -95,45 +95,12 @@ namespace winrt::TerminalApp::implementation
CATCH_RETURN();
// Method Description:
// - Creates a new tab with the given settings. If the tab bar is not being
// currently displayed, it will be shown.
// - Sets up state, event handlers, etc on a tab object that was just made.
// Arguments:
// - profileGuid: ID to use to lookup profile settings for this connection
// - settings: the TerminalSettings object to use to create the TerminalControl with.
// - existingConnection: optionally receives a connection from the outside world instead of attempting to create one
void TerminalPage::_CreateNewTabFromSettings(GUID profileGuid, const TerminalSettingsCreateResult& settings, TerminalConnection::ITerminalConnection existingConnection)
// - newTabImpl: the uninitialized tab.
void TerminalPage::_InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl)
{
// Initialize the new tab
// Create a connection based on the values in our settings object if we weren't given one.
auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profileGuid, settings.DefaultSettings());
// If we had an `existingConnection`, then this is an inbound handoff from somewhere else.
// We need to tell it about our size information so it can match the dimensions of what
// we are about to present.
if (existingConnection)
{
connection.Resize(settings.DefaultSettings().InitialRows(), settings.DefaultSettings().InitialCols());
}
TerminalConnection::ITerminalConnection debugConnection{ nullptr };
if (_settings.GlobalSettings().DebugFeaturesEnabled())
{
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool bothAltsPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (bothAltsPressed)
{
std::tie(connection, debugConnection) = OpenDebugTapConnection(connection);
}
}
// Give term control a child of the settings so that any overrides go in the child
// This way, when we do a settings reload we just update the parent and the overrides remain
auto term = _InitControl(settings, connection);
auto newTabImpl = winrt::make_self<TerminalTab>(profileGuid, term);
newTabImpl->Initialize();
// Add the new tab to the list of our tabs.
_tabs.Append(*newTabImpl);
@ -146,7 +113,7 @@ namespace winrt::TerminalApp::implementation
_UpdateTabIndices();
// Hookup our event handlers to the new terminal
_RegisterTerminalEvents(term, *newTabImpl);
_RegisterTabEvents(*newTabImpl);
// Don't capture a strong ref to the tab. If the tab is removed as this
// is called, we don't really care anymore about handling the event.
@ -208,10 +175,13 @@ namespace winrt::TerminalApp::implementation
_tabView.TabItems().Append(tabViewItem);
// Set this tab's icon to the icon from the user's profile
const auto profile = _settings.FindProfile(profileGuid);
if (profile != nullptr && !profile.Icon().empty())
if (const auto profileGuid = newTabImpl->GetFocusedProfile())
{
newTabImpl->UpdateIcon(profile.Icon());
const auto profile = _settings.FindProfile(profileGuid.value());
if (profile != nullptr && !profile.Icon().empty())
{
newTabImpl->UpdateIcon(profile.Icon());
}
}
tabViewItem.PointerReleased({ this, &TerminalPage::_OnTabClick });
@ -244,19 +214,73 @@ namespace winrt::TerminalApp::implementation
}
});
if (debugConnection) // this will only be set if global debugging is on and tap is active
{
auto newControl = _InitControl(settings, debugConnection);
_RegisterTerminalEvents(newControl, *newTabImpl);
// Split (auto) with the debug tap.
newTabImpl->SplitPane(SplitState::Automatic, 0.5f, profileGuid, newControl);
}
// This kicks off TabView::SelectionChanged, in response to which
// we'll attach the terminal's Xaml control to the Xaml root.
_tabView.SelectedItem(tabViewItem);
}
// Method Description:
// - Create a new tab using a specified pane as the root.
// Arguments:
// - pane: The pane to use as the root.
void TerminalPage::_CreateNewTabFromPane(std::shared_ptr<Pane> pane)
{
auto newTabImpl = winrt::make_self<TerminalTab>(pane);
_InitializeTab(newTabImpl);
}
// Method Description:
// - Creates a new tab with the given settings. If the tab bar is not being
// currently displayed, it will be shown.
// Arguments:
// - profileGuid: ID to use to lookup profile settings for this connection
// - settings: the TerminalSettings object to use to create the TerminalControl with.
// - existingConnection: optionally receives a connection from the outside world instead of attempting to create one
void TerminalPage::_CreateNewTabFromSettings(GUID profileGuid, const TerminalSettingsCreateResult& settings, TerminalConnection::ITerminalConnection existingConnection)
{
// Initialize the new tab
// Create a connection based on the values in our settings object if we weren't given one.
auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profileGuid, settings.DefaultSettings());
// If we had an `existingConnection`, then this is an inbound handoff from somewhere else.
// We need to tell it about our size information so it can match the dimensions of what
// we are about to present.
if (existingConnection)
{
connection.Resize(settings.DefaultSettings().InitialRows(), settings.DefaultSettings().InitialCols());
}
TerminalConnection::ITerminalConnection debugConnection{ nullptr };
if (_settings.GlobalSettings().DebugFeaturesEnabled())
{
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool bothAltsPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (bothAltsPressed)
{
std::tie(connection, debugConnection) = OpenDebugTapConnection(connection);
}
}
// Give term control a child of the settings so that any overrides go in the child
// This way, when we do a settings reload we just update the parent and the overrides remain
auto term = _InitControl(settings, connection);
auto newTabImpl = winrt::make_self<TerminalTab>(profileGuid, term);
_RegisterTerminalEvents(term);
_InitializeTab(newTabImpl);
if (debugConnection) // this will only be set if global debugging is on and tap is active
{
auto newControl = _InitControl(settings, debugConnection);
_RegisterTerminalEvents(newControl);
// Split (auto) with the debug tap.
newTabImpl->SplitPane(SplitState::Automatic, 0.5f, profileGuid, newControl);
}
}
// Method Description:
// - Get the icon of the currently focused terminal control, and set its
// tab's icon to that icon.

View file

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "TaskbarState.h"
#include "TaskbarState.g.cpp"
namespace winrt::TerminalApp::implementation
{
// Default to unset, 0%.
TaskbarState::TaskbarState() :
TaskbarState(0, 0){};
TaskbarState::TaskbarState(const uint64_t dispatchTypesState, const uint64_t progressParam) :
_State{ dispatchTypesState },
_Progress{ progressParam } {}
uint64_t TaskbarState::Priority() const
{
// This seemingly nonsensical ordering is from
// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate#how-the-taskbar-button-chooses-the-progress-indicator-for-a-group
switch (_State)
{
case 0: // Clear = 0,
return 5;
case 1: // Set = 1,
return 3;
case 2: // Error = 2,
return 1;
case 3: // Indeterminate = 3,
return 4;
case 4: // Paused = 4
return 2;
}
// Here, return 6, to definitely be greater than all the other valid values.
// This should never really happen.
return 6;
}
int TaskbarState::ComparePriority(const winrt::TerminalApp::TaskbarState& lhs, const winrt::TerminalApp::TaskbarState& rhs)
{
return lhs.Priority() < rhs.Priority();
}
}

View file

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "inc/cppwinrt_utils.h"
#include "TaskbarState.g.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class TabTests;
};
namespace winrt::TerminalApp::implementation
{
struct TaskbarState : TaskbarStateT<TaskbarState>
{
public:
TaskbarState();
TaskbarState(const uint64_t dispatchTypesState, const uint64_t progress);
static int ComparePriority(const winrt::TerminalApp::TaskbarState& lhs, const winrt::TerminalApp::TaskbarState& rhs);
uint64_t Priority() const;
WINRT_PROPERTY(uint64_t, State, 0);
WINRT_PROPERTY(uint64_t, Progress, 0);
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(TaskbarState);
}

View file

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace TerminalApp
{
[default_interface] runtimeclass TaskbarState
{
TaskbarState();
TaskbarState(UInt64 dispatchTypesState, UInt64 progress);
UInt64 State{ get; };
UInt64 Progress{ get; };
UInt64 Priority { get; };
}
}

View file

@ -53,7 +53,7 @@
<Page Include="TabRowControl.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="TabHeaderControl.xaml">
<Page Include="TabHeaderControl.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="HighlightedTextControl.xaml">
@ -74,6 +74,7 @@
<ClInclude Include="Commandline.h" />
<ClInclude Include="CommandLinePaletteItem.h" />
<ClInclude Include="Jumplist.h" />
<ClInclude Include="LanguageProfileNotifier.h" />
<ClInclude Include="MinMaxCloseControl.h">
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
</ClInclude>
@ -89,6 +90,9 @@
<DependentUpon>TabBase.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TabPaletteItem.h" />
<ClInclude Include="TaskbarState.h">
<DependentUpon>TaskbarState.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TerminalTab.h">
<DependentUpon>TerminalTab.idl</DependentUpon>
</ClInclude>
@ -112,7 +116,7 @@
<ClInclude Include="HighlightedTextControl.h">
<DependentUpon>HighlightedTextControl.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="HighlightedText.h" />
<ClInclude Include="HighlightedText.h" />
<ClInclude Include="ColorPickupFlyout.h">
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
</ClInclude>
@ -139,7 +143,7 @@
<ClInclude Include="AppLogic.h">
<DependentUpon>AppLogic.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Toast.h"/>
<ClInclude Include="Toast.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
@ -149,6 +153,7 @@
<ClCompile Include="AppCommandlineArgs.cpp" />
<ClCompile Include="Commandline.cpp" />
<ClCompile Include="Jumplist.cpp" />
<ClCompile Include="LanguageProfileNotifier.cpp" />
<ClCompile Include="MinMaxCloseControl.cpp">
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
</ClCompile>
@ -164,6 +169,9 @@
<DependentUpon>TabBase.idl</DependentUpon>
</ClCompile>
<ClCompile Include="TabPaletteItem.cpp" />
<ClCompile Include="TaskbarState.cpp">
<DependentUpon>TaskbarState.idl</DependentUpon>
</ClCompile>
<ClCompile Include="TerminalTab.cpp">
<DependentUpon>TerminalTab.idl</DependentUpon>
</ClCompile>
@ -195,7 +203,7 @@
<ClCompile Include="HighlightedTextControl.cpp">
<DependentUpon>HighlightedTextControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="HighlightedText.cpp" />
<ClCompile Include="HighlightedText.cpp" />
<ClCompile Include="ColorPickupFlyout.cpp">
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
</ClCompile>
@ -256,6 +264,7 @@
</Midl>
<Midl Include="TabBase.idl" />
<Midl Include="TabPaletteItem.idl" />
<Midl Include="TaskbarState.idl" />
<Midl Include="TerminalTab.idl" />
<Midl Include="TerminalPage.idl">
<DependentUpon>TerminalPage.xaml</DependentUpon>
@ -280,7 +289,7 @@
<DependentUpon>HighlightedTextControl.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="HighlightedText.idl" />
<Midl Include="HighlightedText.idl" />
<Midl Include="ColorPickupFlyout.idl">
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
<SubType>Code</SubType>

View file

@ -13,9 +13,6 @@
<ClCompile Include="Pane.cpp">
<Filter>pane</Filter>
</ClCompile>
<ClCompile Include="Tab.cpp">
<Filter>tab</Filter>
</ClCompile>
<ClCompile Include="Pane.LayoutSizeNode.cpp">
<Filter>pane</Filter>
</ClCompile>
@ -23,7 +20,6 @@
<ClCompile Include="Commandline.cpp" />
<ClCompile Include="ColorHelper.cpp" />
<ClCompile Include="DebugTapConnection.cpp" />
<ClCompile Include="Utils.cpp" />
<ClCompile Include="Jumplist.cpp" />
<ClCompile Include="Tab.cpp">
<Filter>tab</Filter>
@ -49,10 +45,10 @@
<ClCompile Include="HighlightedText.cpp">
<Filter>highlightedText</Filter>
</ClCompile>
<ClCompile Include="Toast.cpp" />
<ClCompile Include="LanguageProfileNotifier.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Utils.h" />
<ClInclude Include="TerminalWarnings.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="App.base.h">
<Filter>app</Filter>
@ -60,9 +56,6 @@
<ClInclude Include="Pane.h">
<Filter>pane</Filter>
</ClInclude>
<ClInclude Include="Tab.h">
<Filter>tab</Filter>
</ClInclude>
<ClInclude Include="AppCommandlineArgs.h" />
<ClInclude Include="Commandline.h" />
<ClInclude Include="DebugTapConnection.h" />
@ -92,14 +85,13 @@
<ClInclude Include="HighlightedText.h">
<Filter>highlightedText</Filter>
</ClInclude>
<ClInclude Include="Toast.h" />
<ClInclude Include="LanguageProfileNotifier.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="AppLogic.idl">
<Filter>app</Filter>
</Midl>
<Midl Include="ActionArgs.idl">
<Filter>settings</Filter>
</Midl>
<Midl Include="AppKeyBindings.idl">
<Filter>settings</Filter>
</Midl>
@ -107,9 +99,6 @@
<Filter>settings</Filter>
</Midl>
<Midl Include="IDirectKeyListener.idl" />
<Midl Include="ITab.idl">
<Filter>tab</Filter>
</Midl>
<Midl Include="SettingsTab.idl">
<Filter>tab</Filter>
</Midl>
@ -125,6 +114,9 @@
<Midl Include="TerminalTabStatus.idl">
<Filter>tab</Filter>
</Midl>
<Midl Include="PaletteItemTemplateSelector.idl" />
<Midl Include="TabBase.idl" />
<Midl Include="EmptyStringVisibilityConverter.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
@ -160,9 +152,6 @@
<Midl Include="ActionPaletteItem.idl">
<Filter>commandPalette</Filter>
</Midl>
<Midl Include="FilterableListItem.idl">
<Filter>commandPalette</Filter>
</Midl>
<Midl Include="CommandLinePaletteItem.idl">
<Filter>commandPalette</Filter>
</Midl>

View file

@ -980,11 +980,9 @@ namespace winrt::TerminalApp::implementation
// handle. This includes:
// * the Copy and Paste events, for setting and retrieving clipboard data
// on the right thread
// * the TitleChanged event, for changing the text of the tab
// Arguments:
// - term: The newly created TermControl to connect the events for
// - hostingTab: The Tab that's hosting this TermControl instance
void TerminalPage::_RegisterTerminalEvents(TermControl term, TerminalTab& hostingTab)
void TerminalPage::_RegisterTerminalEvents(TermControl term)
{
term.RaiseNotice({ this, &TerminalPage::_ControlNoticeRaisedHandler });
@ -999,10 +997,20 @@ namespace winrt::TerminalApp::implementation
term.HidePointerCursor({ get_weak(), &TerminalPage::_HidePointerCursorHandler });
term.RestorePointerCursor({ get_weak(), &TerminalPage::_RestorePointerCursorHandler });
// Add an event handler for when the terminal or tab wants to set a
// progress indicator on the taskbar
term.SetTaskbarProgress({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });
}
// Bind Tab events to the TermControl and the Tab's Pane
hostingTab.Initialize(term);
// Method Description:
// - Connects event handlers to the TerminalTab for events that we want to
// handle. This includes:
// * the TitleChanged event, for changing the text of the tab
// * the Color{Selected,Cleared} events to change the color of a tab.
// Arguments:
// - hostingTab: The Tab that's hosting this TermControl instance
void TerminalPage::_RegisterTabEvents(TerminalTab& hostingTab)
{
auto weakTab{ hostingTab.get_weak() };
auto weakThis{ get_weak() };
// PropertyChanged is the generic mechanism by which the Tab
@ -1054,7 +1062,6 @@ namespace winrt::TerminalApp::implementation
// Add an event handler for when the terminal or tab wants to set a
// progress indicator on the taskbar
hostingTab.TaskbarProgressChanged({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });
term.SetTaskbarProgress({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });
// TODO GH#3327: Once we support colorizing the NewTab button based on
// the color of the tab, we'll want to make sure to call
@ -1115,17 +1122,17 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Attempt to swap the positions of the focused pane with another pane.
// See Pane::MovePane for details.
// See Pane::SwapPane for details.
// Arguments:
// - direction: The direction to move the focused pane in.
// Return Value:
// - <none>
void TerminalPage::_MovePane(const FocusDirection& direction)
void TerminalPage::_SwapPane(const FocusDirection& direction)
{
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
_UnZoomIfNeeded();
terminalTab->MovePane(direction);
terminalTab->SwapPane(direction);
}
}
@ -1188,6 +1195,52 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Moves the currently active pane on the currently active tab to the
// specified tab. If the tab index is greater than the number of
// tabs, then a new tab will be created for the pane. Similarly, if a pane
// is the last remaining pane on a tab, that tab will be closed upon moving.
// Arguments:
// - tabIdx: The target tab index.
bool TerminalPage::_MovePane(const uint32_t tabIdx)
{
auto focusedTab{ _GetFocusedTabImpl() };
if (!focusedTab)
{
return false;
}
// If we are trying to move from the current tab to the current tab do nothing.
if (_GetFocusedTabIndex() == tabIdx)
{
return false;
}
// Moving the pane from the current tab might close it, so get the next
// tab before its index changes.
if (_tabs.Size() > tabIdx)
{
auto targetTab = _GetTerminalTabImpl(_tabs.GetAt(tabIdx));
// if the selected tab is not a host of terminals (e.g. settings)
// don't attempt to add a pane to it.
if (!targetTab)
{
return false;
}
auto pane = focusedTab->DetachPane();
targetTab->AttachPane(pane);
_SetFocusedTab(*targetTab);
}
else
{
auto pane = focusedTab->DetachPane();
_CreateNewTabFromPane(pane);
}
return true;
}
// Method Description:
// - Split the focused pane either horizontally or vertically, and place the
// given TermControl into the newly created pane.
@ -1306,7 +1359,7 @@ namespace winrt::TerminalApp::implementation
auto newControl = _InitControl(controlSettings, controlConnection);
// Hookup our event handlers to the new terminal
_RegisterTerminalEvents(newControl, tab);
_RegisterTerminalEvents(newControl);
_UnZoomIfNeeded();
@ -2077,29 +2130,35 @@ namespace winrt::TerminalApp::implementation
}
// Method Description:
// - Gets the taskbar state value from the last active control
// - Get the combined taskbar state for the page. This is the combination of
// all the states of all the tabs, which are themselves a combination of
// all their panes. Taskbar states are given a priority based on the rules
// in:
// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate
// under "How the Taskbar Button Chooses the Progress Indicator for a Group"
// Arguments:
// - <none>
// Return Value:
// - The taskbar state of the last active control
uint64_t TerminalPage::GetLastActiveControlTaskbarState()
// - A TaskbarState object representing the combined taskbar state and
// progress percentage of all our tabs.
winrt::TerminalApp::TaskbarState TerminalPage::TaskbarState() const
{
if (auto control{ _GetActiveControl() })
{
return control.TaskbarState();
}
return {};
}
auto state{ winrt::make<winrt::TerminalApp::implementation::TaskbarState>() };
// Method Description:
// - Gets the taskbar progress value from the last active control
// Return Value:
// - The taskbar progress of the last active control
uint64_t TerminalPage::GetLastActiveControlTaskbarProgress()
{
if (auto control{ _GetActiveControl() })
for (const auto& tab : _tabs)
{
return control.TaskbarProgress();
if (auto tabImpl{ _GetTerminalTabImpl(tab) })
{
auto tabState{ tabImpl->GetCombinedTaskbarState() };
// lowest priority wins
if (tabState.Priority() < state.Priority())
{
state = tabState;
}
}
}
return {};
return state;
}
// Method Description:

View file

@ -85,8 +85,7 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::IDialogPresenter DialogPresenter() const;
void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter);
uint64_t GetLastActiveControlTaskbarState();
uint64_t GetLastActiveControlTaskbarProgress();
winrt::TerminalApp::TaskbarState TaskbarState() const;
void ShowKeyboardServiceWarning();
winrt::hstring KeyboardServiceDisabledText();
@ -189,6 +188,7 @@ namespace winrt::TerminalApp::implementation
void _CreateNewTabFlyout();
void _OpenNewTabDropdown();
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
void _CreateNewTabFromSettings(GUID profileGuid, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(GUID profileGuid, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
@ -226,7 +226,9 @@ namespace winrt::TerminalApp::implementation
void _RemoveTab(const winrt::TerminalApp::TabBase& tab);
winrt::fire_and_forget _RemoveTabs(const std::vector<winrt::TerminalApp::TabBase> tabs);
void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term, TerminalTab& hostingTab);
void _InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl);
void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term);
void _RegisterTabEvents(TerminalTab& hostingTab);
void _DismissTabContextMenus();
void _FocusCurrentTab(const bool focusAlways);
@ -237,7 +239,8 @@ namespace winrt::TerminalApp::implementation
void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode);
bool _SelectTab(uint32_t tabIndex);
bool _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
void _MovePane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
void _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _MovePane(const uint32_t tabIdx);
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl();
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;

View file

@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "TaskbarState.idl";
namespace TerminalApp
{
@ -42,8 +43,7 @@ namespace TerminalApp
void ShowKeyboardServiceWarning();
String KeyboardServiceDisabledText { get; };
UInt64 GetLastActiveControlTaskbarState();
UInt64 GetLastActiveControlTaskbarProgress();
TaskbarState TaskbarState{ get; };
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;

View file

@ -30,14 +30,61 @@ namespace winrt::TerminalApp::implementation
_rootPane = std::make_shared<Pane>(profile, control, true);
_rootPane->Id(_nextPaneId);
_activePane = _rootPane;
_mruPanes.insert(_mruPanes.begin(), _nextPaneId);
++_nextPaneId;
_rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) {
_Setup();
}
TerminalTab::TerminalTab(std::shared_ptr<Pane> rootPane)
{
_rootPane = rootPane;
_activePane = nullptr;
auto firstId = _nextPaneId;
_rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
// update the IDs on each pane
if (pane->_IsLeaf())
{
pane->Id(_nextPaneId);
_nextPaneId++;
}
// Try to find the pane marked active (if it exists)
if (pane->_lastActive)
{
_activePane = pane;
}
return false;
});
// In case none of the panes were already marked as the focus, just
// focus the first one.
if (_activePane == nullptr)
{
_rootPane->FocusPane(firstId);
_activePane = _rootPane->GetActivePane();
}
// Set the active control
_mruPanes.insert(_mruPanes.begin(), _activePane->Id().value());
_Setup();
}
// Method Description:
// - Shared setup for the constructors. Assumed that _rootPane has been set.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TerminalTab::_Setup()
{
_rootClosedToken = _rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) {
_ClosedHandlers(nullptr, nullptr);
});
_activePane = _rootPane;
Content(_rootPane->GetRootElement());
_MakeTabViewItem();
@ -144,19 +191,31 @@ namespace winrt::TerminalApp::implementation
// that was last focused.
TermControl TerminalTab::GetActiveTerminalControl() const
{
return _activePane->GetTerminalControl();
if (_activePane)
{
return _activePane->GetTerminalControl();
}
return nullptr;
}
// Method Description:
// - Called after construction of a Tab object to bind event handlers to its
// associated Pane and TermControl object
// associated Pane and TermControl objects
// Arguments:
// - control: reference to the TermControl object to bind event to
// - <none>
// Return Value:
// - <none>
void TerminalTab::Initialize(const TermControl& control)
void TerminalTab::Initialize()
{
_BindEventHandlers(control);
_rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
// Attach event handlers to each new pane
_AttachEventHandlersToPane(pane);
if (auto control = pane->GetTerminalControl())
{
_AttachEventHandlersToControl(pane->Id().value(), control);
}
return false;
});
}
// Method Description:
@ -177,10 +236,9 @@ namespace winrt::TerminalApp::implementation
{
lastFocusedControl.Focus(_focusState);
// Update our own progress state, and fire an event signaling
// Update our own progress state. This will fire an event signaling
// that our taskbar progress changed.
_UpdateProgressState();
_TaskbarProgressChangedHandlers(lastFocusedControl, nullptr);
}
// When we gain focus, remove the bell indicator if it is active
if (_tabStatus.BellIndicator())
@ -204,19 +262,6 @@ namespace winrt::TerminalApp::implementation
return _activePane->GetFocusedProfile();
}
// Method Description:
// - Called after construction of a Tab object to bind event handlers to its
// associated Pane and TermControl object
// Arguments:
// - control: reference to the TermControl object to bind event to
// Return Value:
// - <none>
void TerminalTab::_BindEventHandlers(const TermControl& control) noexcept
{
_AttachEventHandlersToPane(_rootPane);
_AttachEventHandlersToControl(control);
}
// Method Description:
// - Attempts to update the settings of this tab's tree of panes.
// Arguments:
@ -426,10 +471,10 @@ namespace winrt::TerminalApp::implementation
++_nextPaneId;
}
_activePane = first;
_AttachEventHandlersToControl(control);
// Add a event handlers to the new panes' GotFocus event. When the pane
// gains focus, we'll mark it as the new active pane.
_AttachEventHandlersToControl(second->Id().value(), control);
_AttachEventHandlersToPane(first);
_AttachEventHandlersToPane(second);
@ -440,11 +485,121 @@ namespace winrt::TerminalApp::implementation
_UpdateActivePane(second);
}
// Method Description:
// - Removes the currently active pane from this tab. If that was the only
// remaining pane, then the entire tab is closed as well.
// Arguments:
// - <none>
// Return Value:
// - The removed pane.
std::shared_ptr<Pane> TerminalTab::DetachPane()
{
// if we only have one pane, remove it entirely
// and close this tab
if (_rootPane == _activePane)
{
return DetachRoot();
}
// Attempt to remove the active pane from the tree
if (const auto pane = _rootPane->DetachPane(_activePane))
{
// Just make sure that the remaining pane is marked active
_UpdateActivePane(_rootPane->GetActivePane());
return pane;
}
return nullptr;
}
// Method Description:
// - Closes this tab and returns the root pane to be used elsewhere.
// Arguments:
// - <none>
// Return Value:
// - The root pane.
std::shared_ptr<Pane> TerminalTab::DetachRoot()
{
// remove the closed event handler since we are closing the tab
// manually.
_rootPane->Closed(_rootClosedToken);
auto p = _rootPane;
p->WalkTree([](auto pane) {
pane->_PaneDetachedHandlers(pane);
return false;
});
// Clean up references and close the tab
_rootPane = nullptr;
_activePane = nullptr;
Content(nullptr);
_ClosedHandlers(nullptr, nullptr);
return p;
}
// Method Description:
// - Add an arbitrary pane to this tab. This will be added as a split on the
// currently active pane.
// Arguments:
// - pane: The pane to add.
// Return Value:
// - <none>
void TerminalTab::AttachPane(std::shared_ptr<Pane> pane)
{
// Add the new event handlers to the new pane(s)
// and update their ids.
pane->WalkTree([&](auto p) {
_AttachEventHandlersToPane(p);
if (p->_IsLeaf())
{
p->Id(_nextPaneId);
_nextPaneId++;
}
if (auto control = p->GetTerminalControl())
{
_AttachEventHandlersToControl(p->Id().value(), control);
}
return false;
});
// pass the old id to the new child
const auto previousId = _activePane->Id();
// Add the new pane as an automatic split on the active pane.
auto first = _activePane->AttachPane(pane, SplitState::Automatic);
// under current assumptions this condition should always be true.
if (previousId)
{
first->Id(previousId.value());
}
else
{
first->Id(_nextPaneId);
++_nextPaneId;
}
// Update with event handlers on the new child.
_activePane = first;
_AttachEventHandlersToPane(first);
// Make sure that we have the right pane set as the active pane
pane->WalkTree([&](auto p) {
if (p->_lastActive)
{
_UpdateActivePane(p);
return true;
}
return false;
});
}
// Method Description:
// - Find the currently active pane, and then switch the split direction of
// its parent. E.g. switch from Horizontal to Vertical.
// Return Value:
// - <none>
void TerminalTab::ToggleSplitOrientation()
{
@ -521,7 +676,7 @@ namespace winrt::TerminalApp::implementation
// - direction: The direction to move the pane in.
// Return Value:
// - <none>
void TerminalTab::MovePane(const FocusDirection& direction)
void TerminalTab::SwapPane(const FocusDirection& direction)
{
if (direction == FocusDirection::Previous)
{
@ -538,7 +693,7 @@ namespace winrt::TerminalApp::implementation
{
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
_rootPane->MovePane(direction);
_rootPane->SwapPane(direction);
}
}
@ -551,7 +706,10 @@ namespace winrt::TerminalApp::implementation
// - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections.
void TerminalTab::Shutdown()
{
_rootPane->Shutdown();
if (_rootPane)
{
_rootPane->Shutdown();
}
}
// Method Description:
@ -596,6 +754,34 @@ namespace winrt::TerminalApp::implementation
_headerControl.BeginRename();
}
// Method Description:
// - Removes any event handlers set by the tab on the given pane's control.
// The pane's ID is the most stable identifier for a given control, because
// the control itself doesn't have a particular ID and its pointer is
// unstable since it is moved when panes split.
// Arguments:
// - paneId: The ID of the pane that contains the given control.
// - control: the control to remove events from.
// Return Value:
// - <none>
void TerminalTab::_DetachEventHandlersFromControl(const uint32_t paneId, const TermControl& control)
{
auto it = _controlEvents.find(paneId);
if (it != _controlEvents.end())
{
auto& events = it->second;
control.TitleChanged(events.titleToken);
control.FontSizeChanged(events.fontToken);
control.TabColorChanged(events.colorToken);
control.SetTaskbarProgress(events.taskbarToken);
control.ReadOnlyChanged(events.readOnlyToken);
control.FocusFollowMouseRequested(events.focusToken);
_controlEvents.erase(paneId);
}
}
// Method Description:
// - Register any event handlers that we may need with the given TermControl.
// This should be called on each and every TermControl that we add to the tree
@ -603,15 +789,17 @@ namespace winrt::TerminalApp::implementation
// * notify us when the control's title changed, so we can update our own
// title (if necessary)
// Arguments:
// - paneId: the ID of the pane that this control belongs to.
// - control: the TermControl to add events to.
// Return Value:
// - <none>
void TerminalTab::_AttachEventHandlersToControl(const TermControl& control)
void TerminalTab::_AttachEventHandlersToControl(const uint32_t paneId, const TermControl& control)
{
auto weakThis{ get_weak() };
auto dispatcher = TabViewItem().Dispatcher();
ControlEventTokens events{};
control.TitleChanged([weakThis](auto&&, auto&&) {
events.titleToken = control.TitleChanged([weakThis](auto&&, auto&&) {
// Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() })
{
@ -626,16 +814,16 @@ namespace winrt::TerminalApp::implementation
// On the latter event, we tell the root pane to resize itself so that its descendants
// (including ourself) can properly snap to character grids. In future, we may also
// want to do that on regular font changes.
control.FontSizeChanged([this](const int /* fontWidth */,
const int /* fontHeight */,
const bool isInitialChange) {
events.fontToken = control.FontSizeChanged([this](const int /* fontWidth */,
const int /* fontHeight */,
const bool isInitialChange) {
if (isInitialChange)
{
_rootPane->Relayout();
}
});
control.TabColorChanged([weakThis](auto&&, auto&&) {
events.colorToken = control.TabColorChanged([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
// The control's tabColor changed, but it is not necessarily the
@ -645,7 +833,7 @@ namespace winrt::TerminalApp::implementation
}
});
control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto &&) -> winrt::fire_and_forget {
events.taskbarToken = control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto &&) -> winrt::fire_and_forget {
co_await winrt::resume_foreground(dispatcher);
// Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() })
@ -654,14 +842,14 @@ namespace winrt::TerminalApp::implementation
}
});
control.ReadOnlyChanged([weakThis](auto&&, auto&&) {
events.readOnlyToken = control.ReadOnlyChanged([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_RecalculateAndApplyReadOnly();
}
});
control.FocusFollowMouseRequested([weakThis](auto&& sender, auto&&) {
events.focusToken = control.FocusFollowMouseRequested([weakThis](auto&& sender, auto&&) {
if (const auto tab{ weakThis.get() })
{
if (tab->_focusState != FocusState::Unfocused)
@ -673,6 +861,31 @@ namespace winrt::TerminalApp::implementation
}
}
});
_controlEvents[paneId] = events;
}
// Method Description:
// - Get the combined taskbar state for the tab. This is the combination of
// all the states of all our panes. Taskbar states are given a priority
// based on the rules in:
// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate
// under "How the Taskbar Button Chooses the Progress Indicator for a
// Group"
// Arguments:
// - <none>
// Return Value:
// - A TaskbarState object representing the combined taskbar state and
// progress percentage of all our panes.
winrt::TerminalApp::TaskbarState TerminalTab::GetCombinedTaskbarState() const
{
std::vector<winrt::TerminalApp::TaskbarState> states;
if (_rootPane)
{
_rootPane->CollectTaskbarStates(states);
}
return states.empty() ? winrt::make<winrt::TerminalApp::implementation::TaskbarState>() :
*std::min_element(states.begin(), states.end(), TerminalApp::implementation::TaskbarState::ComparePriority);
}
// Method Description:
@ -690,37 +903,39 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalTab::_UpdateProgressState()
{
if (const auto& activeControl{ GetActiveTerminalControl() })
{
const auto taskbarState = activeControl.TaskbarState();
// The progress of the control changed, but not necessarily the progress of the tab.
// Set the tab's progress ring to the active pane's progress
if (taskbarState > 0)
{
if (taskbarState == 3)
{
// 3 is the indeterminate state, set the progress ring as such
_tabStatus.IsProgressRingIndeterminate(true);
}
else
{
// any non-indeterminate state has a value, set the progress ring as such
_tabStatus.IsProgressRingIndeterminate(false);
const auto state{ GetCombinedTaskbarState() };
const auto progressValue = gsl::narrow<uint32_t>(activeControl.TaskbarProgress());
_tabStatus.ProgressValue(progressValue);
}
// Hide the tab icon (the progress ring is placed over it)
HideIcon(true);
_tabStatus.IsProgressRingActive(true);
const auto taskbarState = state.State();
// The progress of the control changed, but not necessarily the progress of the tab.
// Set the tab's progress ring to the active pane's progress
if (taskbarState > 0)
{
if (taskbarState == 3)
{
// 3 is the indeterminate state, set the progress ring as such
_tabStatus.IsProgressRingIndeterminate(true);
}
else
{
// Show the tab icon
HideIcon(false);
_tabStatus.IsProgressRingActive(false);
// any non-indeterminate state has a value, set the progress ring as such
_tabStatus.IsProgressRingIndeterminate(false);
const auto progressValue = gsl::narrow<uint32_t>(state.Progress());
_tabStatus.ProgressValue(progressValue);
}
// Hide the tab icon (the progress ring is placed over it)
HideIcon(true);
_tabStatus.IsProgressRingActive(true);
}
else
{
// Show the tab icon
HideIcon(false);
_tabStatus.IsProgressRingActive(false);
}
// fire an event signaling that our taskbar progress changed.
_TaskbarProgressChangedHandlers(nullptr, nullptr);
}
// Method Description:
@ -777,7 +992,7 @@ namespace winrt::TerminalApp::implementation
auto weakThis{ get_weak() };
std::weak_ptr<Pane> weakPane{ pane };
pane->GotFocus([weakThis](std::shared_ptr<Pane> sender) {
auto gotFocusToken = pane->GotFocus([weakThis](std::shared_ptr<Pane> sender) {
// Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.get() };
@ -797,7 +1012,7 @@ namespace winrt::TerminalApp::implementation
}
});
pane->LostFocus([weakThis](std::shared_ptr<Pane> /*sender*/) {
auto lostFocusToken = pane->LostFocus([weakThis](std::shared_ptr<Pane> /*sender*/) {
// Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.get() };
@ -811,7 +1026,7 @@ namespace winrt::TerminalApp::implementation
// Add a Closed event handler to the Pane. If the pane closes out from
// underneath us, and it's zoomed, we want to be able to make sure to
// update our state accordingly to un-zoom that pane. See GH#7252.
pane->Closed([weakThis, weakPane](auto&& /*s*/, auto && /*e*/) -> winrt::fire_and_forget {
auto closedToken = pane->Closed([weakThis, weakPane](auto&& /*s*/, auto && /*e*/) -> winrt::fire_and_forget {
if (auto tab{ weakThis.get() })
{
if (tab->_zoomedPane)
@ -836,7 +1051,7 @@ namespace winrt::TerminalApp::implementation
});
// Add a PaneRaiseBell event handler to the Pane
pane->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) {
auto bellToken = pane->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) {
if (auto tab{ weakThis.get() })
{
if (visual)
@ -858,6 +1073,40 @@ namespace winrt::TerminalApp::implementation
}
}
});
// box the event token so that we can give a reference to it in the
// event handler.
auto detachedToken = std::make_shared<winrt::event_token>();
// Add a Detached event handler to the Pane to clean up tab state
// and other event handlers when a pane is removed from this tab.
*detachedToken = pane->Detached([weakThis, weakPane, gotFocusToken, lostFocusToken, closedToken, bellToken, detachedToken](std::shared_ptr<Pane> /*sender*/) {
// Make sure we do this at most once
if (auto pane{ weakPane.lock() })
{
pane->Detached(*detachedToken);
pane->GotFocus(gotFocusToken);
pane->LostFocus(lostFocusToken);
pane->Closed(closedToken);
pane->PaneRaiseBell(bellToken);
if (auto tab{ weakThis.get() })
{
if (auto control = pane->GetTerminalControl())
{
tab->_DetachEventHandlersFromControl(pane->Id().value(), control);
}
for (auto i = tab->_mruPanes.begin(); i != tab->_mruPanes.end(); ++i)
{
if (*i == pane->Id())
{
tab->_mruPanes.erase(i);
break;
}
}
}
}
});
}
// Method Description:

View file

@ -22,9 +22,10 @@ namespace winrt::TerminalApp::implementation
{
public:
TerminalTab(const GUID& profile, const winrt::Microsoft::Terminal::Control::TermControl& control);
TerminalTab(std::shared_ptr<Pane> rootPane);
// Called after construction to perform the necessary setup, which relies on weak_ptr
void Initialize(const winrt::Microsoft::Terminal::Control::TermControl& control);
void Initialize();
winrt::Microsoft::Terminal::Control::TermControl GetActiveTerminalControl() const;
std::optional<GUID> GetFocusedProfile() const noexcept;
@ -33,6 +34,10 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget Scroll(const int delta);
std::shared_ptr<Pane> DetachRoot();
std::shared_ptr<Pane> DetachPane();
void AttachPane(std::shared_ptr<Pane> pane);
void SplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize,
const GUID& profile,
@ -54,7 +59,7 @@ namespace winrt::TerminalApp::implementation
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
bool NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
void MovePane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
void SwapPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool FocusPane(const uint32_t id);
void UpdateSettings(const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const GUID& profile);
@ -83,6 +88,7 @@ namespace winrt::TerminalApp::implementation
void TogglePaneReadOnly();
std::shared_ptr<Pane> GetActivePane() const;
winrt::TerminalApp::TaskbarState GetCombinedTaskbarState() const;
winrt::TerminalApp::TerminalTabStatus TabStatus()
{
@ -101,6 +107,7 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<Pane> _rootPane{ nullptr };
std::shared_ptr<Pane> _activePane{ nullptr };
std::shared_ptr<Pane> _zoomedPane{ nullptr };
winrt::hstring _lastIconPath{};
winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{};
std::optional<winrt::Windows::UI::Color> _themeTabColor{};
@ -108,6 +115,19 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::TabHeaderControl _headerControl{};
winrt::TerminalApp::TerminalTabStatus _tabStatus{};
struct ControlEventTokens
{
winrt::event_token titleToken;
winrt::event_token fontToken;
winrt::event_token colorToken;
winrt::event_token taskbarToken;
winrt::event_token readOnlyToken;
winrt::event_token focusToken;
};
std::unordered_map<uint32_t, ControlEventTokens> _controlEvents;
winrt::event_token _rootClosedToken{};
std::vector<uint32_t> _mruPanes;
uint32_t _nextPaneId{ 0 };
@ -120,6 +140,8 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
void _Setup();
std::optional<Windows::UI::Xaml::DispatcherTimer> _bellIndicatorTimer;
void _BellIndicatorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
@ -132,9 +154,8 @@ namespace winrt::TerminalApp::implementation
void _RefreshVisualState();
void _BindEventHandlers(const winrt::Microsoft::Terminal::Control::TermControl& control) noexcept;
void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::Control::TermControl& control);
void _DetachEventHandlersFromControl(const uint32_t paneId, const winrt::Microsoft::Terminal::Control::TermControl& control);
void _AttachEventHandlersToControl(const uint32_t paneId, const winrt::Microsoft::Terminal::Control::TermControl& control);
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);
void _UpdateActivePane(std::shared_ptr<Pane> pane);

View file

@ -66,6 +66,7 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
#include <telemetry/ProjectTelemetry.h>
#include <TraceLoggingActivity.h>
#include <msctf.h>
#include <shellapi.h>
#include <shobjidl_core.h>

View file

@ -23,6 +23,16 @@ using namespace winrt::Windows::Graphics::Display;
using namespace winrt::Windows::System;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
// The minimum delay between updates to the scroll bar's values.
// The updates are throttled to limit power usage.
constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);
// The minimum delay between updating the TSF input control.
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100);
// The minimum delay between updating the locations of regex patterns
constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(500);
namespace winrt::Microsoft::Terminal::Control::implementation
{
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
@ -94,6 +104,61 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto pfnTerminalTaskbarProgressChanged = std::bind(&ControlCore::_terminalTaskbarProgressChanged, this);
_terminal->TaskbarProgressChangedCallback(pfnTerminalTaskbarProgressChanged);
// Get our dispatcher. If we're hosted in-proc with XAML, this will get
// us the same dispatcher as TermControl::Dispatcher(). If we're out of
// proc, this'll return null. We'll need to instead make a new
// DispatcherQueue (on a new thread), so we can use that for throttled
// functions.
_dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (!_dispatcher)
{
auto controller{ winrt::Windows::System::DispatcherQueueController::CreateOnDedicatedThread() };
_dispatcher = controller.DispatcherQueue();
}
// A few different events should be throttled, so they don't fire absolutely all the time:
// * _tsfTryRedrawCanvas: When the cursor position moves, we need to
// inform TSF, so it can move the canvas for the composition. We
// throttle this so that we're not hopping across the process boundary
// every time that the cursor moves.
// * _updatePatternLocations: When there's new output, or we scroll the
// viewport, we should re-check if there are any visible hyperlinks.
// But we don't really need to do this every single time text is
// output, we can limit this update to once every 500ms.
// * _updateScrollBar: Same idea as the TSF update - we don't _really_
// need to hop across the process boundary every time text is output.
// We can throttle this to once every 8ms, which will get us out of
// the way of the main output & rendering threads.
_tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
_dispatcher,
TsfRedrawInterval,
[weakThis = get_weak()]() {
if (auto core{ weakThis.get() }; !core->_IsClosing())
{
core->_CursorPositionChangedHandlers(*core, nullptr);
}
});
_updatePatternLocations = std::make_shared<ThrottledFuncTrailing<>>(
_dispatcher,
UpdatePatternLocationsInterval,
[weakThis = get_weak()]() {
if (auto core{ weakThis.get() }; !core->_IsClosing())
{
core->UpdatePatternLocations();
}
});
_updateScrollBar = std::make_shared<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>>(
_dispatcher,
ScrollBarUpdateInterval,
[weakThis = get_weak()](const auto& update) {
if (auto core{ weakThis.get() }; !core->_IsClosing())
{
core->_ScrollPositionChangedHandlers(*core, update);
}
});
UpdateSettings(settings);
}
@ -199,6 +264,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderEngine->SetPixelShaderPath(_settings.PixelShaderPath());
_renderEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
_renderEngine->SetIntenseIsBold(_settings.IntenseIsBold());
_updateAntiAliasingMode(_renderEngine.get());
@ -535,6 +601,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
_updateAntiAliasingMode(_renderEngine.get());
// Refresh our font with the renderer
@ -564,6 +631,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderEngine->SetSelectionBackground(til::color{ newAppearance.SelectionBackground() });
_renderEngine->SetRetroTerminalEffect(newAppearance.RetroTerminalEffect());
_renderEngine->SetPixelShaderPath(newAppearance.PixelShaderPath());
_renderEngine->SetIntenseIsBold(_settings.IntenseIsBold());
_renderer->TriggerRedrawAll();
}
}
@ -739,6 +807,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return;
}
// Convert our new dimensions to characters
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 },
{ static_cast<short>(size.cx), static_cast<short>(size.cy) });
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
const auto currentVP = _terminal->GetViewport();
// Don't actually resize if viewport dimensions didn't change
if (vp.Height() == currentVP.Height() && vp.Width() == currentVP.Width())
{
return;
}
_terminal->ClearSelection();
// Tell the dx engine that our window is now the new size.
@ -747,11 +827,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Invalidate everything
_renderer->TriggerRedrawAll();
// Convert our new dimensions to characters
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 },
{ static_cast<short>(size.cx), static_cast<short>(size.cy) });
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
// If this function succeeds with S_FALSE, then the terminal didn't
// actually change size. No need to notify the connection of this no-op.
const HRESULT hr = _terminal->UserResize({ vp.Width(), vp.Height() });
@ -1103,15 +1178,28 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// TODO GH#9617: refine locking around pattern tree
_terminal->ClearPatternTree();
_ScrollPositionChangedHandlers(*this,
winrt::make<ScrollPositionChangedArgs>(viewTop,
viewHeight,
bufferSize));
// Start the throttled update of our scrollbar.
auto update{ winrt::make<ScrollPositionChangedArgs>(viewTop,
viewHeight,
bufferSize) };
if (!_inUnitTests)
{
_updateScrollBar->Run(update);
}
else
{
_ScrollPositionChangedHandlers(*this, update);
}
// Additionally, start the throttled update of where our links are.
_updatePatternLocations->Run();
}
void ControlCore::_terminalCursorPositionChanged()
{
_CursorPositionChangedHandlers(*this, nullptr);
// When the buffer's cursor moves, start the throttled func to
// eventually dispatch a CursorPositionChanged event.
_tsfTryRedrawCanvas->Run();
}
void ControlCore::_terminalTaskbarProgressChanged()
@ -1221,8 +1309,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ControlCore::Close()
{
if (!_closing.exchange(true))
if (!_IsClosing())
{
_closing = true;
// Stop accepting new output and state changes before we disconnect everything.
_connection.TerminalOutput(_connectionOutputEventToken);
_connectionStateChangedRevoker.revoke();
@ -1412,18 +1502,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_terminal->Write(hstr);
// NOTE: We're raising an event here to inform the TermControl that
// output has been received, so it can queue up a throttled
// UpdatePatternLocations call. In the future, we should have the
// _updatePatternLocations ThrottledFunc internal to this class, and
// run on this object's dispatcher queue.
//
// We're not doing that quite yet, because the Core will eventually
// be out-of-proc from the UI thread, and won't be able to just use
// the UI thread as the dispatcher queue thread.
//
// See TODO: https://github.com/microsoft/terminal/projects/5#card-50760282
_ReceivedOutputHandlers(*this, nullptr);
// Start the throttled update of where our hyperlinks are.
_updatePatternLocations->Run();
}
}

View file

@ -169,7 +169,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
private:
bool _initializedTerminal{ false };
std::atomic<bool> _closing{ false };
bool _closing{ false };
TerminalConnection::ITerminalConnection _connection{ nullptr };
event_token _connectionOutputEventToken;
@ -207,6 +207,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
double _panelHeight{ 0 };
double _compositionScale{ 0 };
winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr };
std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas;
std::shared_ptr<ThrottledFuncTrailing<>> _updatePatternLocations;
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> _updateScrollBar;
winrt::fire_and_forget _asyncCloseConnection();
void _setFontSize(int fontSize);
@ -240,8 +245,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _connectionOutputHandler(const hstring& hstr);
void _updateHoveredCell(const std::optional<til::point> terminalPosition);
inline bool _IsClosing() const noexcept
{
#ifndef NDEBUG
if (_dispatcher)
{
// _closing isn't atomic and may only be accessed from the main thread.
//
// Though, the unit tests don't actually run in TAEF's main
// thread, so we don't care when we're running in tests.
assert(_inUnitTests || _dispatcher.HasThreadAccess());
}
#endif
return _closing;
}
friend class ControlUnitTests::ControlCoreTests;
friend class ControlUnitTests::ControlInteractivityTests;
bool _inUnitTests{ false };
};
}

View file

@ -11,6 +11,8 @@ namespace Microsoft.Terminal.Control
Windows.UI.Xaml.Media.Stretch BackgroundImageStretchMode;
Windows.UI.Xaml.HorizontalAlignment BackgroundImageHorizontalAlignment;
Windows.UI.Xaml.VerticalAlignment BackgroundImageVerticalAlignment;
Boolean IntenseIsBold;
// IntenseIsBright is in Core Appearance
// Experimental settings
Boolean RetroTerminalEffect;

View file

@ -9,5 +9,6 @@ namespace Microsoft.Terminal.Control
interface IKeyBindings
{
Boolean TryKeyChord(KeyChord kc);
Boolean IsKeyChordExplicitlyUnbound(KeyChord kc);
}
}

View file

@ -21,9 +21,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
KeyChord::KeyChord(bool ctrl, bool alt, bool shift, bool win, int32_t vkey, int32_t scanCode) noexcept :
_modifiers{ modifiersFromBooleans(ctrl, alt, shift, win) },
_vkey{ vkey },
_scanCode{ scanCode }
KeyChord(modifiersFromBooleans(ctrl, alt, shift, win), vkey, scanCode)
{
}
@ -32,6 +30,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_vkey{ vkey },
_scanCode{ scanCode }
{
// ActionMap needs to identify KeyChords which should "layer" (overwrite) each other.
// For instance win+sc(41) and win+` both specify the same KeyChord on an US keyboard layout
// from the perspective of a user. Either of the two should correctly overwrite the other.
// We can help ActionMap with this by ensuring that Vkey() is always valid.
if (!_vkey)
{
_vkey = MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK_EX);
}
}
VirtualKeyModifiers KeyChord::Modifiers() noexcept

View file

@ -33,7 +33,8 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer;
constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);
// The minimum delay between updating the TSF input control.
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100);
// This is already throttled primarily in the ControlCore, with a timeout of 100ms. We're adding another smaller one here, as the (potentially x-proc) call will come in off the UI thread
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(8);
// The minimum delay between updating the locations of regex patterns
constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(500);
@ -64,10 +65,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, connection);
_core = _interactivity.Core();
// Use a manual revoker on the output event, so we can immediately stop
// worrying about it on destruction.
_coreOutputEventToken = _core.ReceivedOutput({ this, &TermControl::_coreReceivedOutput });
// These events might all be triggered by the connection, but that
// should be drained and closed before we complete destruction. So these
// are safe.
@ -104,37 +101,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
});
// Many of these ThrottledFunc's should be inside ControlCore. However,
// currently they depend on the Dispatcher() of the UI thread, which the
// Core eventually won't have access to. When we get to
// https://github.com/microsoft/terminal/projects/5#card-50760282
// then we'll move the applicable ones.
//
// These four throttled functions are triggered by terminal output and interact with the UI.
// Get our dispatcher. This will get us the same dispatcher as
// TermControl::Dispatcher().
auto dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
// These three throttled functions are triggered by terminal output and interact with the UI.
// Since Close() is the point after which we are removed from the UI, but before the
// destructor has run, we MUST check control->_IsClosing() before actually doing anything.
_tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
Dispatcher(),
TsfRedrawInterval,
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() }; !control->_IsClosing())
{
control->TSFInputControl().TryRedrawCanvas();
}
});
_updatePatternLocations = std::make_shared<ThrottledFuncTrailing<>>(
Dispatcher(),
UpdatePatternLocationsInterval,
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() }; !control->_IsClosing())
{
control->_core.UpdatePatternLocations();
}
});
_playWarningBell = std::make_shared<ThrottledFuncLeading>(
Dispatcher(),
dispatcher,
TerminalWarningBellInterval,
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() }; !control->_IsClosing())
@ -144,7 +119,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
});
_updateScrollBar = std::make_shared<ThrottledFuncTrailing<ScrollBarUpdate>>(
Dispatcher(),
dispatcher,
ScrollBarUpdateInterval,
[weakThis = get_weak()](const auto& update) {
if (auto control{ weakThis.get() }; !control->_IsClosing())
@ -540,9 +515,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
// create a custom automation peer with this code pattern:
// (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers)
if (const auto& interactivityAutoPeer = _interactivity.OnCreateAutomationPeer())
if (const auto& interactivityAutoPeer{ _interactivity.OnCreateAutomationPeer() })
{
_automationPeer = winrt::make<implementation::TermControlAutomationPeer>(this, interactivityAutoPeer);
auto margins{ SwapChainPanel().Margin() };
Core::Padding padding{ margins.Left,
margins.Top,
margins.Right,
margins.Bottom };
_automationPeer = winrt::make<implementation::TermControlAutomationPeer>(this, padding, interactivityAutoPeer);
return _automationPeer;
}
}
@ -786,28 +767,39 @@ namespace winrt::Microsoft::Terminal::Control::implementation
(void)_TrySendKeyEvent(VK_MENU, scanCode, modifiers, false);
handled = true;
}
else if (vkey == VK_F7 && down)
else if ((vkey == VK_F7 || vkey == VK_SPACE) && down)
{
// Manually generate an F7 event into the key bindings or terminal.
// This is required as part of GH#638.
// Or do so for alt+space; only send to terminal when explicitly unbound
// That is part of #GH7125
auto bindings{ _settings.KeyBindings() };
bool isUnbound = false;
const KeyChord kc = {
modifiers.IsCtrlPressed(),
modifiers.IsAltPressed(),
modifiers.IsShiftPressed(),
modifiers.IsWinPressed(),
gsl::narrow_cast<WORD>(vkey),
0
};
if (bindings)
{
handled = bindings.TryKeyChord({
modifiers.IsCtrlPressed(),
modifiers.IsAltPressed(),
modifiers.IsShiftPressed(),
modifiers.IsWinPressed(),
VK_F7,
0,
});
handled = bindings.TryKeyChord(kc);
if (!handled)
{
isUnbound = bindings.IsKeyChordExplicitlyUnbound(kc);
}
}
if (!handled)
const bool sendToTerminal = vkey == VK_F7 || (vkey == VK_SPACE && isUnbound);
if (!handled && sendToTerminal)
{
// _TrySendKeyEvent pretends it didn't handle F7 for some unknown reason.
(void)_TrySendKeyEvent(VK_F7, scanCode, modifiers, true);
(void)_TrySendKeyEvent(gsl::narrow_cast<WORD>(vkey), scanCode, modifiers, true);
// GH#6438: Note that we're _not_ sending the key up here - that'll
// get passed through XAML to our KeyUp handler normally.
handled = true;
@ -1276,23 +1268,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
CATCH_LOG();
}
void TermControl::_coreReceivedOutput(const IInspectable& /*sender*/,
const IInspectable& /*args*/)
{
// Queue up a throttled UpdatePatternLocations call. In the future, we
// should have the _updatePatternLocations ThrottledFunc internal to
// ControlCore, and run on that object's dispatcher queue.
//
// We're not doing that quite yet, because the Core will eventually
// be out-of-proc from the UI thread, and won't be able to just use
// the UI thread as the dispatcher queue thread.
//
// THIS IS CALLED ON EVERY STRING OF TEXT OUTPUT TO THE TERMINAL. Think
// twice before adding anything here.
_updatePatternLocations->Run();
}
// Method Description:
// - Reset the font size of the terminal to its default size.
// Arguments:
@ -1330,8 +1305,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_updateScrollBar->ModifyPending([](auto& update) {
update.newValue.reset();
});
_updatePatternLocations->Run();
}
// Method Description:
@ -1665,7 +1638,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
update.newValue = args.ViewTop();
_updateScrollBar->Run(update);
_updatePatternLocations->Run();
}
// Method Description:
@ -1673,10 +1645,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// to be where the current cursor position is.
// Arguments:
// - N/A
void TermControl::_CursorPositionChanged(const IInspectable& /*sender*/,
const IInspectable& /*args*/)
winrt::fire_and_forget TermControl::_CursorPositionChanged(const IInspectable& /*sender*/,
const IInspectable& /*args*/)
{
_tsfTryRedrawCanvas->Run();
// Prior to GH#10187, this fired a trailing throttled func to update the
// TSF canvas only every 100ms. Now, the throttling occurs on the
// ControlCore side. If we're told to update the cursor position, we can
// just go ahead and do it.
// This can come in off the COM thread - hop back to the UI thread.
auto weakThis{ get_weak() };
co_await resume_foreground(Dispatcher());
if (auto control{ weakThis.get() }; !control->_IsClosing())
{
control->TSFInputControl().TryRedrawCanvas();
}
}
hstring TermControl::Title()
@ -1730,8 +1712,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_closing = true;
_core.ReceivedOutput(_coreOutputEventToken);
_RestorePointerCursorHandlers(*this, nullptr);
// Disconnect the TSF input control so it doesn't receive EditContext events.
TSFInputControl().Close();
_autoScrollTimer.Stop();

View file

@ -155,8 +155,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool _focused{ false };
bool _initializedTerminal{ false };
std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas;
std::shared_ptr<ThrottledFuncTrailing<>> _updatePatternLocations;
std::shared_ptr<ThrottledFuncLeading> _playWarningBell;
struct ScrollBarUpdate
@ -166,7 +164,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
double newMinimum;
double newViewportSize;
};
std::shared_ptr<ThrottledFuncTrailing<ScrollBarUpdate>> _updateScrollBar;
bool _isInternalScrollBarUpdate;
// Auto scroll occurs when user, while selecting, drags cursor outside
@ -183,14 +183,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
std::optional<Windows::UI::Xaml::DispatcherTimer> _cursorTimer;
std::optional<Windows::UI::Xaml::DispatcherTimer> _blinkTimer;
event_token _coreOutputEventToken;
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
inline bool _IsClosing() const noexcept
{
#ifndef NDEBUG
// _closing isn't atomic and may only be accessed from the main thread.
assert(Dispatcher().HasThreadAccess());
if (const auto dispatcher = Dispatcher())
{
assert(dispatcher.HasThreadAccess());
}
#endif
return _closing;
}
@ -235,7 +238,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _TerminalTabColorChanged(const std::optional<til::color> color);
void _ScrollPositionChanged(const IInspectable& sender, const Control::ScrollPositionChangedArgs& args);
void _CursorPositionChanged(const IInspectable& sender, const IInspectable& args);
winrt::fire_and_forget _CursorPositionChanged(const IInspectable& sender, const IInspectable& args);
bool _CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
bool _ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
@ -267,7 +270,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const int fontHeight,
const bool isInitialChange);
winrt::fire_and_forget _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args);
void _coreReceivedOutput(const IInspectable& sender, const IInspectable& args);
void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args);
void _coreWarningBell(const IInspectable& sender, const IInspectable& args);
};

View file

@ -31,13 +31,14 @@ namespace XamlAutomation
namespace winrt::Microsoft::Terminal::Control::implementation
{
TermControlAutomationPeer::TermControlAutomationPeer(TermControl* owner,
const Core::Padding padding,
Control::InteractivityAutomationPeer impl) :
TermControlAutomationPeerT<TermControlAutomationPeer>(*owner), // pass owner to FrameworkElementAutomationPeer
_termControl{ owner },
_contentAutomationPeer{ impl }
{
UpdateControlBounds();
SetControlPadding(padding);
// Listen for UIA signalling events from the implementation. We need to
// be the one to actually raise these automation events, so they go
// through the UI tree correctly.

View file

@ -43,6 +43,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
public:
TermControlAutomationPeer(Microsoft::Terminal::Control::implementation::TermControl* owner,
const Core::Padding padding,
Control::InteractivityAutomationPeer implementation);
void UpdateControlBounds();

View file

@ -66,5 +66,6 @@ namespace Microsoft.Terminal.Core
Microsoft.Terminal.Core.Color CursorColor;
CursorStyle CursorShape;
UInt32 CursorHeight;
Boolean IntenseIsBright;
};
}

View file

@ -52,7 +52,8 @@ Terminal::Terminal() :
_selection{ std::nullopt },
_taskbarState{ 0 },
_taskbarProgress{ 0 },
_trimBlockSelection{ false }
_trimBlockSelection{ false },
_intenseIsBright{ true }
{
auto dispatch = std::make_unique<TerminalDispatch>(*this);
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
@ -173,6 +174,7 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance)
til::color newBackgroundColor{ appearance.DefaultBackground() };
_defaultBg = newBackgroundColor.with_alpha(0);
_defaultFg = appearance.DefaultForeground();
_intenseIsBright = appearance.IntenseIsBright();
for (int i = 0; i < 16; i++)
{
@ -594,14 +596,6 @@ bool Terminal::SendKeyEvent(const WORD vkey,
const auto isAltOnlyPressed = states.IsAltPressed() && !states.IsCtrlPressed();
// DON'T manually handle Alt+Space - the system will use this to bring up
// the system menu for restore, min/maximize, size, move, close.
// (This doesn't apply to Ctrl+Alt+Space.)
if (isAltOnlyPressed && vkey == VK_SPACE)
{
return false;
}
// By default Windows treats Ctrl+Alt as an alias for AltGr.
// When the altGrAliasing setting is set to false, this behaviour should be disabled.
//
@ -672,13 +666,6 @@ bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButt
// - false otherwise.
bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states)
{
// DON'T manually handle Alt+Space - the system will use this to bring up
// the system menu for restore, min/maximize, size, move, close.
if (ch == L' ' && states.IsAltPressed() && !states.IsCtrlPressed())
{
return false;
}
auto vkey = _TakeVirtualKeyFromLastKeyEvent(scanCode);
if (vkey == 0 && scanCode != 0)
{

View file

@ -283,6 +283,7 @@ private:
bool _suppressApplicationTitle;
bool _bracketedPasteMode;
bool _trimBlockSelection;
bool _intenseIsBright;
size_t _taskbarState;
size_t _taskbarProgress;

View file

@ -16,7 +16,7 @@ try
_WriteBuffer(stringView);
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
bool Terminal::ExecuteChar(wchar_t wch) noexcept
try
@ -24,7 +24,7 @@ try
_WriteBuffer({ &wch, 1 });
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
TextAttribute Terminal::GetTextAttributes() const noexcept
{
@ -54,7 +54,7 @@ try
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
COORD Terminal::GetCursorPosition() noexcept
{
@ -75,7 +75,7 @@ try
_buffer->GetCursor().SetColor(color);
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
// Method Description:
// - Moves the cursor down one line, and possibly also to the leftmost column.
@ -101,7 +101,7 @@ try
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
// Method Description:
// - deletes count characters starting from the cursor's current position
@ -150,7 +150,7 @@ try
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
// Method Description:
// - Inserts count spaces starting from the cursor's current position, moving over the existing text
@ -205,7 +205,7 @@ try
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
bool Terminal::EraseCharacters(const size_t numChars) noexcept
try
@ -218,7 +218,7 @@ try
_buffer->Write(eraseIter, absoluteCursorPos);
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
// Method description:
// - erases a line of text, either from
@ -264,7 +264,7 @@ try
_buffer->Write(eraseIter, startPos, false);
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
// Method description:
// - erases text in the buffer in two ways depending on erase type
@ -348,7 +348,7 @@ try
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
bool Terminal::WarningBell() noexcept
try
@ -356,7 +356,7 @@ try
_pfnWarningBell();
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
bool Terminal::SetWindowTitle(std::wstring_view title) noexcept
try
@ -368,7 +368,7 @@ try
}
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
// Method Description:
// - Updates the value in the colortable at index tableIndex to the new color
@ -387,7 +387,7 @@ try
_buffer->GetRenderTarget().TriggerRedrawAll();
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
// Method Description:
// - Sets the cursor style to the given style.
@ -457,7 +457,7 @@ try
_buffer->GetRenderTarget().TriggerRedrawAll();
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
// Method Description:
// - Updates the default background color from a COLORREF, format 0x00BBGGRR.
@ -475,7 +475,7 @@ try
_buffer->GetRenderTarget().TriggerRedrawAll();
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
til::color Terminal::GetDefaultBackground() const noexcept
{
@ -509,7 +509,7 @@ try
_buffer->GetRenderTarget().TriggerRedrawAll();
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
bool Terminal::EnableVT200MouseMode(const bool enabled) noexcept
{
@ -591,7 +591,7 @@ try
return true;
}
CATCH_LOG_RETURN_FALSE()
CATCH_RETURN_FALSE()
// Method Description:
// - Updates the buffer's current text attributes to start a hyperlink

View file

@ -50,7 +50,8 @@ std::pair<COLORREF, COLORREF> Terminal::GetAttributeColors(const TextAttribute&
_defaultFg,
_defaultBg,
_screenReversed,
_blinkingState.IsBlinkingFaint());
_blinkingState.IsBlinkingFaint(),
_intenseIsBright);
colors.first |= 0xff000000;
// We only care about alpha for the default BG (which enables acrylic)
// If the bg isn't the default bg color, or reverse video is enabled, make it fully opaque.

View file

@ -107,7 +107,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(Model::CascadiaSettings, Settings, nullptr)
};
struct Actions : ActionsT<Actions>
struct Actions : public HasScrollViewer<Actions>, ActionsT<Actions>
{
public:
Actions();

View file

@ -374,7 +374,7 @@
</ResourceDictionary>
</Page.Resources>
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel MaxWidth="600"
Spacing="8"
Style="{StaticResource SettingsStackStyle}">

View file

@ -43,7 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_CALLBACK(AddNew, AddNewArgs);
};
struct AddProfile : AddProfileT<AddProfile>
struct AddProfile : public HasScrollViewer<AddProfile>, AddProfileT<AddProfile>
{
public:
AddProfile();

View file

@ -21,7 +21,7 @@
</ResourceDictionary>
</Page.Resources>
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel Style="{StaticResource SettingsStackStyle}">
<Button x:Uid="AddProfile_AddNewButton"
AutomationProperties.AutomationId="AddProfile_AddNewButton"

View file

@ -131,6 +131,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
const auto backgroundImgCheckboxTooltip{ ToolTipService::GetToolTip(UseDesktopImageCheckBox()) };
Automation::AutomationProperties::SetFullDescription(UseDesktopImageCheckBox(), unbox_value<hstring>(backgroundImgCheckboxTooltip));
INITIALIZE_BINDABLE_ENUM_SETTING(IntenseTextStyle, IntenseTextStyle, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle, L"Appearance_IntenseTextStyle", L"Content");
}
// Method Description:
@ -256,6 +258,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"ShowAllFonts" });
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"UsingMonospaceFont" });
}
else if (settingName == L"IntenseTextStyle")
{
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"CurrentIntenseTextStyle" });
}
// YOU THERE ADDING A NEW APPEARANCE SETTING
// Make sure you add a block like
//
// else if (settingName == L"MyNewSetting")
// {
// _PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"CurrentMyNewSetting" });
// }
//
// To make sure that changes to the AppearanceViewModel will
// propagate back up to the actual UI (in Appearances). The
// CurrentMyNewSetting properties are the ones that are bound in
// XAML. If you don't do this right (or only raise a property
// changed for "MyNewSetting"), then things like the reset
// button won't work right.
});
// make sure to send all the property changed events once here
@ -271,6 +291,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"CurrentFontFace" });
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"ShowAllFonts" });
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"UsingMonospaceFont" });
_PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"CurrentIntenseTextStyle" });
}
}

View file

@ -92,6 +92,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
OBSERVABLE_PROJECTED_SETTING(_appearance, BackgroundImageOpacity);
OBSERVABLE_PROJECTED_SETTING(_appearance, BackgroundImageStretchMode);
OBSERVABLE_PROJECTED_SETTING(_appearance, BackgroundImageAlignment);
OBSERVABLE_PROJECTED_SETTING(_appearance, IntenseTextStyle);
private:
Model::AppearanceConfig _appearance;
@ -136,6 +137,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
GETSET_BINDABLE_ENUM_SETTING(BackgroundImageStretchMode, Windows::UI::Xaml::Media::Stretch, Appearance, BackgroundImageStretchMode);
GETSET_BINDABLE_ENUM_SETTING(IntenseTextStyle, Microsoft::Terminal::Settings::Model::IntenseStyle, Appearance, IntenseTextStyle);
private:
bool _ShowAllFonts;
void _UpdateBIAlignmentControl(const int32_t val);

View file

@ -45,6 +45,7 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Double, BackgroundImageOpacity);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.UI.Xaml.Media.Stretch, BackgroundImageStretchMode);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Microsoft.Terminal.Settings.Model.ConvergedAlignment, BackgroundImageAlignment);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Microsoft.Terminal.Settings.Model.IntenseStyle, IntenseTextStyle);
}
[default_interface] runtimeclass Appearances : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged
@ -73,5 +74,8 @@ namespace Microsoft.Terminal.Settings.Editor
IInspectable CurrentFontFace { get; };
Windows.UI.Xaml.Controls.Slider BIOpacitySlider { get; };
IInspectable CurrentIntenseTextStyle;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> IntenseTextStyleList { get; };
}
}

View file

@ -428,5 +428,23 @@
</Grid>
</local:SettingContainer>
</StackPanel>
<!-- Grouping: Text Formatting -->
<StackPanel Style="{StaticResource PivotStackStyle}">
<TextBlock x:Uid="Appearance_TextFormattingHeader"
Style="{StaticResource SubtitleTextBlockStyle}" />
<!-- Intense is bold, bright -->
<local:SettingContainer x:Uid="Appearance_IntenseTextStyle"
Margin="0"
ClearSettingValue="{x:Bind Appearance.ClearIntenseTextStyle}"
HasSettingValue="{x:Bind Appearance.HasIntenseTextStyle, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.IntenseTextStyleOverrideSource, Mode=OneWay}">
<muxc:RadioButtons ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
ItemsSource="{x:Bind IntenseTextStyleList, Mode=OneWay}"
SelectedItem="{x:Bind CurrentIntenseTextStyle, Mode=TwoWay}" />
</local:SettingContainer>
</StackPanel>
</StackPanel>
</UserControl>

View file

@ -20,7 +20,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(winrt::hstring, LastSelectedScheme, L"");
};
struct ColorSchemes : ColorSchemesT<ColorSchemes>
struct ColorSchemes : public HasScrollViewer<ColorSchemes>, ColorSchemesT<ColorSchemes>
{
ColorSchemes();

View file

@ -93,7 +93,7 @@
</ResourceDictionary>
</Page.Resources>
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel Margin="{StaticResource StandardIndentMargin}"
Spacing="24">

View file

@ -18,7 +18,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(Model::GlobalAppSettings, Globals, nullptr)
};
struct GlobalAppearance : GlobalAppearanceT<GlobalAppearance>
struct GlobalAppearance : public HasScrollViewer<GlobalAppearance>, GlobalAppearanceT<GlobalAppearance>
{
public:
GlobalAppearance();

View file

@ -24,7 +24,7 @@
</ResourceDictionary>
</Page.Resources>
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Language -->
<local:SettingContainer x:Uid="Globals_Language"

View file

@ -18,7 +18,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(Model::GlobalAppSettings, Globals, nullptr)
};
struct Interaction : InteractionT<Interaction>
struct Interaction : public HasScrollViewer<Interaction>, InteractionT<Interaction>
{
Interaction();

View file

@ -24,7 +24,7 @@
</ResourceDictionary>
</Page.Resources>
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Copy On Select -->
<local:SettingContainer x:Uid="Globals_CopyOnSelect"

View file

@ -18,7 +18,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(Model::CascadiaSettings, Settings, nullptr)
};
struct Launch : LaunchT<Launch>
struct Launch : public HasScrollViewer<Launch>, LaunchT<Launch>
{
public:
Launch();

View file

@ -33,7 +33,7 @@
</ResourceDictionary>
</Page.Resources>
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel>
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Default Profile -->

View file

@ -117,7 +117,17 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Frame x:Name="contentFrame"
Grid.Row="0" />
Grid.Row="0">
<Frame.ContentTransitions>
<TransitionCollection>
<NavigationThemeTransition>
<NavigationThemeTransition.DefaultNavigationTransitionInfo>
<DrillInNavigationTransitionInfo />
</NavigationThemeTransition.DefaultNavigationTransitionInfo>
</NavigationThemeTransition>
</TransitionCollection>
</Frame.ContentTransitions>
</Frame>
<!-- Explicitly set the background color on grid to prevent the navigation animation from overflowing it -->
<Grid Grid.Row="1"
Height="100"

View file

@ -153,7 +153,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Windows::Foundation::Collections::IMapView<hstring, Model::ColorScheme> _Schemes;
};
struct Profiles : ProfilesT<Profiles>
struct Profiles : public HasScrollViewer<Profiles>, ProfilesT<Profiles>
{
public:
Profiles();

View file

@ -56,7 +56,7 @@
SelectionChanged="Pivot_SelectionChanged">
<!-- General Tab -->
<PivotItem x:Uid="Profile_General">
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel Style="{StaticResource PivotStackStyle}">
<!-- Name -->
@ -225,7 +225,7 @@
<!-- Appearance Tab -->
<PivotItem x:Uid="Profile_Appearance">
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel>
<!-- Control Preview -->
<Border x:Name="ControlPreview"
@ -397,7 +397,7 @@
<!-- Advanced Tab -->
<PivotItem x:Uid="Profile_Advanced">
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel Style="{StaticResource PivotStackStyle}">
<!-- Suppress Application Title -->
<local:SettingContainer x:Uid="Profile_SuppressApplicationTitle"

View file

@ -32,7 +32,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
TYPED_EVENT(OpenJson, Windows::Foundation::IInspectable, Model::SettingsTarget);
};
struct ReadOnlyActions : ReadOnlyActionsT<ReadOnlyActions>
struct ReadOnlyActions : public HasScrollViewer<ReadOnlyActions>, ReadOnlyActionsT<ReadOnlyActions>
{
public:
ReadOnlyActions();

View file

@ -182,7 +182,7 @@
</ResourceDictionary>
</Page.Resources>
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel Style="{StaticResource SettingsStackStyle}">
<TextBlock x:Uid="Globals_KeybindingsDisclaimer"
Style="{StaticResource DisclaimerStyle}" />

View file

@ -18,7 +18,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(Model::GlobalAppSettings, Globals, nullptr)
};
struct Rendering : RenderingT<Rendering>
struct Rendering : public HasScrollViewer<Rendering>, RenderingT<Rendering>
{
Rendering();

View file

@ -18,7 +18,7 @@
</ResourceDictionary>
</Page.Resources>
<ScrollViewer>
<ScrollViewer ViewChanging="ViewChanging">
<StackPanel Style="{StaticResource SettingsStackStyle}">
<TextBlock x:Uid="Globals_RenderingDisclaimer"
Style="{StaticResource DisclaimerStyle}" />

View file

@ -232,7 +232,7 @@
<comment>Header for a control to toggle if the app should always show the tabs (similar to a website browser).</comment>
</data>
<data name="Globals_AlwaysShowTabs.HelpText" xml:space="preserve">
<value>When unchecked, the tab bar will appear when a new tab is created.</value>
<value>When disabled, the tab bar will appear when a new tab is created.</value>
<comment>A description for what the "always show tabs" setting does. Presented near "Globals_AlwaysShowTabs.Header".</comment>
</data>
<data name="Globals_CopyOnSelect.Header" xml:space="preserve">
@ -272,7 +272,7 @@
<comment>Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames.</comment>
</data>
<data name="Globals_ForceFullRepaint.HelpText" xml:space="preserve">
<value>When unchecked, the terminal will render only the updates to the screen between frames.</value>
<value>When disabled, the terminal will render only the updates to the screen between frames.</value>
<comment>A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header".</comment>
</data>
<data name="Globals_InitialCols.Header" xml:space="preserve">
@ -340,7 +340,7 @@
<comment>Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app.</comment>
</data>
<data name="Globals_ShowTitlebar.HelpText" xml:space="preserve">
<value>When unchecked, the title bar will appear above the tabs.</value>
<value>When disabled, the title bar will appear above the tabs.</value>
<comment>A description for what the "show titlebar" setting does. Presented near "Globals_ShowTitlebar.Header".</comment>
</data>
<data name="Globals_ShowTitleInTitlebar.Header" xml:space="preserve">
@ -348,7 +348,7 @@
<comment>Header for a control to toggle whether the terminal's title is shown as the application title, or not.</comment>
</data>
<data name="Globals_ShowTitleInTitlebar.HelpText" xml:space="preserve">
<value>When unchecked, the title bar will be 'Windows Terminal'.</value>
<value>When disabled, the title bar will be 'Windows Terminal'.</value>
<comment>A description for what the "show title in titlebar" setting does. Presented near "Globals_ShowTitleInTitlebar.Header".{Locked="Windows"}</comment>
</data>
<data name="Globals_SnapToGridOnResize.Header" xml:space="preserve">
@ -356,7 +356,7 @@
<comment>Header for a control to toggle whether the terminal snaps the window to the character grid when resizing, or not.</comment>
</data>
<data name="Globals_SnapToGridOnResize.HelpText" xml:space="preserve">
<value>When unchecked, the window will resize smoothly.</value>
<value>When disabled, the window will resize smoothly.</value>
<comment>A description for what the "snap to grid on resize" setting does. Presented near "Globals_SnapToGridOnResize.Header".</comment>
</data>
<data name="Globals_SoftwareRendering.Header" xml:space="preserve">
@ -364,7 +364,7 @@
<comment>Header for a control to toggle whether the terminal should use software to render content instead of the hardware.</comment>
</data>
<data name="Globals_SoftwareRendering.HelpText" xml:space="preserve">
<value>When checked, the terminal will use the software renderer (a.k.a. WARP) instead of the hardware one.</value>
<value>When enabled, the terminal will use the software renderer (a.k.a. WARP) instead of the hardware one.</value>
<comment>A description for what the "software rendering" setting does. Presented near "Globals_SoftwareRendering.Header".</comment>
</data>
<data name="Globals_StartOnUserLogin.Header" xml:space="preserve">
@ -372,7 +372,7 @@
<comment>Header for a control to toggle whether the app should launch when the user's machine starts up, or not.</comment>
</data>
<data name="Globals_StartOnUserLogin.HelpText" xml:space="preserve">
<value>When checked, this enables the launch of Windows Terminal at machine startup.</value>
<value>When enabled, this enables the launch of Windows Terminal at machine startup.</value>
<comment>A description for what the "start on user login" setting does. Presented near "Globals_StartOnUserLogin.Header".</comment>
</data>
<data name="Globals_AlwaysOnTop.Header" xml:space="preserve">
@ -480,7 +480,7 @@
<comment>Header for a control to toggle whether the app treats ctrl+alt as the AltGr (also known as the Alt Graph) modifier key found on keyboards. {Locked="AltGr"}</comment>
</data>
<data name="Profile_AltGrAliasing.HelpText" xml:space="preserve">
<value>By default Windows treats Ctrl+Alt as an alias for AltGr. When unchecked, this behavior will be disabled.</value>
<value>By default Windows treats Ctrl+Alt as an alias for AltGr. When disabled, this behavior will be disabled.</value>
<comment>A description for what the "AltGr aliasing" setting does. Presented near "Profile_AltGrAliasing.Header".</comment>
</data>
<data name="Profile_AntialiasingMode.Header" xml:space="preserve">
@ -700,7 +700,7 @@
<comment>Header for a control to toggle whether the profile is shown in a dropdown menu, or not.</comment>
</data>
<data name="Profile_Hidden.HelpText" xml:space="preserve">
<value>If checked, the profile will not appear in the list of profiles. This can be used to hide default profiles and dynamically generated profiles, while leaving them in your settings file.</value>
<value>If enabled, the profile will not appear in the list of profiles. This can be used to hide default profiles and dynamically generated profiles, while leaving them in your settings file.</value>
<comment>A description for what the "hidden" setting does. Presented near "Profile_Hidden".</comment>
</data>
<data name="Profile_HistorySize.Header" xml:space="preserve">
@ -736,7 +736,7 @@
<comment>Header for a control to toggle classic CRT display effects, which gives the terminal a retro look.</comment>
</data>
<data name="Profile_RetroTerminalEffect.HelpText" xml:space="preserve">
<value>When checked, enables retro terminal effects such as glowing text and scan lines.</value>
<value>When enabled, enables retro terminal effects such as glowing text and scan lines.</value>
<comment>A description for what the "retro terminal effects" setting does. Presented near "Profile_RetroTerminalEffect".</comment>
</data>
<data name="Profile_ScrollbarVisibility.Header" xml:space="preserve">
@ -772,7 +772,7 @@
<comment>A supplementary setting to the "starting directory" setting. "Parent" refers to the parent process of the current process.</comment>
</data>
<data name="Profile_StartingDirectoryUseParentCheckbox.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>If checked, this profile will spawn in the directory from which Windows Terminal was launched.</value>
<value>If enabled, this profile will spawn in the directory from which Windows Terminal was launched.</value>
<comment>A description for what the supplementary "use parent process directory" setting does. Presented near "Profile_StartingDirectoryUseParentCheckbox".</comment>
</data>
<data name="Profile_SuppressApplicationTitle.Header" xml:space="preserve">
@ -808,7 +808,7 @@
<comment>A supplementary setting to the "background image" setting. When enabled, the OS desktop wallpaper is used as the background image. Presented near "Profile_BackgroundImage".</comment>
</data>
<data name="Profile_UseDesktopImage.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>When checked, use the desktop wallpaper image as the background image for the terminal.</value>
<value>When enabled, use the desktop wallpaper image as the background image for the terminal.</value>
<comment>A description for what the supplementary "use desktop image" setting does. Presented near "Profile_UseDesktopImage".</comment>
</data>
<data name="Settings_ResetSettingsButton.Content" xml:space="preserve">
@ -1111,7 +1111,7 @@
<comment>A supplementary setting to the "font face" setting. Toggling this control updates the font face control to show all of the fonts installed.</comment>
</data>
<data name="Profile_FontFaceShowAllFonts.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>If checked, show all installed fonts in the list above. Otherwise, only show the list of monospace fonts.</value>
<value>If enabled, show all installed fonts in the list above. Otherwise, only show the list of monospace fonts.</value>
<comment>A description for what the supplementary "show all fonts" setting does. Presented near "Profile_FontFaceShowAllFonts".</comment>
</data>
<data name="Profile_CreateUnfocusedAppearanceButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
@ -1186,4 +1186,28 @@
<value>Shortcut</value>
<comment>The label for a "key chord listener" control that sets the keys a key binding is bound to.</comment>
</data>
</root>
<data name="Appearance_TextFormattingHeader.Text" xml:space="preserve">
<value>Text Formatting</value>
<comment>Header for a control to how text is formatted</comment>
</data>
<data name="Appearance_IntenseTextStyle.Header" xml:space="preserve">
<value>Intense Text Format</value>
<comment>Header for a control to select how "intense" text is formatted (bold, bright, both or none)</comment>
</data>
<data name="Appearance_IntenseTextStyleNone.Content" xml:space="preserve">
<value>None</value>
<comment>An option to choose from for the "intense text format" setting. When selected, "intense" text will not be rendered differently</comment>
</data>
<data name="Appearance_IntenseTextStyleBold.Content" xml:space="preserve">
<value>Bold</value>
<comment>An option to choose from for the "intense text format" setting. When selected, "intense" text will be rendered as bold text</comment>
</data>
<data name="Appearance_IntenseTextStyleBright.Content" xml:space="preserve">
<value>Bright</value>
<comment>An option to choose from for the "intense text format" setting. When selected, "intense" text will be rendered in a brighter color</comment>
</data>
<data name="Appearance_IntenseTextStyleAll.Content" xml:space="preserve">
<value>Both</value>
<comment>An option to choose from for the "intense text format" setting. When selected, "intense" text will be rendered as both bold text and in a brighter color</comment>
</data>
</root>

View file

@ -138,3 +138,41 @@ namespace winrt::Microsoft::Terminal::Settings
winrt::hstring GetSelectedItemTag(winrt::Windows::Foundation::IInspectable const& comboBoxAsInspectable);
winrt::hstring LocalizedNameForEnumName(const std::wstring_view sectionAndType, const std::wstring_view enumValue, const std::wstring_view propertyType);
}
// BODGY!
//
// The following function and struct are a workaround for GH#9320.
//
// DismissAllPopups can be used to dismiss all popups for a particular UI
// element. However, we've got a bunch of pages with scroll viewers that may or
// may not have popups in them. Rather than define the same exact body for all
// their ViewChanging events, the HasScrollViewer struct will just do it for
// you!
inline void DismissAllPopups(winrt::Windows::UI::Xaml::XamlRoot const& xamlRoot)
{
const auto popups{ winrt::Windows::UI::Xaml::Media::VisualTreeHelper::GetOpenPopupsForXamlRoot(xamlRoot) };
for (const auto& p : popups)
{
p.IsOpen(false);
}
}
template<typename T>
struct HasScrollViewer
{
// When the ScrollViewer scrolls, dismiss any popups we might have.
void ViewChanging(winrt::Windows::Foundation::IInspectable const& sender,
const winrt::Windows::UI::Xaml::Controls::ScrollViewerViewChangingEventArgs& /*e*/)
{
// Inside this struct, we can't get at the XamlRoot() that our subclass
// implements. I mean, _we_ can, but when XAML does it's code
// generation, _XAML_ won't be able to figure it out.
//
// Fortunately for us, we don't need to! The sender is a UIElement, so
// we can just get _their_ XamlRoot().
if (const auto& uielem{ sender.try_as<winrt::Windows::UI::Xaml::UIElement>() })
{
DismissAllPopups(uielem.XamlRoot());
}
}
};

View file

@ -21,6 +21,7 @@ static constexpr std::string_view ExecuteCommandlineKey{ "wt" };
static constexpr std::string_view FindKey{ "find" };
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
static constexpr std::string_view MovePaneKey{ "movePane" };
static constexpr std::string_view SwapPaneKey{ "swapPane" };
static constexpr std::string_view NewTabKey{ "newTab" };
static constexpr std::string_view NextTabKey{ "nextTab" };
static constexpr std::string_view OpenNewTabDropdownKey{ "openNewTabDropdown" };
@ -323,6 +324,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::Invalid, L"" },
{ ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
{ ShortcutAction::MovePane, RS_(L"MovePaneCommandKey") },
{ ShortcutAction::SwapPane, RS_(L"SwapPaneCommandKey") },
{ ShortcutAction::NewTab, RS_(L"NewTabCommandKey") },
{ ShortcutAction::NextTab, RS_(L"NextTabCommandKey") },
{ ShortcutAction::OpenNewTabDropdown, RS_(L"OpenNewTabDropdownCommandKey") },

View file

@ -13,6 +13,7 @@
#include "ResizePaneArgs.g.cpp"
#include "MoveFocusArgs.g.cpp"
#include "MovePaneArgs.g.cpp"
#include "SwapPaneArgs.g.cpp"
#include "AdjustFontSizeArgs.g.cpp"
#include "SendInputArgs.g.cpp"
#include "SplitPaneArgs.g.cpp"
@ -229,6 +230,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
};
}
winrt::hstring MovePaneArgs::GenerateName() const
{
return winrt::hstring{
fmt::format(L"{}, tab index:{}", RS_(L"MovePaneCommandKey"), TabIndex())
};
}
winrt::hstring SwitchToTabArgs::GenerateName() const
{
if (TabIndex() == UINT32_MAX)
@ -291,7 +299,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
};
}
winrt::hstring MovePaneArgs::GenerateName() const
winrt::hstring SwapPaneArgs::GenerateName() const
{
winrt::hstring directionString;
switch (Direction())
@ -309,10 +317,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
directionString = RS_(L"DirectionDown");
break;
case FocusDirection::Previous:
return RS_(L"MovePaneToLastUsedPane");
return RS_(L"SwapPaneToLastUsedPane");
}
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"MovePaneWithArgCommandKey")),
fmt::format(std::wstring_view(RS_(L"SwapPaneWithArgCommandKey")),
directionString)
};
}

View file

@ -13,6 +13,7 @@
#include "ResizePaneArgs.g.h"
#include "MoveFocusArgs.g.h"
#include "MovePaneArgs.g.h"
#include "SwapPaneArgs.g.h"
#include "AdjustFontSizeArgs.g.h"
#include "SendInputArgs.g.h"
#include "SplitPaneArgs.g.h"
@ -286,6 +287,57 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
};
struct MovePaneArgs : public MovePaneArgsT<MovePaneArgs>
{
MovePaneArgs() = default;
MovePaneArgs(uint32_t& tabIndex) :
_TabIndex{ tabIndex } {};
ACTION_ARG(uint32_t, TabIndex, 0);
static constexpr std::string_view TabIndexKey{ "index" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<MovePaneArgs>();
if (otherAsUs)
{
return otherAsUs->_TabIndex == _TabIndex;
}
return false;
};
static FromJsonResult FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<MovePaneArgs>();
JsonUtils::GetValueForKey(json, TabIndexKey, args->_TabIndex);
return { *args, {} };
}
static Json::Value ToJson(const IActionArgs& val)
{
if (!val)
{
return {};
}
Json::Value json{ Json::ValueType::objectValue };
const auto args{ get_self<MovePaneArgs>(val) };
JsonUtils::SetValueForKey(json, TabIndexKey, args->_TabIndex);
return json;
}
IActionArgs Copy() const
{
auto copy{ winrt::make_self<MovePaneArgs>() };
copy->_TabIndex = _TabIndex;
return *copy;
}
size_t Hash() const
{
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(TabIndex());
}
};
struct SwitchToTabArgs : public SwitchToTabArgsT<SwitchToTabArgs>
{
SwitchToTabArgs() = default;
@ -452,10 +504,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
};
struct MovePaneArgs : public MovePaneArgsT<MovePaneArgs>
struct SwapPaneArgs : public SwapPaneArgsT<SwapPaneArgs>
{
MovePaneArgs() = default;
MovePaneArgs(Model::FocusDirection direction) :
SwapPaneArgs() = default;
SwapPaneArgs(Model::FocusDirection direction) :
_Direction{ direction } {};
ACTION_ARG(Model::FocusDirection, Direction, FocusDirection::None);
@ -467,7 +519,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<MovePaneArgs>();
auto otherAsUs = other.try_as<SwapPaneArgs>();
if (otherAsUs)
{
return otherAsUs->_Direction == _Direction;
@ -477,7 +529,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static FromJsonResult FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<MovePaneArgs>();
auto args = winrt::make_self<SwapPaneArgs>();
JsonUtils::GetValueForKey(json, DirectionKey, args->_Direction);
if (args->Direction() == FocusDirection::None)
{
@ -495,13 +547,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return {};
}
Json::Value json{ Json::ValueType::objectValue };
const auto args{ get_self<MovePaneArgs>(val) };
const auto args{ get_self<SwapPaneArgs>(val) };
JsonUtils::SetValueForKey(json, DirectionKey, args->_Direction);
return json;
}
IActionArgs Copy() const
{
auto copy{ winrt::make_self<MovePaneArgs>() };
auto copy{ winrt::make_self<SwapPaneArgs>() };
copy->_Direction = _Direction;
return *copy;
}
@ -1708,6 +1760,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
BASIC_FACTORY(NewTabArgs);
BASIC_FACTORY(MoveFocusArgs);
BASIC_FACTORY(MovePaneArgs);
BASIC_FACTORY(SwapPaneArgs);
BASIC_FACTORY(SplitPaneArgs);
BASIC_FACTORY(SetColorSchemeArgs);
BASIC_FACTORY(ExecuteCommandlineArgs);

View file

@ -141,6 +141,12 @@ namespace Microsoft.Terminal.Settings.Model
NewTerminalArgs TerminalArgs { get; };
};
[default_interface] runtimeclass MovePaneArgs : IActionArgs
{
MovePaneArgs(UInt32 tabIndex);
UInt32 TabIndex;
};
[default_interface] runtimeclass SwitchToTabArgs : IActionArgs
{
SwitchToTabArgs(UInt32 tabIndex);
@ -158,9 +164,9 @@ namespace Microsoft.Terminal.Settings.Model
FocusDirection FocusDirection { get; };
};
[default_interface] runtimeclass MovePaneArgs : IActionArgs
[default_interface] runtimeclass SwapPaneArgs : IActionArgs
{
MovePaneArgs(FocusDirection direction);
SwapPaneArgs(FocusDirection direction);
FocusDirection Direction { get; };
};

View file

@ -47,12 +47,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return hashedAction ^ hashedArgs;
}
ActionMap::ActionMap() :
_NestedCommands{ single_threaded_map<hstring, Model::Command>() },
_IterableCommands{ single_threaded_vector<Model::Command>() }
{
}
// Method Description:
// - Retrieves the Command in the current layer, if it's valid
// - We internally store invalid commands as full commands.
@ -194,7 +188,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
// Update NameMap with our parents.
// Starting with this means we're doing a top-down approach.
FAIL_FAST_IF(_parents.size() > 1);
assert(_parents.size() <= 1);
for (const auto& parent : _parents)
{
parent->_PopulateNameMapWithSpecialCommands(nameMap);
@ -272,7 +266,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
});
// Now, add the accumulated actions from our parents
FAIL_FAST_IF(_parents.size() > 1);
assert(_parents.size() <= 1);
for (const auto& parent : _parents)
{
const auto parentActions{ parent->_GetCumulativeActions() };
@ -308,7 +302,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
// populate _KeyBindingMapCache
std::unordered_map<KeyChord, Model::Command, KeyChordHash, KeyChordEquality> keyBindingsMap;
std::unordered_set<KeyChord, KeyChordHash, KeyChordEquality> unboundKeys;
std::unordered_set<ActionMapKeyChord> unboundKeys;
_PopulateKeyBindingMapWithStandardCommands(keyBindingsMap, unboundKeys);
_KeyBindingMapCache = single_threaded_map<KeyChord, Model::Command>(std::move(keyBindingsMap));
@ -323,7 +317,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// Arguments:
// - keyBindingsMap: the keyBindingsMap we're populating. This maps the key chord of a command to the command itself.
// - unboundKeys: a set of keys that are explicitly unbound
void ActionMap::_PopulateKeyBindingMapWithStandardCommands(std::unordered_map<KeyChord, Model::Command, KeyChordHash, KeyChordEquality>& keyBindingsMap, std::unordered_set<Control::KeyChord, KeyChordHash, KeyChordEquality>& unboundKeys) const
void ActionMap::_PopulateKeyBindingMapWithStandardCommands(std::unordered_map<KeyChord, Model::Command, KeyChordHash, KeyChordEquality>& keyBindingsMap, std::unordered_set<ActionMapKeyChord>& unboundKeys) const
{
// Update KeyBindingsMap with our current layer
for (const auto& [keys, actionID] : _KeyMap)
@ -358,7 +352,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
// Update keyBindingsMap and unboundKeys with our parents
FAIL_FAST_IF(_parents.size() > 1);
assert(_parents.size() <= 1);
for (const auto& parent : _parents)
{
parent->_PopulateKeyBindingMapWithStandardCommands(keyBindingsMap, unboundKeys);
@ -369,35 +363,38 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
auto actionMap{ make_self<ActionMap>() };
// copy _KeyMap (KeyChord --> ID)
// KeyChord --> ID
actionMap->_KeyMap = _KeyMap;
// copy _ActionMap (ID --> Command)
// ID --> Command
actionMap->_ActionMap.reserve(_ActionMap.size());
for (const auto& [actionID, cmd] : _ActionMap)
{
actionMap->_ActionMap.emplace(actionID, *(get_self<Command>(cmd)->Copy()));
actionMap->_ActionMap.emplace(actionID, *winrt::get_self<Command>(cmd)->Copy());
}
// copy _MaskingActions (ID --> Command)
// ID --> Command
actionMap->_MaskingActions.reserve(_MaskingActions.size());
for (const auto& [actionID, cmd] : _MaskingActions)
{
actionMap->_MaskingActions.emplace(actionID, *(get_self<Command>(cmd)->Copy()));
actionMap->_MaskingActions.emplace(actionID, *winrt::get_self<Command>(cmd)->Copy());
}
// copy _NestedCommands (Name --> Command)
// Name --> Command
actionMap->_NestedCommands.reserve(_NestedCommands.size());
for (const auto& [name, cmd] : _NestedCommands)
{
actionMap->_NestedCommands.Insert(name, *(get_self<Command>(cmd)->Copy()));
actionMap->_NestedCommands.emplace(name, *winrt::get_self<Command>(cmd)->Copy());
}
// copy _IterableCommands
actionMap->_IterableCommands.reserve(_IterableCommands.size());
for (const auto& cmd : _IterableCommands)
{
actionMap->_IterableCommands.Append(*(get_self<Command>(cmd)->Copy()));
actionMap->_IterableCommands.emplace_back(*winrt::get_self<Command>(cmd)->Copy());
}
// repeat this for each of our parents
FAIL_FAST_IF(_parents.size() > 1);
assert(_parents.size() <= 1);
actionMap->_parents.reserve(_parents.size());
for (const auto& parent : _parents)
{
actionMap->_parents.emplace_back(parent->Copy());
@ -431,7 +428,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
const auto name{ cmd.Name() };
if (!name.empty())
{
_NestedCommands.Insert(name, cmd);
_NestedCommands.emplace(name, cmd);
}
return;
}
@ -439,7 +436,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// Handle iterable commands
if (cmdImpl->IterateOn() != ExpandCommandType::None)
{
_IterableCommands.Append(cmd);
_IterableCommands.emplace_back(cmd);
return;
}
@ -582,7 +579,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
// Handle a collision with NestedCommands
_NestedCommands.TryRemove(newName);
_NestedCommands.erase(newName);
}
// Method Description:
@ -677,6 +674,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
}
// Method Description:
// - Determines whether the given key chord is explicitly unbound
// Arguments:
// - keys: the key chord to check
// Return value:
// - true if the keychord is explicitly unbound
// - false if either the keychord is bound, or not bound at all
bool ActionMap::IsKeyChordExplicitlyUnbound(Control::KeyChord const& keys) const
{
// We use the fact that the ..Internal call returns nullptr for explicitly unbound
// key chords, and nullopt for keychord that are not bound - it allows us to distinguish
// between unbound and lack of binding.
return _GetActionByKeyChordInternal(keys) == nullptr;
}
// Method Description:
// - Retrieves the assigned command that can be invoked with the given key chord
// Arguments:
@ -686,21 +698,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// - nullptr if the key chord doesn't exist
Model::Command ActionMap::GetActionByKeyChord(Control::KeyChord const& keys) const
{
const auto modifiers = keys.Modifiers();
// The "keys" given to us can contain both a Vkey, as well as a ScanCode.
// For instance our UI code fills out a KeyChord with all available information.
// But our _KeyMap only contains KeyChords that contain _either_ a Vkey or ScanCode.
// Due to this we'll have to call _GetActionByKeyChordInternal twice.
if (auto vkey = keys.Vkey())
{
if (auto command = _GetActionByKeyChordInternal({ modifiers, vkey, 0 }))
{
return *command;
}
}
return _GetActionByKeyChordInternal({ modifiers, 0, keys.ScanCode() }).value_or(nullptr);
return _GetActionByKeyChordInternal(keys).value_or(nullptr);
}
// Method Description:
@ -712,8 +710,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// - the command with the given key chord
// - nullptr if the key chord is explicitly unbound
// - nullopt if it was not bound in this layer
std::optional<Model::Command> ActionMap::_GetActionByKeyChordInternal(Control::KeyChord const& keys) const
std::optional<Model::Command> ActionMap::_GetActionByKeyChordInternal(const ActionMapKeyChord keys) const
{
// KeyChord's constructor ensures that Modifiers() & Vkey() is a valid value at a minimum.
// This allows ActionMap to identify KeyChords which should "layer" (overwrite) each other.
// For instance win+sc(41) and win+` both specify the same KeyChord on an US keyboard layout
// from the perspective of a user. Either of the two should correctly overwrite the other.
// As such we need to pretend as if ScanCode doesn't exist.
assert(keys.vkey != 0);
// Check the current layer
if (const auto actionIDPair = _KeyMap.find(keys); actionIDPair != _KeyMap.end())
{
@ -724,7 +729,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// the command was not bound in this layer,
// ask my parents
FAIL_FAST_IF(_parents.size() > 1);
assert(_parents.size() <= 1);
for (const auto& parent : _parents)
{
const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal(keys) };
@ -774,7 +779,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
// Check our parents
FAIL_FAST_IF(_parents.size() > 1);
assert(_parents.size() <= 1);
for (const auto& parent : _parents)
{
if (const auto& keys{ parent->GetKeyBindingForAction(myAction, myArgs) })

View file

@ -28,6 +28,60 @@ namespace SettingsModelLocalTests
class TerminalSettingsTests;
}
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
union ActionMapKeyChord
{
uint16_t value = 0;
struct
{
uint8_t modifiers;
uint8_t vkey;
};
constexpr ActionMapKeyChord() = default;
ActionMapKeyChord(const Control::KeyChord& keys) :
modifiers(gsl::narrow_cast<uint8_t>(keys.Modifiers())),
vkey(gsl::narrow_cast<uint8_t>(keys.Vkey()))
{
}
constexpr bool operator==(ActionMapKeyChord other) const noexcept
{
return value == other.value;
}
};
}
template<>
struct std::hash<winrt::Microsoft::Terminal::Settings::Model::implementation::ActionMapKeyChord>
{
constexpr size_t operator()(winrt::Microsoft::Terminal::Settings::Model::implementation::ActionMapKeyChord keys) const noexcept
{
// I didn't like how std::hash uses the byte-wise FNV1a for integers.
// So I built my own std::hash with murmurhash3.
#if SIZE_MAX == UINT32_MAX
size_t h = keys.value;
h ^= h >> 16;
h *= UINT32_C(0x85ebca6b);
h ^= h >> 13;
h *= UINT32_C(0xc2b2ae35);
h ^= h >> 16;
return h;
#elif SIZE_MAX == UINT64_MAX
size_t h = keys.value;
h ^= h >> 33;
h *= UINT64_C(0xff51afd7ed558ccd);
h ^= h >> 33;
h *= UINT64_C(0xc4ceb9fe1a85ec53);
h ^= h >> 33;
return h;
#else
return std::hash<uint16_t>{}(keys.value);
#endif
}
};
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
using InternalActionID = size_t;
@ -36,7 +90,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
std::size_t operator()(const Control::KeyChord& key) const
{
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(key.Modifiers(), key.Vkey(), key.ScanCode());
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(key.Modifiers(), key.Vkey());
}
};
@ -44,14 +98,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
bool operator()(const Control::KeyChord& lhs, const Control::KeyChord& rhs) const
{
return lhs.Modifiers() == rhs.Modifiers() && lhs.Vkey() == rhs.Vkey() && lhs.ScanCode() == rhs.ScanCode();
return lhs.Modifiers() == rhs.Modifiers() && lhs.Vkey() == rhs.Vkey();
}
};
struct ActionMap : ActionMapT<ActionMap>, IInheritable<ActionMap>
{
ActionMap();
// views
Windows::Foundation::Collections::IMapView<hstring, Model::ActionAndArgs> AvailableActions();
Windows::Foundation::Collections::IMapView<hstring, Model::Command> NameMap();
@ -61,6 +113,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// queries
Model::Command GetActionByKeyChord(Control::KeyChord const& keys) const;
bool IsKeyChordExplicitlyUnbound(Control::KeyChord const& keys) const;
Control::KeyChord GetKeyBindingForAction(ShortcutAction const& action) const;
Control::KeyChord GetKeyBindingForAction(ShortcutAction const& action, IActionArgs const& actionArgs) const;
@ -79,12 +132,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
private:
std::optional<Model::Command> _GetActionByID(const InternalActionID actionID) const;
std::optional<Model::Command> _GetActionByKeyChordInternal(const Control::KeyChord& keys) const;
std::optional<Model::Command> _GetActionByKeyChordInternal(const ActionMapKeyChord keys) const;
void _PopulateAvailableActionsWithStandardCommands(std::unordered_map<hstring, Model::ActionAndArgs>& availableActions, std::unordered_set<InternalActionID>& visitedActionIDs) const;
void _PopulateNameMapWithSpecialCommands(std::unordered_map<hstring, Model::Command>& nameMap) const;
void _PopulateNameMapWithStandardCommands(std::unordered_map<hstring, Model::Command>& nameMap) const;
void _PopulateKeyBindingMapWithStandardCommands(std::unordered_map<Control::KeyChord, Model::Command, KeyChordHash, KeyChordEquality>& keyBindingsMap, std::unordered_set<Control::KeyChord, KeyChordHash, KeyChordEquality>& unboundKeys) const;
void _PopulateKeyBindingMapWithStandardCommands(std::unordered_map<Control::KeyChord, Model::Command, KeyChordHash, KeyChordEquality>& keyBindingsMap, std::unordered_set<ActionMapKeyChord>& unboundKeys) const;
std::vector<Model::Command> _GetCumulativeActions() const noexcept;
void _TryUpdateActionMap(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& consolidatedCmd);
@ -95,9 +148,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Windows::Foundation::Collections::IMap<hstring, Model::Command> _NameMapCache{ nullptr };
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _GlobalHotkeysCache{ nullptr };
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _KeyBindingMapCache{ nullptr };
Windows::Foundation::Collections::IMap<hstring, Model::Command> _NestedCommands{ nullptr };
Windows::Foundation::Collections::IVector<Model::Command> _IterableCommands{ nullptr };
std::unordered_map<Control::KeyChord, InternalActionID, KeyChordHash, KeyChordEquality> _KeyMap;
std::unordered_map<winrt::hstring, Model::Command> _NestedCommands;
std::vector<Model::Command> _IterableCommands;
std::unordered_map<ActionMapKeyChord, InternalActionID> _KeyMap;
std::unordered_map<InternalActionID, Model::Command> _ActionMap;
// Masking Actions:

View file

@ -8,6 +8,8 @@ namespace Microsoft.Terminal.Settings.Model
// This interface ensures that no changes are made to ActionMap
interface IActionMapView
{
Boolean IsKeyChordExplicitlyUnbound(Microsoft.Terminal.Control.KeyChord keys);
Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys);
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action);

View file

@ -50,6 +50,7 @@
ON_ALL_ACTIONS(ResizePane) \
ON_ALL_ACTIONS(MoveFocus) \
ON_ALL_ACTIONS(MovePane) \
ON_ALL_ACTIONS(SwapPane) \
ON_ALL_ACTIONS(Find) \
ON_ALL_ACTIONS(ToggleShaderEffects) \
ON_ALL_ACTIONS(ToggleFocusMode) \
@ -91,6 +92,7 @@
ON_ALL_ACTIONS_WITH_ARGS(GlobalSummon) \
ON_ALL_ACTIONS_WITH_ARGS(MoveFocus) \
ON_ALL_ACTIONS_WITH_ARGS(MovePane) \
ON_ALL_ACTIONS_WITH_ARGS(SwapPane) \
ON_ALL_ACTIONS_WITH_ARGS(MoveTab) \
ON_ALL_ACTIONS_WITH_ARGS(NewTab) \
ON_ALL_ACTIONS_WITH_ARGS(NewWindow) \

View file

@ -25,6 +25,7 @@ static constexpr std::string_view BackgroundImageStretchModeKey{ "backgroundImag
static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageAlignment" };
static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" };
static constexpr std::string_view PixelShaderPathKey{ "experimental.pixelShaderPath" };
static constexpr std::string_view IntenseTextStyleKey{ "intenseTextStyle" };
winrt::Microsoft::Terminal::Settings::Model::implementation::AppearanceConfig::AppearanceConfig(const winrt::weak_ref<Profile> sourceProfile) :
_sourceProfile(sourceProfile)
@ -48,6 +49,7 @@ winrt::com_ptr<AppearanceConfig> AppearanceConfig::CopyAppearance(const winrt::c
appearance->_BackgroundImageAlignment = sourceAppearance->_BackgroundImageAlignment;
appearance->_RetroTerminalEffect = sourceAppearance->_RetroTerminalEffect;
appearance->_PixelShaderPath = sourceAppearance->_PixelShaderPath;
appearance->_IntenseTextStyle = sourceAppearance->_IntenseTextStyle;
return appearance;
}
@ -68,6 +70,7 @@ Json::Value AppearanceConfig::ToJson() const
JsonUtils::SetValueForKey(json, BackgroundImageAlignmentKey, _BackgroundImageAlignment);
JsonUtils::SetValueForKey(json, RetroTerminalEffectKey, _RetroTerminalEffect);
JsonUtils::SetValueForKey(json, PixelShaderPathKey, _PixelShaderPath);
JsonUtils::SetValueForKey(json, IntenseTextStyleKey, _IntenseTextStyle);
return json;
}
@ -98,6 +101,7 @@ void AppearanceConfig::LayerJson(const Json::Value& json)
JsonUtils::GetValueForKey(json, BackgroundImageAlignmentKey, _BackgroundImageAlignment);
JsonUtils::GetValueForKey(json, RetroTerminalEffectKey, _RetroTerminalEffect);
JsonUtils::GetValueForKey(json, PixelShaderPathKey, _PixelShaderPath);
JsonUtils::GetValueForKey(json, IntenseTextStyleKey, _IntenseTextStyle);
}
winrt::Microsoft::Terminal::Settings::Model::Profile AppearanceConfig::SourceProfile()

View file

@ -52,6 +52,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
INHERITABLE_SETTING(Model::IAppearanceConfig, bool, RetroTerminalEffect, false);
INHERITABLE_SETTING(Model::IAppearanceConfig, hstring, PixelShaderPath, L"");
INHERITABLE_SETTING(Model::IAppearanceConfig, Model::IntenseStyle, IntenseTextStyle, Model::IntenseStyle::All);
private:
winrt::weak_ref<Profile> _sourceProfile;

View file

@ -348,6 +348,7 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::Duplicate
DUPLICATE_SETTING_MACRO_SUB(appearance, target, SelectionBackground);
DUPLICATE_SETTING_MACRO_SUB(appearance, target, CursorColor);
DUPLICATE_SETTING_MACRO_SUB(appearance, target, PixelShaderPath);
DUPLICATE_SETTING_MACRO_SUB(appearance, target, IntenseTextStyle);
DUPLICATE_SETTING_MACRO_SUB(appearance, target, BackgroundImagePath);
DUPLICATE_SETTING_MACRO_SUB(appearance, target, BackgroundImageOpacity);
DUPLICATE_SETTING_MACRO_SUB(appearance, target, BackgroundImageStretchMode);

View file

@ -155,15 +155,14 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::
const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().DefaultProfile();
std::optional<std::string> fileData = _ReadUserSettings();
const bool foundFile = fileData.has_value();
// Make sure the file isn't totally empty. If it is, we'll treat the file
// like it doesn't exist at all.
const bool fileHasData = foundFile && !fileData.value().empty();
const bool fileHasData = fileData && !fileData->empty();
bool needToWriteFile = false;
if (fileHasData)
{
resultPtr->_ParseJsonString(fileData.value(), false);
resultPtr->_ParseJsonString(*fileData, false);
}
// Load profiles from dynamic profile generators. _userSettings should be
@ -204,6 +203,35 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::
_CatchRethrowSerializationExceptionWithLocationInfo(resultPtr->_userSettingsString);
}
// Let's say a user doesn't know that they need to write `"hidden": true` in
// order to prevent a profile from showing up (and a settings UI doesn't exist).
// Naturally they would open settings.json and try to remove the profile object.
// This section of code recognizes if a profile was seen before and marks it as
// `"hidden": true` by default and thus ensures the behavior the user expects:
// Profiles won't show up again after they've been removed from settings.json.
{
const auto state = winrt::get_self<implementation::ApplicationState>(ApplicationState::SharedInstance());
auto generatedProfiles = state->GeneratedProfiles();
bool generatedProfilesChanged = false;
for (auto profile : resultPtr->_allProfiles)
{
if (generatedProfiles.emplace(profile.Guid()).second)
{
generatedProfilesChanged = true;
}
else if (profile.Origin() != OriginTag::User)
{
profile.Hidden(true);
}
}
if (generatedProfilesChanged)
{
state->GeneratedProfiles(generatedProfiles);
}
}
// After layering the user settings, check if there are any new profiles
// that need to be inserted into their user settings file.
needToWriteFile = resultPtr->_AppendDynamicProfilesToUserSettings() || needToWriteFile;
@ -352,7 +380,6 @@ void CascadiaSettings::_LoadDynamicProfiles()
}
}
const GUID nullGuid{ 0 };
for (auto& generator : _profileGenerators)
{
const std::wstring generatorNamespace{ generator->GetNamespace() };
@ -711,7 +738,7 @@ bool CascadiaSettings::_AppendDynamicProfilesToUserSettings()
wbuilder.settings_["indentation"] = " ";
wbuilder.settings_["enableYAMLCompatibility"] = true; // suppress spaces around colons
auto isInJsonObj = [](const auto& profile, const auto& json) {
static const auto isInJsonObj = [](const auto& profile, const auto& json) {
for (auto profileJson : _GetProfilesJsonObject(json))
{
if (profileJson.isObject())
@ -745,8 +772,16 @@ bool CascadiaSettings::_AppendDynamicProfilesToUserSettings()
for (const auto& profile : _allProfiles)
{
// Skip profiles that are in the user settings or the default settings.
if (isInJsonObj(profile, _userSettings) || isInJsonObj(profile, _defaultSettings))
// Skip profiles that are:
// * hidden
// Because when a user manually removes profiles from settings.json,
// we mark them as hidden in LoadAll(). Adding those profiles right
// back into settings.json would feel confusing, while the
// profile that was just erased is added right back.
// * in the user settings or the default settings
// Because we don't want to add profiles which are already
// in the settings.json (explicitly or implicitly).
if (profile.Hidden() || isInJsonObj(profile, _userSettings) || isInJsonObj(profile, _defaultSettings))
{
continue;
}

View file

@ -43,6 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
DEFINE_ENUM_MAP(Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode);
DEFINE_ENUM_MAP(Microsoft::Terminal::Control::TextAntialiasingMode, TextAntialiasingMode);
DEFINE_ENUM_MAP(Microsoft::Terminal::Core::CursorStyle, CursorStyle);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::IntenseStyle, IntenseTextStyle);
// FontWeight is special because the JsonUtils::ConversionTrait for it
// creates a FontWeight object, but we need to use the uint16_t value.

View file

@ -40,6 +40,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::TextAntialiasingMode> TextAntialiasingMode();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Core::CursorStyle> CursorStyle();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint16_t> FontWeight();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle> IntenseTextStyle();
};
}

View file

@ -22,5 +22,6 @@ namespace Microsoft.Terminal.Settings.Model
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.TextAntialiasingMode> TextAntialiasingMode { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Core.CursorStyle> CursorStyle { get; };
static Windows.Foundation.Collections.IMap<String, UInt16> FontWeight { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.IntenseStyle> IntenseTextStyle { get; };
}
}

View file

@ -123,7 +123,27 @@ namespace Microsoft::Terminal::Settings::Model
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view content)
{
auto tmpPath = path;
// GH#10787: rename() will replace symbolic links themselves and not the path they point at.
// It's thus important that we first resolve them before generating temporary path.
std::error_code ec;
const auto resolvedPath = std::filesystem::is_symlink(path) ? std::filesystem::canonical(path, ec) : path;
if (ec)
{
if (ec.value() != ERROR_FILE_NOT_FOUND)
{
THROW_WIN32_MSG(ec.value(), "failed to compute canonical path");
}
// The original file is a symbolic link, but the target doesn't exist.
// Consider two fall-backs:
// * resolve the link manually, which might be less accurate and more prone to race conditions
// * write to the file directly, which lets the system resolve the symbolic link but leaves the write non-atomic
// The latter is chosen, as this is an edge case and our 'atomic' writes are only best-effort.
WriteUTF8File(path, content);
return;
}
auto tmpPath = resolvedPath;
tmpPath += L".tmp";
// Writing to a file isn't atomic, but...
@ -132,6 +152,6 @@ namespace Microsoft::Terminal::Settings::Model
// renaming one is (supposed to be) atomic.
// Wait... "supposed to be"!? Well it's technically not always atomic,
// but it's pretty darn close to it, so... better than nothing.
std::filesystem::rename(tmpPath, path);
std::filesystem::rename(tmpPath, resolvedPath);
}
}

View file

@ -48,6 +48,8 @@ static constexpr std::string_view StartupActionsKey{ "startupActions" };
static constexpr std::string_view FocusFollowMouseKey{ "focusFollowMouse" };
static constexpr std::string_view WindowingBehaviorKey{ "windowingBehavior" };
static constexpr std::string_view TrimBlockSelectionKey{ "trimBlockSelection" };
static constexpr std::string_view AlwaysShowTrayIconKey{ "alwaysShowTrayIcon" };
static constexpr std::string_view MinimizeToTrayKey{ "minimizeToTray" };
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
@ -129,6 +131,8 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
globals->_WindowingBehavior = _WindowingBehavior;
globals->_TrimBlockSelection = _TrimBlockSelection;
globals->_DetectURLs = _DetectURLs;
globals->_MinimizeToTray = _MinimizeToTray;
globals->_AlwaysShowTrayIcon = _AlwaysShowTrayIcon;
globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile;
globals->_validDefaultProfile = _validDefaultProfile;
@ -319,6 +323,10 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
JsonUtils::GetValueForKey(json, DetectURLsKey, _DetectURLs);
JsonUtils::GetValueForKey(json, MinimizeToTrayKey, _MinimizeToTray);
JsonUtils::GetValueForKey(json, AlwaysShowTrayIconKey, _AlwaysShowTrayIcon);
// This is a helper lambda to get the keybindings and commands out of both
// and array of objects. We'll use this twice, once on the legacy
// `keybindings` key, and again on the newer `bindings` key.
@ -414,6 +422,8 @@ Json::Value GlobalAppSettings::ToJson() const
JsonUtils::SetValueForKey(json, WindowingBehaviorKey, _WindowingBehavior);
JsonUtils::SetValueForKey(json, TrimBlockSelectionKey, _TrimBlockSelection);
JsonUtils::SetValueForKey(json, DetectURLsKey, _DetectURLs);
JsonUtils::SetValueForKey(json, MinimizeToTrayKey, _MinimizeToTray);
JsonUtils::SetValueForKey(json, AlwaysShowTrayIconKey, _AlwaysShowTrayIcon);
// clang-format on
json[JsonKey(ActionsKey)] = _actionMap->ToJson();

Some files were not shown because too many files have changed in this diff Show more