diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 3be64694a0..cc86eb8723 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -193,7 +193,6 @@ chh chk CHT Cic -CLA Clcompile CLE cleartype @@ -480,7 +479,6 @@ defterm DELAYLOAD DELETEONRELEASE Delt -demoable depersist deprioritized deserializers @@ -537,7 +535,6 @@ DSSCL DSwap DTest DTTERM -DUMMYUNIONNAME dup'ed dvi dwl @@ -590,7 +587,6 @@ ETW EUDC EVENTID eventing -everytime evflags evt execd @@ -802,7 +798,6 @@ HIBYTE hicon HIDEWINDOW hinst -Hirots HISTORYBUFS HISTORYNODUP HISTORYSIZE @@ -903,7 +898,6 @@ INSERTMODE INTERACTIVITYBASE INTERCEPTCOPYPASTE INTERNALNAME -inthread intsafe INVALIDARG INVALIDATERECT @@ -1260,14 +1254,12 @@ ntm nto ntrtl ntstatus -ntsubauth NTSYSCALLAPI nttree nturtl ntuser NTVDM ntverp -NTWIN nugetversions nullability nullness @@ -1306,8 +1298,6 @@ opencode opencon openconsole openconsoleproxy -OPENIF -OPENLINK openps openvt ORIGINALFILENAME @@ -1360,9 +1350,7 @@ pcg pch PCIDLIST PCIS -PCLIENT PCLONG -PCOBJECT pcon PCONSOLE PCONSOLEENDTASK @@ -1374,7 +1362,6 @@ pcshell PCSHORT PCSR PCSTR -PCUNICODE PCWCH PCWCHAR PCWSTR @@ -1423,7 +1410,6 @@ PLOGICAL pnm PNMLINK pntm -PNTSTATUS POBJECT Podcast POINTSLIST @@ -1442,7 +1428,6 @@ ppf ppguid ppidl PPROC -PPROCESS ppropvar ppsi ppsl @@ -1506,7 +1491,6 @@ ptrs ptsz PTYIn PUCHAR -PUNICODE pwch PWDDMCONSOLECONTEXT pws @@ -1568,7 +1552,6 @@ REGISTEROS REGISTERVDM regkey REGSTR -reingest RELBINPATH remoting renamer @@ -1863,6 +1846,7 @@ TDP TEAMPROJECT tearoff Teb +Techo tellp teraflop terminalcore @@ -2005,7 +1989,6 @@ unittesting unittests unk unknwn -unmark UNORM unparseable unregistering diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml new file mode 100644 index 0000000000..9bf6910caf --- /dev/null +++ b/.github/workflows/winget.yml @@ -0,0 +1,27 @@ +name: Publish to Winget + +on: + release: + types: [published] + +env: + REGEX: 'Microsoft\.WindowsTerminal(?:Preview)?_Win10_([\d.]+)_8wekyb3d8bbwe\.msixbundle$' + +jobs: + publish: + runs-on: windows-latest # Action can only run on Windows + steps: + - name: Extract version from release asset + id: extract-version + run: | + $version = '${{ toJSON(github.event.release.assets.*.name) }}' | ConvertFrom-Json | Select-String -Pattern $env:REGEX | ForEach-Object { $_.Matches.Groups[1].Value } + Write-Output "version=$version" >> $env:GITHUB_OUTPUT + + - name: Publish ${{ github.event.release.prerelease && 'Preview' || 'Stable' }} + uses: vedantmgoyal2009/winget-releaser@v2 + with: + identifier: Microsoft.WindowsTerminal${{ github.event.release.prerelease && '.Preview' || '' }} + version: ${{ steps.extract-version.outputs.version }} + installers-regex: ${{ env.REGEX }} + token: ${{ secrets.WINGET_TOKEN }} + fork-user: DHowett diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 6230a50032..f33881aadd 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -133,7 +133,12 @@ namespace winrt::TerminalApp::implementation // cause you to chase down the rabbit hole of "why is App not // registered?" when it definitely is. - _isElevated = ::Microsoft::Console::Utils::IsElevated(); + // The TerminalPage has to be constructed during our construction, to + // make sure that there's a terminal page for callers of + // SetTitleBarContent + + _isElevated = ::Microsoft::Console::Utils::IsRunningElevated(); + _canDragDrop = ::Microsoft::Console::Utils::CanUwpDragDrop(); _reloadSettings = std::make_shared>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() { if (auto self{ weakSelf.get() }) @@ -164,10 +169,14 @@ namespace winrt::TerminalApp::implementation // - - reports internal state // Return Value: // - True if elevated, false otherwise. - bool AppLogic::IsElevated() const noexcept + bool AppLogic::IsRunningElevated() const noexcept { return _isElevated; } + bool AppLogic::CanDragDrop() const noexcept + { + return _canDragDrop; + } // Method Description: // - Called by UWP context invoker to let us know that we may have to change some of our behaviors diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 22fa47b98c..578736f505 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -49,7 +49,8 @@ namespace winrt::TerminalApp::implementation void Create(); bool IsUwp() const noexcept; void RunAsUwp(); - bool IsElevated() const noexcept; + bool IsRunningElevated() const noexcept; + bool CanDragDrop() const noexcept; void ReloadSettings(); bool HasSettingsStartupActions() const noexcept; @@ -77,6 +78,7 @@ namespace winrt::TerminalApp::implementation private: bool _isUwp{ false }; bool _isElevated{ false }; + bool _canDragDrop{ false }; Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index ef29b6ac2a..510fac2e24 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -31,7 +31,8 @@ namespace TerminalApp Boolean IsUwp(); void RunAsUwp(); - Boolean IsElevated(); + Boolean IsRunningElevated(); + Boolean CanDragDrop(); Boolean HasSettingsStartupActions(); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 4d7571363b..25b89261c5 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -128,27 +128,26 @@ namespace winrt::TerminalApp::implementation _systemRowsToScroll = _ReadSystemRowsToScroll(); } - bool TerminalPage::IsElevated() const noexcept + bool TerminalPage::IsRunningElevated() const noexcept { - // use C++11 magic statics to make sure we only do this once. - // This won't change over the lifetime of the application - - static const auto isElevated = []() { - // *** THIS IS A SINGLETON *** - auto result = false; - - // GH#2455 - Make sure to try/catch calls to Application::Current, - // because that _won't_ be an instance of TerminalApp::App in the - // LocalTests - try - { - result = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated(); - } - CATCH_LOG(); - return result; - }(); - - return isElevated; + // GH#2455 - Make sure to try/catch calls to Application::Current, + // because that _won't_ be an instance of TerminalApp::App in the + // LocalTests + try + { + return Application::Current().as().Logic().IsRunningElevated(); + } + CATCH_LOG(); + return false; + } + bool TerminalPage::CanDragDrop() const noexcept + { + try + { + return Application::Current().as().Logic().CanDragDrop(); + } + CATCH_LOG(); + return true; } void TerminalPage::Create() @@ -161,11 +160,11 @@ namespace winrt::TerminalApp::implementation _tabView = _tabRow.TabView(); _rearranging = false; - const auto isElevated = IsElevated(); + const auto canDragDrop = CanDragDrop(); _tabRow.PointerMoved({ get_weak(), &TerminalPage::_RestorePointerCursorHandler }); - _tabView.CanReorderTabs(!isElevated); - _tabView.CanDragTabs(!isElevated); + _tabView.CanReorderTabs(canDragDrop); + _tabView.CanDragTabs(canDragDrop); _tabView.TabDragStarting({ get_weak(), &TerminalPage::_TabDragStarted }); _tabView.TabDragCompleted({ get_weak(), &TerminalPage::_TabDragCompleted }); @@ -284,7 +283,7 @@ namespace winrt::TerminalApp::implementation // Setup mouse vanish attributes SystemParametersInfoW(SPI_GETMOUSEVANISH, 0, &_shouldMouseVanish, false); - _tabRow.ShowElevationShield(IsElevated() && _settings.GlobalSettings().ShowAdminShield()); + _tabRow.ShowElevationShield(IsRunningElevated() && _settings.GlobalSettings().ShowAdminShield()); // Store cursor, so we can restore it, e.g., after mouse vanishing // (we'll need to adapt this logic once we make cursor context aware) @@ -311,7 +310,7 @@ namespace winrt::TerminalApp::implementation // GH#12267: Don't forget about defterm handoff here. If we're being // created for embedding, then _yea_, we don't need to handoff to an // elevated window. - if (!_startupActions || IsElevated() || _shouldStartInboundListener || _startupActions.Size() == 0) + if (!_startupActions || IsRunningElevated() || _shouldStartInboundListener || _startupActions.Size() == 0) { // there aren't startup actions, or we're elevated. In that case, go for it. return false; @@ -1103,7 +1102,7 @@ namespace winrt::TerminalApp::implementation WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) && WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down); - const auto dispatchToElevatedWindow = ctrlPressed && !IsElevated(); + const auto dispatchToElevatedWindow = ctrlPressed && !IsRunningElevated(); if ((shiftPressed || dispatchToElevatedWindow) && !debugTap) { @@ -2869,7 +2868,7 @@ namespace winrt::TerminalApp::implementation // want to create an animation. WUX::Media::Animation::Timeline::AllowDependentAnimations(!_settings.GlobalSettings().DisableAnimations()); - _tabRow.ShowElevationShield(IsElevated() && _settings.GlobalSettings().ShowAdminShield()); + _tabRow.ShowElevationShield(IsRunningElevated() && _settings.GlobalSettings().ShowAdminShield()); Media::SolidColorBrush transparent{ Windows::UI::Colors::Transparent() }; _tabView.Background(transparent); @@ -3956,7 +3955,7 @@ namespace winrt::TerminalApp::implementation { // Try to handle auto-elevation const auto requestedElevation = controlSettings.DefaultSettings().Elevate(); - const auto currentlyElevated = IsElevated(); + const auto currentlyElevated = IsRunningElevated(); // We aren't elevated, but we want to be. if (requestedElevation && !currentlyElevated) diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 34382de8ae..1b261f71ec 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -126,7 +126,8 @@ namespace winrt::TerminalApp::implementation TerminalApp::WindowProperties WindowProperties() const noexcept { return _WindowProperties; }; - bool IsElevated() const noexcept; + bool CanDragDrop() const noexcept; + bool IsRunningElevated() const noexcept; void OpenSettingsUI(); void WindowActivated(const bool activated); diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 7ab1837b19..fdee09a38c 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -202,35 +202,6 @@ namespace winrt::TerminalApp::implementation return isUwp; } - // Method Description: - // - Called around the codebase to discover if Terminal is running elevated - // Arguments: - // - - reports internal state - // Return Value: - // - True if elevated, false otherwise. - bool TerminalWindow::IsElevated() const noexcept - { - // use C++11 magic statics to make sure we only do this once. - // This won't change over the lifetime of the application - - static const auto isElevated = []() { - // *** THIS IS A SINGLETON *** - auto result = false; - - // GH#2455 - Make sure to try/catch calls to Application::Current, - // because that _won't_ be an instance of TerminalApp::App in the - // LocalTests - try - { - result = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated(); - } - CATCH_LOG(); - return result; - }(); - - return isElevated; - } - // Method Description: // - Build the UI for the terminal app. Before this method is called, it // should not be assumed that the TerminalApp is usable. The Settings diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 757a966c4b..61c51844fb 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -66,7 +66,6 @@ namespace winrt::TerminalApp::implementation void Create(); bool IsUwp() const noexcept; - bool IsElevated() const noexcept; void Quit(); diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index a945f88e00..0dc6550e7b 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -51,8 +51,6 @@ namespace TerminalApp // registered?" when it definitely is. void Create(); - Boolean IsElevated(); - Boolean HasCommandlineArguments(); Int32 SetStartupCommandline(String[] commands); diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index 59ebb0ccd0..6f684f91d6 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -177,7 +177,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // from state.json. We'll then load the Local props from // `elevated-state.json` // - If we're unelevated, then load _everything_ from state.json. - if (::Microsoft::Console::Utils::IsElevated()) + if (::Microsoft::Console::Utils::IsRunningElevated()) { // Only load shared properties if we're elevated FromJson(root, FileSource::Shared); @@ -225,7 +225,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // // After that's done, we'll write our Local properties into // elevated-state.json. - if (::Microsoft::Console::Utils::IsElevated()) + if (::Microsoft::Console::Utils::IsRunningElevated()) { std::string errs; std::unique_ptr reader{ Json::CharReaderBuilder{}.newCharReader() }; @@ -353,7 +353,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // permissions, so we don't potentially read malicious data. std::optional ApplicationState::_readLocalContents() const { - return ::Microsoft::Console::Utils::IsElevated() ? + return ::Microsoft::Console::Utils::IsRunningElevated() ? ReadUTF8FileIfExists(_elevatedPath, true) : ReadUTF8FileIfExists(_sharedPath, false); } @@ -374,7 +374,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // will atomically write to `user-state.json` void ApplicationState::_writeLocalContents(const std::string_view content) const { - if (::Microsoft::Console::Utils::IsElevated()) + if (::Microsoft::Console::Utils::IsRunningElevated()) { // DON'T use WriteUTF8FileAtomic, which will write to a temporary file // then rename that file to the final filename. That actually lets us diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index e53290b05f..5ffaab982a 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -50,6 +50,7 @@ private: std::vector _hotkeys; std::unique_ptr _notificationIcon; + bool _quitting{ false }; void _windowStartedHandler(const std::shared_ptr& sender); diff --git a/src/common.build.post.props b/src/common.build.post.props index d243c3361c..1326e062bc 100644 --- a/src/common.build.post.props +++ b/src/common.build.post.props @@ -25,7 +25,7 @@ %(AdditionalOptions) /defaultlib:ucrtd.lib - + MultiThreaded diff --git a/src/common.build.pre.props b/src/common.build.pre.props index a670249bf4..5d4c2a6f81 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -81,12 +81,26 @@ + + + false + true + + v143 Unicode false x64 + true + false diff --git a/src/host/inputReadHandleData.cpp b/src/host/inputReadHandleData.cpp index e8ab613cb1..8628d13113 100644 --- a/src/host/inputReadHandleData.cpp +++ b/src/host/inputReadHandleData.cpp @@ -20,7 +20,7 @@ bool INPUT_READ_HANDLE_DATA::IsInputPending() const bool INPUT_READ_HANDLE_DATA::IsMultilineInput() const { - FAIL_FAST_IF(!_isInputPending); // we shouldn't have multiline input without a pending input. + assert(_isInputPending); // we shouldn't have multiline input without a pending input. return _isMultilineInput; } diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 15f6e49d31..6ea80796f7 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -1019,27 +1019,43 @@ void COOKED_READ_DATA::SavePendingInput(const size_t index, const bool multiline } Tracing::s_TraceCookedRead(_clientProcess, _backupLimit, base::saturated_cast(idx)); - ProcessAliases(LineCount); + // Don't be fooled by ProcessAliases only taking one argument. It rewrites multiple + // class members on return, including `_bytesRead`, requiring us to reconstruct `input`. + ProcessAliases(LineCount); + input = { _backupLimit, _bytesRead / sizeof(wchar_t) }; + + // The exact reasons for this are unclear to me (the one writing this comment), but this code used to + // split the contents of a multiline alias (for instance `doskey test=echo foo$Techo bar$Techo baz`) + // into multiple separate read outputs, ensuring that the client receives them line by line. + // + // This code first truncates the `input` to only contain the first line, so that Consume() below only + // writes that line into the user buffer. We'll later store the remainder in SaveMultilinePendingInput(). if (LineCount > 1) { - input = input.substr(0, idx + 1); + // ProcessAliases() is supposed to end each line with \r\n. If it doesn't we might as well fail-fast. + const auto firstLineEnd = input.find(UNICODE_LINEFEED) + 1; + input = input.substr(0, std::min(input.size(), firstLineEnd)); } } } + const auto inputSizeBefore = input.size(); GetInputBuffer()->Consume(isUnicode, input, writer); - if (!input.empty()) + if (LineCount > 1) { - if (LineCount > 1) - { - GetInputReadHandleData()->SaveMultilinePendingInput(input); - } - else - { - GetInputReadHandleData()->SavePendingInput(input); - } + // This is a continuation of the above identical if condition. + // We've truncated the `input` slice and now we need to restore it. + const auto inputSizeAfter = input.size(); + const auto amountConsumed = inputSizeBefore - inputSizeAfter; + input = { _backupLimit, _bytesRead / sizeof(wchar_t) }; + input = input.substr(std::min(input.size(), amountConsumed)); + GetInputReadHandleData()->SaveMultilinePendingInput(input); + } + else if (!input.empty()) + { + GetInputReadHandleData()->SavePendingInput(input); } numBytes = _userBufferSize - writer.size(); diff --git a/src/host/stream.cpp b/src/host/stream.cpp index 92c76b89ad..5ab812bda9 100644 --- a/src/host/stream.cpp +++ b/src/host/stream.cpp @@ -288,28 +288,36 @@ try { bytesRead = 0; - auto pending = readHandleState.GetPendingInput(); + const auto pending = readHandleState.GetPendingInput(); + auto input = pending; + // This is basically the continuation of COOKED_READ_DATA::_handlePostCharInputLoop. if (readHandleState.IsMultilineInput()) { - const auto idx = pending.find(UNICODE_LINEFEED); - if (idx != decltype(pending)::npos) - { - // +1 to include the newline. - pending = pending.substr(0, idx + 1); - } + const auto firstLineEnd = input.find(UNICODE_LINEFEED) + 1; + input = input.substr(0, std::min(input.size(), firstLineEnd)); } + const auto inputSizeBefore = input.size(); std::span writer{ buffer }; - inputBuffer.Consume(unicode, pending, writer); + inputBuffer.Consume(unicode, input, writer); - if (pending.empty()) + // Since we truncated `input` to only include the first line, + // we need to restore `input` here to the entirety of the remaining input. + if (readHandleState.IsMultilineInput()) + { + const auto inputSizeAfter = input.size(); + const auto amountConsumed = inputSizeBefore - inputSizeAfter; + input = pending.substr(std::min(pending.size(), amountConsumed)); + } + + if (input.empty()) { readHandleState.CompletePending(); } else { - readHandleState.UpdatePending(pending); + readHandleState.UpdatePending(input); } bytesRead = buffer.size() - writer.size(); diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 3dbe7928c9..45e16a049d 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -96,7 +96,8 @@ namespace Microsoft::Console::Utils GUID CreateV5Uuid(const GUID& namespaceGuid, const std::span name); - bool IsElevated(); + bool CanUwpDragDrop(); + bool IsRunningElevated(); // This function is only ever used by the ConPTY connection in // TerminalConnection. However, that library does not have a good system of diff --git a/src/types/utils.cpp b/src/types/utils.cpp index dac6212c6e..94813bfc96 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -653,9 +653,18 @@ GUID Utils::CreateV5Uuid(const GUID& namespaceGuid, const std::span they _can_ drag drop } + // If they are running admin, they cannot drag drop. + return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + // This failed? That's very peculiar indeed. Let's err on the side + // of "drag drop is broken", just in case. + return true; + } + }(); + + return !isDragDropBroken; +} + +// See CanUwpDragDrop, GH#13928 for why this is different. +bool Utils::IsRunningElevated() +{ + static auto isElevated = []() { + try + { return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); } catch (...)