diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index 6e24cc54485..0ac4cf51afe 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "AppCommandlineArgs.h" #include "../types/inc/utils.hpp" +#include "TerminalSettingsModel/ModelSerializationHelpers.h" #include using namespace winrt::Microsoft::Terminal::Settings::Model; @@ -183,6 +184,15 @@ void AppCommandlineArgs::_buildParser() maximized->excludes(fullscreen); focus->excludes(fullscreen); + auto positionCallback = [this](std::string string) { + _position = LaunchPositionFromString(string); + }; + _app.add_option_function("--pos", positionCallback, RS_A(L"CmdPositionDesc")); + auto sizeCallback = [this](std::string string) { + _size = SizeFromString(string); + }; + _app.add_option_function("--size", sizeCallback, RS_A(L"CmdSizeDesc")); + _app.add_option("-w,--window", _windowTarget, RS_A(L"CmdWindowTargetArgDesc")); @@ -709,7 +719,7 @@ void AppCommandlineArgs::_resetStateToDefault() // DON'T clear _launchMode here! This will get called once for every // subcommand, so we don't want `wt -F new-tab ; split-pane` clearing out // the "global" fullscreen flag (-F). - // Same with _windowTarget. + // Same with _windowTarget, _position and _size. } // Function Description: @@ -936,6 +946,16 @@ std::optional AppComman return _launchMode; } +std::optional AppCommandlineArgs::GetPosition() const noexcept +{ + return _position; +} + +std::optional AppCommandlineArgs::GetSize() const noexcept +{ + return _size; +} + // Method Description: // - Attempts to parse an array of commandline args into a list of // commands to execute, and then parses these commands. As commands are diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.h b/src/cascadia/TerminalApp/AppCommandlineArgs.h index de076ec99c1..31eac78168b 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.h +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h @@ -41,6 +41,8 @@ class TerminalApp::AppCommandlineArgs final std::optional GetPersistedLayoutIdx() const noexcept; std::optional GetLaunchMode() const noexcept; + std::optional GetPosition() const noexcept; + std::optional GetSize() const noexcept; int ParseArgs(const winrt::Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args); void DisableHelpInExitMessage(); @@ -119,6 +121,8 @@ class TerminalApp::AppCommandlineArgs final const Commandline* _currentCommandline{ nullptr }; std::optional _launchMode{ std::nullopt }; + std::optional _position{ std::nullopt }; + std::optional _size{ std::nullopt }; bool _isHandoffListener{ false }; std::vector _startupActions; std::string _exitMessage; diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index c17b7dff7f1..a4279620833 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -633,12 +633,17 @@ namespace winrt::TerminalApp::implementation } } - if (proposedSize.Width == 0 && proposedSize.Height == 0) + if (_appArgs.GetSize().has_value() || (proposedSize.Width == 0 && proposedSize.Height == 0)) { // Use the default profile to determine how big of a window we need. const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, nullptr, nullptr) }; - proposedSize = TermControl::GetProposedDimensions(settings.DefaultSettings(), dpi); + const til::size emptySize{}; + const auto commandlineSize = _appArgs.GetSize().value_or(emptySize); + proposedSize = TermControl::GetProposedDimensions(settings.DefaultSettings(), + dpi, + commandlineSize.width, + commandlineSize.height); } // GH#2061 - If the global setting "Always show tab bar" is @@ -738,6 +743,12 @@ namespace winrt::TerminalApp::implementation } } + // Commandline args trump everything else + if (_appArgs.GetPosition().has_value()) + { + initialPosition = _appArgs.GetPosition().value(); + } + return { initialPosition.X ? initialPosition.X.Value() : defaultInitialX, initialPosition.Y ? initialPosition.Y.Value() : defaultInitialY diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 195535985d9..5f6d01f167c 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -394,6 +394,12 @@ Specify a terminal window to run the given commandline in. "0" always refers to the current window. + + Specify the position for the terminal, in "x,y" format. + + + Specify the number of columns and rows for the terminal, in "c,r" format. + Press the button to open a new terminal tab with your default profile. Open the flyout to select which profile you want to open. diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index a965f35ff8c..00b0b46ecef 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -2032,15 +2032,26 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - dpi: The DPI we should create the terminal at. This affects things such // as font size, scrollbar and other control scaling, etc. Make sure the // caller knows what monitor the control is about to appear on. + // - commandlineCols: Number of cols specified on the commandline + // - commandlineRows: Number of rows specified on the commandline // Return Value: // - a size containing the requested dimensions in pixels. - winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(const IControlSettings& settings, const uint32_t dpi) + winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(const IControlSettings& settings, + const uint32_t dpi, + int32_t commandlineCols, + int32_t commandlineRows) { // If the settings have negative or zero row or column counts, ignore those counts. // (The lower TerminalCore layer also has upper bounds as well, but at this layer // we may eventually impose different ones depending on how many pixels we can address.) - const auto cols = ::base::saturated_cast(std::max(settings.InitialCols(), 1)); - const auto rows = ::base::saturated_cast(std::max(settings.InitialRows(), 1)); + const auto cols = ::base::saturated_cast(std::max(commandlineCols > 0 ? + commandlineCols : + settings.InitialCols(), + 1)); + const auto rows = ::base::saturated_cast(std::max(commandlineRows > 0 ? + commandlineRows : + settings.InitialRows(), + 1)); const winrt::Windows::Foundation::Size initialSize{ cols, rows }; diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index afa134dbef3..bed685ede24 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -114,7 +114,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer(); const Windows::UI::Xaml::Thickness GetPadding(); - static Windows::Foundation::Size GetProposedDimensions(const IControlSettings& settings, const uint32_t dpi); + static Windows::Foundation::Size GetProposedDimensions(const IControlSettings& settings, + const uint32_t dpi, + int32_t commandlineCols, + int32_t commandlineRows); static Windows::Foundation::Size GetProposedDimensions(const IControlSettings& settings, const uint32_t dpi, const winrt::Windows::Foundation::Size& initialSizeInChars); void BellLightOn(); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 29080e6b8de..a287989d19f 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -21,7 +21,10 @@ namespace Microsoft.Terminal.Control IControlAppearance unfocusedAppearance, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection); - static Windows.Foundation.Size GetProposedDimensions(IControlSettings settings, UInt32 dpi); + static Windows.Foundation.Size GetProposedDimensions(IControlSettings settings, + UInt32 dpi, + Int32 commandlineCols, + Int32 commandlineRows); void UpdateControlSettings(IControlSettings settings); void UpdateControlSettings(IControlSettings settings, IControlAppearance unfocusedAppearance); diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 04f0f38d7b8..a1aaf301ce8 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -92,6 +92,7 @@ + diff --git a/src/cascadia/TerminalSettingsModel/ModelSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/ModelSerializationHelpers.h new file mode 100644 index 00000000000..d8c9201f662 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ModelSerializationHelpers.h @@ -0,0 +1,82 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- ModelSerializationHelpers.h + +Abstract: +- Helper methods for serializing/de-serializing model data. + +--*/ + +#pragma once + +// Function Description: +// - Helper for converting a pair of comma separated, potentially absent integer values +// into the corresponding left and right values. The leftValue and rightValue functions +// will be called back with the associated parsed integer value, assuming it's present. +// (100, 100): leftValue and rightValue functions both called back with 100. +// (100, ), (100, abc): leftValue function called back with 100 +// (, 100), (abc, 100): rightValue function called back with 100 +// (,): no function called back +// (100, 100, 100): we only read the first two values, this is equivalent to (100, 100) +// Arguments: +// - string: The string to parse +// - leftValue: Function called back with the value before the comma +// - rightValue: Function called back with the value after the comma +_TIL_INLINEPREFIX void ParseCommaSeparatedPair(const std::string& string, + std::function leftValue, + std::function rightValue) +{ + static constexpr auto singleCharDelim = ','; + std::stringstream tokenStream(string); + std::string token; + uint8_t index = 0; + + // Get values till we run out of delimiter separated values in the stream + // or we hit max number of allowable values (= 2) + // Non-numeral values or empty string will be caught as exception and we do not assign them + for (; std::getline(tokenStream, token, singleCharDelim) && (index < 2); index++) + { + try + { + int32_t value = std::stol(token); + if (index == 0) + { + leftValue(value); + } + + if (index == 1) + { + rightValue(value); + } + } + catch (...) + { + // Do nothing + } + } +} + +// See: ParseCommaSeparatedPair +_TIL_INLINEPREFIX ::winrt::Microsoft::Terminal::Settings::Model::LaunchPosition LaunchPositionFromString(const std::string& string) +{ + ::winrt::Microsoft::Terminal::Settings::Model::LaunchPosition initialPosition; + ParseCommaSeparatedPair( + string, + [&initialPosition](int32_t left) { initialPosition.X = left; }, + [&initialPosition](int32_t right) { initialPosition.Y = right; }); + return initialPosition; +} + +// See: ParseCommaSeparatedPair +_TIL_INLINEPREFIX ::til::size SizeFromString(const std::string& string) +{ + til::size size{}; + ParseCommaSeparatedPair( + string, + [&size](int32_t left) { size.width = left; }, + [&size](int32_t right) { size.height = right; }); + return size; +} diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index a26c0b7e73d..47c6f8ebcef 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -17,6 +17,7 @@ Module Name: #include "JsonUtils.h" #include "SettingsTypes.h" +#include "ModelSerializationHelpers.h" JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Core::CursorStyle) { @@ -317,51 +318,12 @@ JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Control::CopyFormat) } }; -// Type Description: -// - Helper for converting the initial position string into -// 2 coordinate values. We allow users to only provide one coordinate, -// thus, we use comma as the separator: -// (100, 100): standard input string -// (, 100), (100, ): if a value is missing, we set this value as a default -// (,): both x and y are set to default -// (abc, 100): if a value is not valid, we treat it as default -// (100, 100, 100): we only read the first two values, this is equivalent to (100, 100) template<> struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<::winrt::Microsoft::Terminal::Settings::Model::LaunchPosition> { ::winrt::Microsoft::Terminal::Settings::Model::LaunchPosition FromJson(const Json::Value& json) { - ::winrt::Microsoft::Terminal::Settings::Model::LaunchPosition ret; - auto initialPosition{ json.asString() }; - static constexpr auto singleCharDelim = ','; - std::stringstream tokenStream(initialPosition); - std::string token; - uint8_t initialPosIndex = 0; - - // Get initial position values till we run out of delimiter separated values in the stream - // or we hit max number of allowable values (= 2) - // Non-numeral values or empty string will be caught as exception and we do not assign them - for (; std::getline(tokenStream, token, singleCharDelim) && (initialPosIndex < 2); initialPosIndex++) - { - try - { - int64_t position = std::stol(token); - if (initialPosIndex == 0) - { - ret.X = position; - } - - if (initialPosIndex == 1) - { - ret.Y = position; - } - } - catch (...) - { - // Do nothing - } - } - return ret; + return LaunchPositionFromString(json.asString()); } bool CanConvert(const Json::Value& json)