From 6f1eb087fb9ed91f39628a1b1292cf4087b9258d Mon Sep 17 00:00:00 2001 From: David Bennett Date: Mon, 6 Jul 2026 16:36:15 -0700 Subject: [PATCH 1/2] Update all non-help output to use Reporter --- src/windows/wslc/commands/RegistryCommand.cpp | 16 ++--- src/windows/wslc/commands/RootCommand.cpp | 2 +- src/windows/wslc/commands/SettingsCommand.cpp | 2 +- src/windows/wslc/commands/VersionCommand.cpp | 8 +-- src/windows/wslc/commands/VersionCommand.h | 3 +- .../wslc/services/BuildImageCallback.cpp | 15 +++-- .../wslc/services/BuildImageCallback.h | 12 ++-- src/windows/wslc/services/ConsoleService.cpp | 5 +- src/windows/wslc/services/ConsoleService.h | 6 +- .../wslc/services/ContainerService.cpp | 31 +++++----- src/windows/wslc/services/ContainerService.h | 11 ++-- .../wslc/services/ImageProgressCallback.cpp | 60 ++++++++++--------- .../wslc/services/ImageProgressCallback.h | 14 +++-- src/windows/wslc/services/SessionService.cpp | 22 +++---- src/windows/wslc/services/SessionService.h | 9 +-- src/windows/wslc/tasks/ContainerTasks.cpp | 40 ++++++------- src/windows/wslc/tasks/ImageTasks.cpp | 26 ++++---- src/windows/wslc/tasks/InspectTasks.cpp | 4 +- src/windows/wslc/tasks/NetworkTasks.cpp | 24 ++++---- src/windows/wslc/tasks/RegistryTasks.cpp | 4 +- src/windows/wslc/tasks/SessionTasks.cpp | 10 ++-- src/windows/wslc/tasks/VolumeTasks.cpp | 28 ++++----- 22 files changed, 183 insertions(+), 169 deletions(-) diff --git a/src/windows/wslc/commands/RegistryCommand.cpp b/src/windows/wslc/commands/RegistryCommand.cpp index 8514a5048c..0c63becf7d 100644 --- a/src/windows/wslc/commands/RegistryCommand.cpp +++ b/src/windows/wslc/commands/RegistryCommand.cpp @@ -25,7 +25,7 @@ using namespace wsl::shared; namespace { -auto MaskInput() +auto MaskInput(wsl::windows::wslc::Reporter& reporter) { HANDLE input = GetStdHandle(STD_INPUT_HANDLE); DWORD mode = 0; @@ -33,21 +33,21 @@ auto MaskInput() if ((input != INVALID_HANDLE_VALUE) && GetConsoleMode(input, &mode)) { THROW_IF_WIN32_BOOL_FALSE(SetConsoleMode(input, mode & ~ENABLE_ECHO_INPUT)); - return wil::scope_exit(std::function([input, mode] { + return wil::scope_exit(std::function([input, mode, &reporter] { SetConsoleMode(input, mode); - std::wcerr << L'\n'; + reporter.Info(L"\n"); })); } return wil::scope_exit(std::function([] {})); } -std::wstring Prompt(const std::wstring& label, bool maskInput) +std::wstring Prompt(wsl::windows::wslc::Reporter& reporter, const std::wstring& label, bool maskInput) { // Write without a trailing newline so the cursor stays inline (matching Docker's behavior). - std::wcerr << label; + reporter.Info(L"{}", label); - auto restoreConsole = maskInput ? MaskInput() : wil::scope_exit(std::function([] {})); + auto restoreConsole = maskInput ? MaskInput(reporter) : wil::scope_exit(std::function([] {})); std::wstring value; std::getline(std::wcin, value); @@ -127,7 +127,7 @@ void RegistryLoginCommand::ExecuteInternal(CLIExecutionContext& context) const // Prompt for username if not provided. if (!context.Args.Contains(ArgType::Username)) { - context.Args.Add(ArgType::Username, Prompt(Localization::WSLCCLI_LoginUsernamePrompt(), false)); + context.Args.Add(ArgType::Username, Prompt(context.Reporter, Localization::WSLCCLI_LoginUsernamePrompt(), false)); } // Resolve password: --password, --password-stdin, or interactive prompt. @@ -146,7 +146,7 @@ void RegistryLoginCommand::ExecuteInternal(CLIExecutionContext& context) const } else { - context.Args.Add(ArgType::Password, Prompt(Localization::WSLCCLI_LoginPasswordPrompt(), true)); + context.Args.Add(ArgType::Password, Prompt(context.Reporter, Localization::WSLCCLI_LoginPasswordPrompt(), true)); } } diff --git a/src/windows/wslc/commands/RootCommand.cpp b/src/windows/wslc/commands/RootCommand.cpp index a972e996c9..49db22ea48 100644 --- a/src/windows/wslc/commands/RootCommand.cpp +++ b/src/windows/wslc/commands/RootCommand.cpp @@ -105,7 +105,7 @@ void RootCommand::ExecuteInternal(CLIExecutionContext& context) const { if (context.Args.Contains(ArgType::Version)) { - VersionCommand::PrintVersion(); + VersionCommand::PrintVersion(context.Reporter); return; } diff --git a/src/windows/wslc/commands/SettingsCommand.cpp b/src/windows/wslc/commands/SettingsCommand.cpp index d66a7b9993..eb399fa3a5 100644 --- a/src/windows/wslc/commands/SettingsCommand.cpp +++ b/src/windows/wslc/commands/SettingsCommand.cpp @@ -83,7 +83,7 @@ std::wstring SettingsResetCommand::LongDescription() const void SettingsResetCommand::ExecuteInternal(CLIExecutionContext& context) const { settings::User().Reset(); - PrintMessage(Localization::WSLCCLI_SettingsResetConfirm()); + context.Reporter.Output(L"{}\n", Localization::WSLCCLI_SettingsResetConfirm()); } } // namespace wsl::windows::wslc diff --git a/src/windows/wslc/commands/VersionCommand.cpp b/src/windows/wslc/commands/VersionCommand.cpp index 63f99c106e..ce9fc2e2cc 100644 --- a/src/windows/wslc/commands/VersionCommand.cpp +++ b/src/windows/wslc/commands/VersionCommand.cpp @@ -12,6 +12,7 @@ Module Name: --*/ #include "VersionCommand.h" +#include "CLIExecutionContext.h" using namespace wsl::shared; using namespace wsl::windows::wslc::execution; @@ -27,14 +28,13 @@ std::wstring VersionCommand::LongDescription() const return Localization::WSLCCLI_VersionLongDesc(); } -void VersionCommand::PrintVersion() +void VersionCommand::PrintVersion(Reporter& reporter) { - wsl::windows::common::wslutil::PrintMessage(std::format(L"{} {}", s_ExecutableName, WSL_PACKAGE_VERSION)); + reporter.Output(L"{} {}\n", s_ExecutableName, WSL_PACKAGE_VERSION); } void VersionCommand::ExecuteInternal(CLIExecutionContext& context) const { - UNREFERENCED_PARAMETER(context); - PrintVersion(); + PrintVersion(context.Reporter); } } // namespace wsl::windows::wslc diff --git a/src/windows/wslc/commands/VersionCommand.h b/src/windows/wslc/commands/VersionCommand.h index be700f5501..6e74bdc563 100644 --- a/src/windows/wslc/commands/VersionCommand.h +++ b/src/windows/wslc/commands/VersionCommand.h @@ -15,13 +15,14 @@ Module Name: #include "Command.h" namespace wsl::windows::wslc { +struct Reporter; struct VersionCommand final : public Command { constexpr static std::wstring_view CommandName = L"version"; VersionCommand(const std::wstring& parent) : Command(CommandName, parent) { } - static void PrintVersion(); + static void PrintVersion(Reporter& reporter); std::wstring ShortDescription() const override; std::wstring LongDescription() const override; diff --git a/src/windows/wslc/services/BuildImageCallback.cpp b/src/windows/wslc/services/BuildImageCallback.cpp index 0e3b9181cc..39eaad855f 100644 --- a/src/windows/wslc/services/BuildImageCallback.cpp +++ b/src/windows/wslc/services/BuildImageCallback.cpp @@ -46,8 +46,9 @@ CATCH_LOG() void BuildImageCallback::WriteTerminal(std::wstring_view content) const { - DWORD written; - LOG_IF_WIN32_BOOL_FALSE(WriteConsoleW(m_console, content.data(), static_cast(content.size()), &written, nullptr)); + // Route the scrolling build display through the Reporter's Info channel (stderr) so it + // respects the global output state. Each call is one atomic write, as before. + m_reporter.Write(Reporter::Level::Info, L"{}", content); } bool BuildImageCallback::IsCancelled() const @@ -103,7 +104,7 @@ try // Skip pull progress updates when output is redirected, show only major steps if (!isPullProgress) { - wprintf(L"%hs", status); + m_reporter.Write(Reporter::Level::Info, L"{}", MultiByteToWide(status)); } return S_OK; } @@ -184,9 +185,7 @@ CATCH_RETURN(); void BuildImageCallback::Redraw() { - CONSOLE_SCREEN_BUFFER_INFO info{}; - THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfo(m_console, &info)); - const int consoleWidth = std::max(0, static_cast(info.srWindow.Right) - info.srWindow.Left); + const auto consoleWidth = m_reporter.GetConsoleWidth(Reporter::Level::Info); const bool showPending = !m_pendingLine.empty(); const int pullCount = static_cast(m_pullLines.size()); @@ -220,9 +219,9 @@ void BuildImageCallback::Redraw() auto appendLine = [&](const std::string& line) { auto wline = MultiByteToWide(line); - if (wline.size() > static_cast(consoleWidth)) + if (consoleWidth.has_value() && wline.size() > static_cast(*consoleWidth)) { - wline.resize(static_cast(consoleWidth)); + wline.resize(static_cast(*consoleWidth)); } m_frameBuffer += std::move(wline); m_frameBuffer += Erase::LineForward; diff --git a/src/windows/wslc/services/BuildImageCallback.h b/src/windows/wslc/services/BuildImageCallback.h index 2067e2525d..bb77024732 100644 --- a/src/windows/wslc/services/BuildImageCallback.h +++ b/src/windows/wslc/services/BuildImageCallback.h @@ -12,6 +12,7 @@ Module Name: --*/ #pragma once +#include "Reporter.h" #include "SessionService.h" #include "VTSupport.h" #include @@ -23,7 +24,7 @@ class DECLSPEC_UUID("3EDD5DBF-CA6C-4CF7-923A-AD94B6A732E5") BuildImageCallback { public: // The cancel event handle must remain valid for the lifetime of this callback. - BuildImageCallback(HANDLE cancelEvent, bool verbose) : m_verbose(verbose), m_cancelEvent(cancelEvent) + BuildImageCallback(Reporter& reporter, HANDLE cancelEvent, bool verbose) : m_reporter(reporter), m_verbose(verbose), m_cancelEvent(cancelEvent) { } ~BuildImageCallback(); @@ -37,16 +38,15 @@ class DECLSPEC_UUID("3EDD5DBF-CA6C-4CF7-923A-AD94B6A732E5") BuildImageCallback void CollapseWindow(); void Redraw(); void RedrawIfNeeded(); - // Use WriteConsoleW directly rather than wprintf: wprintf is noticeably slower for - // the per-redraw scrolling display and produces visible flicker. + // Routes terminal writes through the Reporter's Info channel so each frame is emitted + // as a single atomic write and respects the global output state. void WriteTerminal(std::wstring_view content) const; bool IsCancelled() const; + Reporter& m_reporter; const bool m_verbose; const HANDLE m_cancelEvent; - HANDLE m_console = GetStdHandle(STD_OUTPUT_HANDLE); - bool m_isConsole = wsl::windows::common::wslutil::IsConsoleHandle(m_console); - wsl::windows::common::vt::EnableVirtualTerminal m_vtMode{m_console}; + bool m_isConsole = m_reporter.IsVTEnabled(Reporter::Level::Info); std::deque m_lines; // Each entry already contains the trailing newline so the bytes match what's replayed. // TODO: Track logs per step so the destructor can replay only the failing step's diff --git a/src/windows/wslc/services/ConsoleService.cpp b/src/windows/wslc/services/ConsoleService.cpp index 2ec1161b6d..8511fecf5c 100644 --- a/src/windows/wslc/services/ConsoleService.cpp +++ b/src/windows/wslc/services/ConsoleService.cpp @@ -173,13 +173,14 @@ void ConsoleService::RelayNonTtyProcess(wil::unique_handle&& Stdin, wil::unique_ io.Run({}); } -int ConsoleService::AttachToCurrentConsole(wsl::windows::common::ConsoleState& console, wsl::windows::common::ClientRunningWSLCProcess&& process, bool triggerRefresh) +int ConsoleService::AttachToCurrentConsole( + Reporter& reporter, wsl::windows::common::ConsoleState& console, wsl::windows::common::ClientRunningWSLCProcess&& process, bool triggerRefresh) { if (WI_IsFlagSet(process.Flags(), WSLCProcessFlagsTty)) { if (!RelayInteractiveTty(console, process, process.GetStdHandle(WSLCFDTty).get(), triggerRefresh)) { - windows::common::wslutil::PrintMessage(L"[detached]", stderr); + reporter.Info(L"[detached]\n"); return 0; } } diff --git a/src/windows/wslc/services/ConsoleService.h b/src/windows/wslc/services/ConsoleService.h index 446cf63844..73f02ac498 100644 --- a/src/windows/wslc/services/ConsoleService.h +++ b/src/windows/wslc/services/ConsoleService.h @@ -16,13 +16,17 @@ Module Name: #include #include #include +#include "Reporter.h" namespace wsl::windows::wslc::services { class ConsoleService { public: static int AttachToCurrentConsole( - wsl::windows::common::ConsoleState& console, wsl::windows::common::ClientRunningWSLCProcess&& process, bool triggerRefresh = false); + Reporter& reporter, + wsl::windows::common::ConsoleState& console, + wsl::windows::common::ClientRunningWSLCProcess&& process, + bool triggerRefresh = false); static bool RelayInteractiveTty( wsl::windows::common::ConsoleState& console, wsl::windows::common::ClientRunningWSLCProcess& process, HANDLE tty, bool triggerRefresh = false); static void RelayNonTtyProcess(wil::unique_handle&& Stdin, wil::unique_handle&& Stdout, wil::unique_handle&& Stderr); diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 9a85ebf988..6c4b928ba9 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -31,7 +31,6 @@ Module Name: namespace wsl::windows::wslc::services { using wsl::windows::common::ClientRunningWSLCProcess; using wsl::windows::common::wslc_schema::InspectContainer; -using wsl::windows::common::wslutil::PrintMessage; using namespace wsl::windows::common::wslutil; using namespace wsl::shared; using namespace wsl::windows::wslc::models; @@ -43,7 +42,7 @@ static void SetContainerArguments(WSLCProcessOptions& options, std::vector container; THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container)); @@ -325,7 +324,7 @@ int ContainerService::Attach(Session& session, const std::string& id) wsl::windows::common::ConsoleState console; if (!ConsoleService::RelayInteractiveTty(console, runningProcess, stdinLogs.Release().get(), true)) { - wsl::windows::common::wslutil::PrintMessage(L"[detached]", stderr); + reporter.Info(L"[detached]\n"); return 0; // Exit early if user detached } } @@ -395,7 +394,7 @@ std::wstring ContainerService::FormatPorts(WSLCContainerState state, const std:: return result; } -int ContainerService::Run(Session& session, const std::string& image, ContainerOptions runOptions) +int ContainerService::Run(Reporter& reporter, Session& session, const std::string& image, ContainerOptions runOptions) { // Reserve the CID file (fails if it already exists) before creating the container so a // container isn't created when the caller-requested path can't be written. The file is @@ -404,7 +403,7 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO auto warningCallback = Microsoft::WRL::Make(); // Create the container - auto runningContainer = CreateInternal(session, image, runOptions, warningCallback.Get()); + auto runningContainer = CreateInternal(reporter, session, image, runOptions, warningCallback.Get()); auto& container = runningContainer.Get(); WSLCContainerId containerId{}; @@ -435,18 +434,18 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO // Handle attach if requested if (attach) { - return ConsoleService::AttachToCurrentConsole(console, runningContainer.GetInitProcess()); + return ConsoleService::AttachToCurrentConsole(reporter, console, runningContainer.GetInitProcess()); } - PrintMessage(L"%hs", stdout, containerId); + reporter.Output(L"{}\n", wsl::shared::string::MultiByteToWide(containerId)); return 0; } -CreateContainerResult ContainerService::Create(Session& session, const std::string& image, ContainerOptions runOptions) +CreateContainerResult ContainerService::Create(Reporter& reporter, Session& session, const std::string& image, ContainerOptions runOptions) { CidFile cidFile(runOptions.CidFile); auto warningCallback = Microsoft::WRL::Make(); - auto runningContainer = CreateInternal(session, image, runOptions, warningCallback.Get()); + auto runningContainer = CreateInternal(reporter, session, image, runOptions, warningCallback.Get()); runningContainer.SetDeleteOnClose(false); auto& container = runningContainer.Get(); WSLCContainerId id{}; @@ -455,7 +454,7 @@ CreateContainerResult ContainerService::Create(Session& session, const std::stri return {.Id = id}; } -int ContainerService::Start(Session& session, const std::string& id, bool attach) +int ContainerService::Start(Reporter& reporter, Session& session, const std::string& id, bool attach) { wil::com_ptr container; THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container)); @@ -482,7 +481,7 @@ int ContainerService::Start(Session& session, const std::string& id, bool attach THROW_IF_FAILED(process->GetFlags(&processFlags)); ClientRunningWSLCProcess runningProcess(std::move(process), processFlags); - return ConsoleService::AttachToCurrentConsole(console, std::move(runningProcess), true); + return ConsoleService::AttachToCurrentConsole(reporter, console, std::move(runningProcess), true); } void ContainerService::Stop(Session& session, const std::string& id, StopContainerOptions options) @@ -553,7 +552,7 @@ std::vector ContainerService::List( return result; } -int ContainerService::Exec(Session& session, const std::string& id, ContainerOptions options) +int ContainerService::Exec(Reporter& reporter, Session& session, const std::string& id, ContainerOptions options) { wil::com_ptr container; THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container)); @@ -581,7 +580,7 @@ int ContainerService::Exec(Session& session, const std::string& id, ContainerOpt processLauncher.SetWorkingDirectory(std::move(options.WorkingDirectory)); } - return ConsoleService::AttachToCurrentConsole(console, processLauncher.Launch(*container)); + return ConsoleService::AttachToCurrentConsole(reporter, console, processLauncher.Launch(*container)); } InspectContainer ContainerService::Inspect(Session& session, const std::string& id) diff --git a/src/windows/wslc/services/ContainerService.h b/src/windows/wslc/services/ContainerService.h index fccdf20560..59a344214f 100644 --- a/src/windows/wslc/services/ContainerService.h +++ b/src/windows/wslc/services/ContainerService.h @@ -14,6 +14,7 @@ Module Name: #pragma once #include "SessionModel.h" #include "ContainerModel.h" +#include "Reporter.h" #include #include @@ -23,17 +24,17 @@ struct ContainerService static std::wstring ContainerStateToString(WSLCContainerState state, ULONGLONG stateChangedAt = 0); static std::wstring FormatRelativeTime(ULONGLONG timestamp); static std::wstring FormatPorts(WSLCContainerState state, const std::vector& ports); - static int Attach(models::Session& session, const std::string& id); - static int Run(models::Session& session, const std::string& image, models::ContainerOptions options); - static models::CreateContainerResult Create(models::Session& session, const std::string& image, models::ContainerOptions options); - static int Start(models::Session& session, const std::string& id, bool attach = false); + static int Attach(Reporter& reporter, models::Session& session, const std::string& id); + static int Run(Reporter& reporter, models::Session& session, const std::string& image, models::ContainerOptions options); + static models::CreateContainerResult Create(Reporter& reporter, models::Session& session, const std::string& image, models::ContainerOptions options); + static int Start(Reporter& reporter, models::Session& session, const std::string& id, bool attach = false); static void Stop(models::Session& session, const std::string& id, models::StopContainerOptions options); static void Kill(models::Session& session, const std::string& id, WSLCSignal signal = WSLCSignalSIGKILL); static void Delete(models::Session& session, const std::string& id, bool force); static std::vector List( models::Session& session, bool all = false, int limit = -1, const std::vector>& filters = {}); - static int Exec(models::Session& session, const std::string& id, models::ContainerOptions options); + static int Exec(Reporter& reporter, models::Session& session, const std::string& id, models::ContainerOptions options); static void Export(models::Session& session, const std::string& id, const std::wstring& outputPath); static void Export(models::Session& session, const std::string& id, HANDLE outputHandle); static wsl::windows::common::wslc_schema::InspectContainer Inspect(models::Session& session, const std::string& id); diff --git a/src/windows/wslc/services/ImageProgressCallback.cpp b/src/windows/wslc/services/ImageProgressCallback.cpp index d84a8e4fd9..9c9f483da2 100644 --- a/src/windows/wslc/services/ImageProgressCallback.cpp +++ b/src/windows/wslc/services/ImageProgressCallback.cpp @@ -23,8 +23,10 @@ using namespace wsl::windows::common::vt; void ImageProgressCallback::WriteTerminal(std::wstring_view content) const { - DWORD written; - LOG_IF_WIN32_BOOL_FALSE(WriteConsoleW(m_console, content.data(), static_cast(content.size()), &written, nullptr)); + // Route progress rendering through the Reporter's Info channel (stderr) so it + // respects the global output state and keeps stdout clean for scripting. Each + // call is emitted as a single atomic write, matching the previous WriteConsoleW. + m_reporter.Write(Reporter::Level::Info, L"{}", content); } auto ImageProgressCallback::MoveToLine(int line) @@ -46,11 +48,19 @@ HRESULT ImageProgressCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG cu { try { - if (!m_terminalMode.IsConsole()) + // The in-place progress display relies on cursor movement, so it only renders + // when the Info channel is an interactive VT console. When redirected, skip it. + if (!m_vtEnabled) { return S_OK; } + // Hide the cursor while rendering so the user doesn't see it bouncing through the + // cursor movements, then restore it at the final position. Uses VT sequences to match + // BuildImageCallback. scope_exit guarantees the cursor is shown again on every path. + WriteTerminal(Cursor::Hide.Get()); + auto showCursor = wil::scope_exit([this]() { WriteTerminal(Cursor::Show.Get()); }); + if (id == nullptr || *id == '\0') // Print all 'global' statuses on their own line { WriteTerminal(std::format(L"{}\n", status)); @@ -58,20 +68,20 @@ HRESULT ImageProgressCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG cu return S_OK; } - auto info = Info(); + const auto visibleWidth = m_reporter.GetConsoleWidth(Reporter::Level::Info); auto it = m_statuses.find(id); if (it == m_statuses.end()) { // If this is the first time we see this ID, create a new line for it. m_statuses.emplace(id, m_currentLine); - WriteTerminal(GenerateStatusLine(status, id, current, total, info) + L'\n'); + WriteTerminal(GenerateStatusLine(status, id, current, total, visibleWidth) + L'\n'); m_currentLine++; } else { auto revert = MoveToLine(m_currentLine - it->second); - WriteTerminal(GenerateStatusLine(status, id, current, total, info) + L'\n'); + WriteTerminal(GenerateStatusLine(status, id, current, total, visibleWidth) + L'\n'); } return S_OK; @@ -79,14 +89,7 @@ HRESULT ImageProgressCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG cu CATCH_RETURN(); } -CONSOLE_SCREEN_BUFFER_INFO ImageProgressCallback::Info() -{ - CONSOLE_SCREEN_BUFFER_INFO info{}; - THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)); - return info; -} - -std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total, const CONSOLE_SCREEN_BUFFER_INFO& info) +std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total, std::optional visibleWidth) { std::wstring line; if (total != 0) @@ -132,24 +135,25 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, line = std::format(L"{}: {}", id, status); } - // Use the visible window width (not the buffer width) to prevent wrapping. - const auto visibleWidth = std::max(0, static_cast(info.srWindow.Right) - info.srWindow.Left + 1); - - // Truncate to console width to prevent wrapping that would break cursor repositioning. - if (line.size() > static_cast(visibleWidth)) + // Truncate to the console width to prevent wrapping that would break cursor repositioning. + // When the width is unknown (redirected) the line is written as-is. + if (visibleWidth) { - line.resize(visibleWidth); - - // Avoid splitting a surrogate pair — if the last code unit is a high surrogate, - // drop it so we don't emit an invalid UTF-16 sequence. - if (!line.empty() && IS_HIGH_SURROGATE(line.back())) + if (line.size() > static_cast(*visibleWidth)) { - line.pop_back(); + line.resize(*visibleWidth); + + // Avoid splitting a surrogate pair — if the last code unit is a high surrogate, + // drop it so we don't emit an invalid UTF-16 sequence. + if (!line.empty() && IS_HIGH_SURROGATE(line.back())) + { + line.pop_back(); + } } - } - // Erase any previously written char on that line. - line.resize(visibleWidth, L' '); + // Erase any previously written char on that line. + line.resize(*visibleWidth, L' '); + } return line; } diff --git a/src/windows/wslc/services/ImageProgressCallback.h b/src/windows/wslc/services/ImageProgressCallback.h index f4bcc7b422..144c489f43 100644 --- a/src/windows/wslc/services/ImageProgressCallback.h +++ b/src/windows/wslc/services/ImageProgressCallback.h @@ -12,9 +12,11 @@ Module Name: --*/ #pragma once +#include "Reporter.h" #include "SessionService.h" #include "VTSupport.h" #include +#include #include namespace wsl::windows::wslc::services { @@ -24,17 +26,19 @@ class DECLSPEC_UUID("7A1D3376-835A-471A-8DC9-23653D9962D0") ImageProgressCallbac : public Microsoft::WRL::RuntimeClass, IProgressCallback, IFastRundown> { public: + explicit ImageProgressCallback(Reporter& reporter) : m_reporter(reporter) + { + } HRESULT OnProgress(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total) override; private: auto MoveToLine(int line); - static CONSOLE_SCREEN_BUFFER_INFO Info(); void WriteTerminal(std::wstring_view content) const; - std::wstring GenerateStatusLine(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total, const CONSOLE_SCREEN_BUFFER_INFO& info); + std::wstring GenerateStatusLine(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total, std::optional visibleWidth); + Reporter& m_reporter; std::map m_statuses; int m_currentLine = 0; - HANDLE m_console = GetStdHandle(STD_OUTPUT_HANDLE); - wsl::windows::common::vt::EnableVirtualTerminal m_vtMode{m_console}; - wsl::windows::common::vt::ChangeTerminalMode m_terminalMode{m_console, false}; + // Captured once: the progress display only renders when the Info channel is a VT console. + bool m_vtEnabled = m_reporter.IsVTEnabled(Reporter::Level::Info); }; } // namespace wsl::windows::wslc::services diff --git a/src/windows/wslc/services/SessionService.cpp b/src/windows/wslc/services/SessionService.cpp index 86ebcebbe7..97ac6cb073 100644 --- a/src/windows/wslc/services/SessionService.cpp +++ b/src/windows/wslc/services/SessionService.cpp @@ -64,7 +64,7 @@ Session SessionService::OpenOrCreateDefaultSession() return Session(std::move(session)); } -int SessionService::Attach(const Session& session) +int SessionService::Attach(Reporter& reporter, const Session& session) { // Configure console for interactive usage. wsl::windows::common::ConsoleState console{}; @@ -113,12 +113,12 @@ int SessionService::Attach(const Session& session) auto exitCode = process.GetExitCode(); - wslutil::PrintMessage(wsl::shared::Localization::MessageWslcShellExited(string::MultiByteToWide(shell), static_cast(exitCode)), stdout); + reporter.Output(L"{}\n", wsl::shared::Localization::MessageWslcShellExited(string::MultiByteToWide(shell), static_cast(exitCode))); return static_cast(exitCode); } -int SessionService::Enter(const std::wstring& storagePath, const std::wstring& displayName) +int SessionService::Enter(Reporter& reporter, const std::wstring& storagePath, const std::wstring& displayName) { THROW_HR_IF(E_INVALIDARG, storagePath.empty()); THROW_HR_IF(E_INVALIDARG, displayName.empty()); @@ -129,7 +129,7 @@ int SessionService::Enter(const std::wstring& storagePath, const std::wstring& d auto warningCallback = Microsoft::WRL::Make(); THROW_IF_FAILED(sessionManager->EnterSession(displayName.c_str(), storagePath.c_str(), warningCallback.Get(), &session)); wsl::windows::common::security::ConfigureForCOMImpersonation(session.get()); - wsl::windows::common::wslutil::PrintMessage(Localization::MessageWslcCreatedSession(displayName), stderr); + reporter.Info(L"{}\n", Localization::MessageWslcCreatedSession(displayName)); const std::string shell = "/bin/sh"; wsl::windows::common::WSLCProcessLauncher launcher{shell, {shell, "--login"}, {"TERM=xterm-256color"}, WSLCProcessFlagsTty | WSLCProcessFlagsStdin}; @@ -138,7 +138,7 @@ int SessionService::Enter(const std::wstring& storagePath, const std::wstring& d const auto windowSize = console.GetWindowSize(); launcher.SetTtySize(windowSize.Y, windowSize.X); - return ConsoleService::AttachToCurrentConsole(console, launcher.Launch(*session.get())); + return ConsoleService::AttachToCurrentConsole(reporter, console, launcher.Launch(*session.get())); } std::vector SessionService::List() @@ -161,7 +161,7 @@ std::vector SessionService::List() return result; } -int SessionService::Run(const Session& session, const std::vector& arguments) +int SessionService::Run(Reporter& reporter, const Session& session, const std::vector& arguments) { WI_ASSERT(!arguments.empty()); @@ -175,10 +175,10 @@ int SessionService::Run(const Session& session, const std::vector& THROW_IF_FAILED(result); wsl::windows::common::ConsoleState console{}; - return ConsoleService::AttachToCurrentConsole(console, std::move(process.value())); + return ConsoleService::AttachToCurrentConsole(reporter, console, std::move(process.value())); } -int SessionService::TerminateSession(const Session& session) +int SessionService::TerminateSession(Reporter& reporter, const Session& session) { HRESULT hr = session.Get()->Terminate(); if (FAILED(hr)) @@ -188,12 +188,12 @@ int SessionService::TerminateSession(const Session& session) wil::unique_cotaskmem_string displayName; if (SUCCEEDED(session.Get()->GetDisplayName(&displayName)) && displayName) { - wslutil::PrintMessage( - Localization::MessageErrorCode(Localization::MessageWslcTerminateSessionFailed(displayName.get()), errorString), stderr); + reporter.Error( + L"{}\n", Localization::MessageErrorCode(Localization::MessageWslcTerminateSessionFailed(displayName.get()), errorString)); } else { - wslutil::PrintMessage(Localization::MessageErrorCode(Localization::MessageWslcTerminateDefaultSessionFailed(), errorString), stderr); + reporter.Error(L"{}\n", Localization::MessageErrorCode(Localization::MessageWslcTerminateDefaultSessionFailed(), errorString)); } return 1; } diff --git a/src/windows/wslc/services/SessionService.h b/src/windows/wslc/services/SessionService.h index f92abf4222..acb226cdc6 100644 --- a/src/windows/wslc/services/SessionService.h +++ b/src/windows/wslc/services/SessionService.h @@ -14,6 +14,7 @@ Module Name: #pragma once #include "SessionModel.h" +#include "Reporter.h" #include namespace wsl::windows::wslc::services { @@ -26,8 +27,8 @@ struct SessionInformation struct SessionService { - static int Attach(const wsl::windows::wslc::models::Session& session); - static int Enter(const std::wstring& storagePath, const std::wstring& displayName); + static int Attach(Reporter& reporter, const wsl::windows::wslc::models::Session& session); + static int Enter(Reporter& reporter, const std::wstring& storagePath, const std::wstring& displayName); static std::vector List(); // Opens an existing session by name. Throws if not found. static wsl::windows::wslc::models::Session OpenSession(const std::wstring& name); @@ -36,8 +37,8 @@ struct SessionService // Opens or creates the default session. static wsl::windows::wslc::models::Session OpenOrCreateDefaultSession(); // Runs the given command and arguments in a session without a TTY, resolving the executable from PATH. - static int Run(const wsl::windows::wslc::models::Session& session, const std::vector& arguments); - static int TerminateSession(const wsl::windows::wslc::models::Session& session); + static int Run(Reporter& reporter, const wsl::windows::wslc::models::Session& session, const std::vector& arguments); + static int TerminateSession(Reporter& reporter, const wsl::windows::wslc::models::Session& session); private: // Common open-only session lookup with unified error handling. diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index ad7d112d87..a04b60b2c4 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -135,7 +135,7 @@ nlohmann::json ComputeContainerStatsJson(const wsl::windows::common::docker_sche namespace wsl::windows::wslc::task { -static bool TryInspectContainer(Session& session, const std::string& containerId, std::optional& inspectData) +static bool TryInspectContainer(Reporter& reporter, Session& session, const std::string& containerId, std::optional& inspectData) { try { @@ -146,7 +146,7 @@ static bool TryInspectContainer(Session& session, const std::string& containerId { if (ex.GetErrorCode() == WSLC_E_CONTAINER_NOT_FOUND) { - PrintMessage(Localization::MessageWslcContainerNotFound(containerId.c_str()), stderr); + reporter.Error(L"{}\n", Localization::MessageWslcContainerNotFound(containerId.c_str())); return false; } @@ -157,7 +157,7 @@ static bool TryInspectContainer(Session& session, const std::string& containerId void AttachContainer::operator()(CLIExecutionContext& context) const { WI_ASSERT(context.Data.Contains(Data::Session)); - context.ExitCode = ContainerService::Attach(context.Data.Get(), WideToMultiByte(m_containerId)); + context.ExitCode = ContainerService::Attach(context.Reporter, context.Data.Get(), WideToMultiByte(m_containerId)); } void CreateContainer(CLIExecutionContext& context) @@ -166,8 +166,8 @@ void CreateContainer(CLIExecutionContext& context) WI_ASSERT(context.Args.Contains(ArgType::ImageId)); WI_ASSERT(context.Data.Contains(Data::ContainerOptions)); auto result = ContainerService::Create( - context.Data.Get(), WideToMultiByte(context.Args.Get()), context.Data.Get()); - PrintMessage(MultiByteToWide(result.Id)); + context.Reporter, context.Data.Get(), WideToMultiByte(context.Args.Get()), context.Data.Get()); + context.Reporter.Output(L"{}\n", MultiByteToWide(result.Id)); } void ExecContainer(CLIExecutionContext& context) @@ -176,7 +176,7 @@ void ExecContainer(CLIExecutionContext& context) WI_ASSERT(context.Args.Contains(ArgType::ContainerId)); WI_ASSERT(context.Data.Contains(Data::ContainerOptions)); context.ExitCode = ContainerService::Exec( - context.Data.Get(), WideToMultiByte(context.Args.Get()), context.Data.Get()); + context.Reporter, context.Data.Get(), WideToMultiByte(context.Args.Get()), context.Data.Get()); } void GetContainers(CLIExecutionContext& context) @@ -221,7 +221,7 @@ void InspectContainers(CLIExecutionContext& context) for (const auto& id : containerIds) { std::optional inspectData; - if (TryInspectContainer(session, WideToMultiByte(id), inspectData)) + if (TryInspectContainer(context.Reporter, session, WideToMultiByte(id), inspectData)) { result.push_back(*inspectData); } @@ -232,7 +232,7 @@ void InspectContainers(CLIExecutionContext& context) } auto json = ToJson(result, c_jsonPrettyPrintIndent); - PrintMessage(MultiByteToWide(json)); + context.Reporter.Output(L"{}\n", MultiByteToWide(json)); } void KillContainers(CLIExecutionContext& context) @@ -249,7 +249,7 @@ void KillContainers(CLIExecutionContext& context) for (const auto& id : containerIds) { ContainerService::Kill(session, WideToMultiByte(id), signal); - PrintMessage(id); + context.Reporter.Output(L"{}\n", id); } } @@ -290,7 +290,7 @@ void ListContainers(CLIExecutionContext& context) // Print only the container ids for (const auto& container : containers) { - PrintMessage(MultiByteToWide(container.Id)); + context.Reporter.Output(L"{}\n", MultiByteToWide(container.Id)); } return; @@ -307,7 +307,7 @@ void ListContainers(CLIExecutionContext& context) case FormatType::Json: { auto json = ToJson(containers, c_jsonPrettyPrintIndent); - PrintMessage(MultiByteToWide(json)); + context.Reporter.Output(L"{}\n", MultiByteToWide(json)); break; } case FormatType::Table: @@ -361,7 +361,7 @@ void RemoveContainers(CLIExecutionContext& context) for (const auto& id : containerIds) { ContainerService::Delete(session, WideToMultiByte(id), force); - PrintMessage(id); + context.Reporter.Output(L"{}\n", id); } } @@ -371,7 +371,7 @@ void RunContainer(CLIExecutionContext& context) WI_ASSERT(context.Args.Contains(ArgType::ImageId)); WI_ASSERT(context.Data.Contains(Data::ContainerOptions)); context.ExitCode = ContainerService::Run( - context.Data.Get(), WideToMultiByte(context.Args.Get()), context.Data.Get()); + context.Reporter, context.Data.Get(), WideToMultiByte(context.Args.Get()), context.Data.Get()); } void SetContainerOptionsFromArgs(CLIExecutionContext& context) @@ -680,7 +680,7 @@ void ShowContainerStats(CLIExecutionContext& context) { case FormatType::Json: { - PrintMessage(MultiByteToWide(statsJson.dump(c_jsonPrettyPrintIndent))); + context.Reporter.Output(L"{}\n", MultiByteToWide(statsJson.dump(c_jsonPrettyPrintIndent))); break; } case FormatType::Table: @@ -736,11 +736,11 @@ void StartContainer(CLIExecutionContext& context) WI_ASSERT(context.Args.Contains(ArgType::ContainerId)); const auto& containerId = context.Args.Get(); const bool attach = context.Args.Contains(ArgType::Attach); - context.ExitCode = ContainerService::Start(context.Data.Get(), WideToMultiByte(containerId), attach); + context.ExitCode = ContainerService::Start(context.Reporter, context.Data.Get(), WideToMultiByte(containerId), attach); if (!attach) { - PrintMessage(containerId); + context.Reporter.Output(L"{}\n", containerId); } } @@ -763,7 +763,7 @@ void StopContainers(CLIExecutionContext& context) for (const auto& id : containersToStop) { ContainerService::Stop(context.Data.Get(), WideToMultiByte(id), options); - PrintMessage(id); + context.Reporter.Output(L"{}\n", id); } } @@ -808,10 +808,10 @@ void PruneContainers(CLIExecutionContext& context) for (const auto& containerId : result.PrunedContainers) { - PrintMessage(MultiByteToWide(containerId)); + context.Reporter.Output(L"{}\n", MultiByteToWide(containerId)); } - PrintMessage(L""); - PrintMessage(Localization::WSLCCLI_ContainerPruneSpaceReclaimedBytes(wsl::shared::string::FormatBytes(result.SpaceReclaimed))); + context.Reporter.Output(L"\n"); + context.Reporter.Output(L"{}\n", Localization::WSLCCLI_ContainerPruneSpaceReclaimedBytes(wsl::shared::string::FormatBytes(result.SpaceReclaimed))); } } // namespace wsl::windows::wslc::task diff --git a/src/windows/wslc/tasks/ImageTasks.cpp b/src/windows/wslc/tasks/ImageTasks.cpp index 5bde53de75..ba28c1196d 100644 --- a/src/windows/wslc/tasks/ImageTasks.cpp +++ b/src/windows/wslc/tasks/ImageTasks.cpp @@ -71,7 +71,7 @@ namespace { } // namespace -static bool TryInspectImage(Session& session, const std::string& imageId, std::optional& inspectData) +static bool TryInspectImage(Reporter& reporter, Session& session, const std::string& imageId, std::optional& inspectData) { try { @@ -82,7 +82,7 @@ static bool TryInspectImage(Session& session, const std::string& imageId, std::o { if (ex.GetErrorCode() == WSLC_E_IMAGE_NOT_FOUND) { - PrintMessage(Localization::MessageWslcImageNotFound(imageId.c_str()), stderr); + reporter.Error(L"{}\n", Localization::MessageWslcImageNotFound(imageId.c_str())); return false; } @@ -117,7 +117,7 @@ void BuildImage(CLIExecutionContext& context) target = context.Args.Get(); } - PrintMessage(std::format(L"Building image from directory: {}\n", contextPath), stdout); + context.Reporter.Output(L"Building image from directory: {}\n", contextPath); WSLCBuildImageFlags flags = WSLCBuildImageFlagsNone; WI_SetFlagIf(flags, WSLCBuildImageFlagsVerbose, context.Args.Contains(ArgType::Verbose)); @@ -125,7 +125,7 @@ void BuildImage(CLIExecutionContext& context) WI_SetFlagIf(flags, WSLCBuildImageFlagsPull, context.Args.Contains(ArgType::BuildPull)); auto cancelEvent = context.CreateCancelEvent(); - BuildImageCallback callback(cancelEvent, context.Args.Contains(ArgType::Verbose)); + BuildImageCallback callback(context.Reporter, cancelEvent, context.Args.Contains(ArgType::Verbose)); services::ImageService::Build(session, contextPath, tags, buildArgs, labels, dockerfilePath, target, flags, &callback, cancelEvent); } @@ -179,7 +179,7 @@ void ListImages(CLIExecutionContext& context) case FormatType::Json: { auto json = ToJson(images, c_jsonPrettyPrintIndent); - PrintMessage(MultiByteToWide(json)); + context.Reporter.Output(L"{}\n", MultiByteToWide(json)); break; } case FormatType::Table: @@ -223,7 +223,7 @@ void PullImage(CLIExecutionContext& context) auto& session = context.Data.Get(); auto& imageId = context.Args.Get(); - ImageProgressCallback callback; + ImageProgressCallback callback(context.Reporter); services::ImageService::Pull(session, WideToMultiByte(imageId), &callback); } @@ -234,7 +234,7 @@ void PushImage(CLIExecutionContext& context) auto& session = context.Data.Get(); auto& imageId = context.Args.Get(); - ImageProgressCallback callback; + ImageProgressCallback callback(context.Reporter); services::ImageService::Push(session, WideToMultiByte(imageId), &callback); } @@ -300,7 +300,7 @@ void InspectImages(CLIExecutionContext& context) for (const auto& id : imageIds) { std::optional inspectData; - if (TryInspectImage(session, WideToMultiByte(id), inspectData)) + if (TryInspectImage(context.Reporter, session, WideToMultiByte(id), inspectData)) { result.push_back(*inspectData); } @@ -311,7 +311,7 @@ void InspectImages(CLIExecutionContext& context) } auto json = ToJson(result, c_jsonPrettyPrintIndent); - PrintMessage(MultiByteToWide(json)); + context.Reporter.Output(L"{}\n", MultiByteToWide(json)); } void SaveImage(CLIExecutionContext& context) @@ -379,15 +379,15 @@ void PruneImages(CLIExecutionContext& context) for (const auto& image : result.UntaggedImages) { - PrintMessage(Localization::WSLCCLI_ImagePruneUntagged(image)); + context.Reporter.Output(L"{}\n", Localization::WSLCCLI_ImagePruneUntagged(image)); } for (const auto& image : result.DeletedImages) { - PrintMessage(Localization::WSLCCLI_ImagePruneDeleted(image)); + context.Reporter.Output(L"{}\n", Localization::WSLCCLI_ImagePruneDeleted(image)); } - PrintMessage(L""); - PrintMessage(Localization::WSLCCLI_ImagePruneSpaceReclaimedBytes(wsl::shared::string::FormatBytes(result.SpaceReclaimed))); + context.Reporter.Output(L"\n"); + context.Reporter.Output(L"{}\n", Localization::WSLCCLI_ImagePruneSpaceReclaimedBytes(wsl::shared::string::FormatBytes(result.SpaceReclaimed))); } } // namespace wsl::windows::wslc::task diff --git a/src/windows/wslc/tasks/InspectTasks.cpp b/src/windows/wslc/tasks/InspectTasks.cpp index 52de869112..644daf5483 100644 --- a/src/windows/wslc/tasks/InspectTasks.cpp +++ b/src/windows/wslc/tasks/InspectTasks.cpp @@ -107,12 +107,12 @@ void Inspect(CLIExecutionContext& context) } else { - PrintMessage(Localization::WSLCCLI_ObjectNotFoundError(objectId), stderr); + context.Reporter.Error(L"{}\n", Localization::WSLCCLI_ObjectNotFoundError(objectId)); context.ExitCode = 1; } } // Always print the array, even if it's empty or an error was encountered - PrintMessage(MultiByteToWide(array.dump(c_jsonPrettyPrintIndent))); + context.Reporter.Output(L"{}\n", MultiByteToWide(array.dump(c_jsonPrettyPrintIndent))); } } // namespace wsl::windows::wslc::task diff --git a/src/windows/wslc/tasks/NetworkTasks.cpp b/src/windows/wslc/tasks/NetworkTasks.cpp index d4a7a8467b..47f27ce44a 100644 --- a/src/windows/wslc/tasks/NetworkTasks.cpp +++ b/src/windows/wslc/tasks/NetworkTasks.cpp @@ -30,7 +30,7 @@ using namespace wsl::windows::wslc::services; namespace wsl::windows::wslc::task { -static bool TryInspectNetwork(Session& session, const std::string& networkName, std::optional& inspectData) +static bool TryInspectNetwork(Reporter& reporter, Session& session, const std::string& networkName, std::optional& inspectData) { try { @@ -41,7 +41,7 @@ static bool TryInspectNetwork(Session& session, const std::string& networkName, { if (ex.GetErrorCode() == WSLC_E_NETWORK_NOT_FOUND) { - PrintMessage(Localization::MessageWslcNetworkNotFound(networkName.c_str()), stderr); + reporter.Error(L"{}\n", Localization::MessageWslcNetworkNotFound(networkName.c_str())); return false; } @@ -49,7 +49,7 @@ static bool TryInspectNetwork(Session& session, const std::string& networkName, } } -static bool TryDeleteNetwork(Session& session, const std::string& networkName, bool force) +static bool TryDeleteNetwork(Reporter& reporter, Session& session, const std::string& networkName, bool force) { try { @@ -62,7 +62,7 @@ static bool TryDeleteNetwork(Session& session, const std::string& networkName, b { if (!force) { - PrintMessage(Localization::MessageWslcNetworkNotFound(networkName.c_str()), stderr); + reporter.Error(L"{}\n", Localization::MessageWslcNetworkNotFound(networkName.c_str())); } return false; @@ -108,7 +108,7 @@ void CreateNetwork(CLIExecutionContext& context) } NetworkService::Create(context.Data.Get(), options); - PrintMessage(MultiByteToWide(options.Name)); + context.Reporter.Output(L"{}\n", MultiByteToWide(options.Name)); } void DeleteNetworks(CLIExecutionContext& context) @@ -119,9 +119,9 @@ void DeleteNetworks(CLIExecutionContext& context) const bool force = context.Args.Contains(ArgType::Force); for (const auto& name : networkNames) { - if (TryDeleteNetwork(session, WideToMultiByte(name), force)) + if (TryDeleteNetwork(context.Reporter, session, WideToMultiByte(name), force)) { - PrintMessage(name); + context.Reporter.Output(L"{}\n", name); } else if (!force) { @@ -146,7 +146,7 @@ void InspectNetworks(CLIExecutionContext& context) for (const auto& name : networkNames) { std::optional inspectData; - if (TryInspectNetwork(session, WideToMultiByte(name), inspectData)) + if (TryInspectNetwork(context.Reporter, session, WideToMultiByte(name), inspectData)) { result.push_back(*inspectData); } @@ -157,7 +157,7 @@ void InspectNetworks(CLIExecutionContext& context) } auto json = ToJson(result, c_jsonPrettyPrintIndent); - PrintMessage(MultiByteToWide(json)); + context.Reporter.Output(L"{}\n", MultiByteToWide(json)); } void ListNetworks(CLIExecutionContext& context) @@ -169,7 +169,7 @@ void ListNetworks(CLIExecutionContext& context) { for (const auto& network : networks) { - PrintMessage(MultiByteToWide(network.Name)); + context.Reporter.Output(L"{}\n", MultiByteToWide(network.Name)); } return; @@ -186,7 +186,7 @@ void ListNetworks(CLIExecutionContext& context) case FormatType::Json: { auto json = ToJson(networks, c_jsonPrettyPrintIndent); - PrintMessage(MultiByteToWide(json)); + context.Reporter.Output(L"{}\n", MultiByteToWide(json)); break; } case FormatType::Table: @@ -224,7 +224,7 @@ void PruneNetworks(CLIExecutionContext& context) for (const auto& networkName : result.PrunedNetworks) { - PrintMessage(Localization::WSLCCLI_NetworkPruneDeleted(MultiByteToWide(networkName))); + context.Reporter.Output(L"{}\n", Localization::WSLCCLI_NetworkPruneDeleted(MultiByteToWide(networkName))); } } } // namespace wsl::windows::wslc::task diff --git a/src/windows/wslc/tasks/RegistryTasks.cpp b/src/windows/wslc/tasks/RegistryTasks.cpp index 279a1ead4e..7084988cb9 100644 --- a/src/windows/wslc/tasks/RegistryTasks.cpp +++ b/src/windows/wslc/tasks/RegistryTasks.cpp @@ -46,7 +46,7 @@ void Login(CLIExecutionContext& context) auto [credUsername, credSecret] = RegistryService::Authenticate(session, serverAddress, username, password); RegistryService::Store(serverAddress, credUsername, credSecret); - PrintMessage(Localization::WSLCCLI_LoginSucceeded()); + context.Reporter.Output(L"{}\n", Localization::WSLCCLI_LoginSucceeded()); } void Logout(CLIExecutionContext& context) @@ -60,7 +60,7 @@ void Logout(CLIExecutionContext& context) RegistryService::Erase(serverAddress); - PrintMessage(Localization::WSLCCLI_LogoutSucceeded(MultiByteToWide(serverAddress))); + context.Reporter.Output(L"{}\n", Localization::WSLCCLI_LogoutSucceeded(MultiByteToWide(serverAddress))); } } // namespace wsl::windows::wslc::task diff --git a/src/windows/wslc/tasks/SessionTasks.cpp b/src/windows/wslc/tasks/SessionTasks.cpp index 41893fd2a9..2ad531118d 100644 --- a/src/windows/wslc/tasks/SessionTasks.cpp +++ b/src/windows/wslc/tasks/SessionTasks.cpp @@ -30,7 +30,7 @@ namespace wsl::windows::wslc::task { void AttachToSession(CLIExecutionContext& context) { auto& session = context.Data.Get(); - context.ExitCode = SessionService::Attach(session); + context.ExitCode = SessionService::Attach(context.Reporter, session); } void OpenSessionIfSpecified(CLIExecutionContext& context) @@ -70,7 +70,7 @@ void ListSessions(CLIExecutionContext& context) if (context.Args.Contains(ArgType::Verbose)) { const wchar_t* plural = sessions.size() == 1 ? L"" : L"s"; - PrintMessage(std::format(L"[wslc] Found {} session{}", sessions.size(), plural), stdout); + context.Reporter.Output(L"[wslc] Found {} session{}\n", sessions.size(), plural); } TableOutput<3> table( @@ -91,7 +91,7 @@ void ListSessions(CLIExecutionContext& context) void TerminateSession(CLIExecutionContext& context) { auto& session = context.Data.Get(); - context.ExitCode = SessionService::TerminateSession(session); + context.ExitCode = SessionService::TerminateSession(context.Reporter, session); } void RunInSession(CLIExecutionContext& context) @@ -108,7 +108,7 @@ void RunInSession(CLIExecutionContext& context) } } - context.ExitCode = SessionService::Run(session, arguments); + context.ExitCode = SessionService::Run(context.Reporter, session, arguments); } void EnterSession(CLIExecutionContext& context) @@ -127,7 +127,7 @@ void EnterSession(CLIExecutionContext& context) sessionName = wsl::shared::string::GuidToString(guid, wsl::shared::string::GuidToStringFlags::None); } - context.ExitCode = SessionService::Enter(storagePath.wstring(), sessionName); + context.ExitCode = SessionService::Enter(context.Reporter, storagePath.wstring(), sessionName); } } // namespace wsl::windows::wslc::task diff --git a/src/windows/wslc/tasks/VolumeTasks.cpp b/src/windows/wslc/tasks/VolumeTasks.cpp index 4d00fa90ad..8f683a08c6 100644 --- a/src/windows/wslc/tasks/VolumeTasks.cpp +++ b/src/windows/wslc/tasks/VolumeTasks.cpp @@ -30,7 +30,7 @@ using namespace wsl::windows::wslc::services; namespace wsl::windows::wslc::task { -static bool TryInspectVolume(Session& session, const std::string& volumeName, std::optional& inspectData) +static bool TryInspectVolume(Reporter& reporter, Session& session, const std::string& volumeName, std::optional& inspectData) { try { @@ -41,7 +41,7 @@ static bool TryInspectVolume(Session& session, const std::string& volumeName, st { if (ex.GetErrorCode() == WSLC_E_VOLUME_NOT_FOUND) { - PrintMessage(Localization::MessageWslcVolumeNotFound(volumeName.c_str()), stderr); + reporter.Error(L"{}\n", Localization::MessageWslcVolumeNotFound(volumeName.c_str())); return false; } @@ -49,7 +49,7 @@ static bool TryInspectVolume(Session& session, const std::string& volumeName, st } } -static bool TryDeleteVolume(Session& session, const std::string& volumeName, bool force) +static bool TryDeleteVolume(Reporter& reporter, Session& session, const std::string& volumeName, bool force) { try { @@ -62,7 +62,7 @@ static bool TryDeleteVolume(Session& session, const std::string& volumeName, boo { if (!force) { - PrintMessage(Localization::MessageWslcVolumeNotFound(volumeName.c_str()), stderr); + reporter.Error(L"{}\n", Localization::MessageWslcVolumeNotFound(volumeName.c_str())); } return false; @@ -100,7 +100,7 @@ void CreateVolume(CLIExecutionContext& context) } auto result = VolumeService::Create(context.Data.Get(), options); - PrintMessage(MultiByteToWide(result.Name)); + context.Reporter.Output(L"{}\n", MultiByteToWide(result.Name)); } void DeleteVolumes(CLIExecutionContext& context) @@ -111,9 +111,9 @@ void DeleteVolumes(CLIExecutionContext& context) const bool force = context.Args.Contains(ArgType::Force); for (const auto& name : volumeNames) { - if (TryDeleteVolume(session, WideToMultiByte(name), force)) + if (TryDeleteVolume(context.Reporter, session, WideToMultiByte(name), force)) { - PrintMessage(name); + context.Reporter.Output(L"{}\n", name); } else if (!force) { @@ -138,7 +138,7 @@ void InspectVolumes(CLIExecutionContext& context) for (const auto& name : volumeNames) { std::optional inspectData; - if (TryInspectVolume(session, WideToMultiByte(name), inspectData)) + if (TryInspectVolume(context.Reporter, session, WideToMultiByte(name), inspectData)) { result.push_back(*inspectData); } @@ -149,7 +149,7 @@ void InspectVolumes(CLIExecutionContext& context) } auto json = ToJson(result, c_jsonPrettyPrintIndent); - PrintMessage(MultiByteToWide(json)); + context.Reporter.Output(L"{}\n", MultiByteToWide(json)); } void ListVolumes(CLIExecutionContext& context) @@ -161,7 +161,7 @@ void ListVolumes(CLIExecutionContext& context) { for (const auto& volume : volumes) { - PrintMessage(MultiByteToWide(volume.Name)); + context.Reporter.Output(L"{}\n", MultiByteToWide(volume.Name)); } return; @@ -178,7 +178,7 @@ void ListVolumes(CLIExecutionContext& context) case FormatType::Json: { auto json = ToJson(volumes, c_jsonPrettyPrintIndent); - PrintMessage(MultiByteToWide(json)); + context.Reporter.Output(L"{}\n", MultiByteToWide(json)); break; } case FormatType::Table: @@ -217,10 +217,10 @@ void PruneVolumes(CLIExecutionContext& context) for (const auto& volumeName : result.PrunedVolumes) { - PrintMessage(Localization::WSLCCLI_VolumePruneDeleted(MultiByteToWide(volumeName))); + context.Reporter.Output(L"{}\n", Localization::WSLCCLI_VolumePruneDeleted(MultiByteToWide(volumeName))); } - PrintMessage(L""); - PrintMessage(Localization::WSLCCLI_VolumePruneSpaceReclaimed(wsl::shared::string::FormatBytes(result.SpaceReclaimed))); + context.Reporter.Output(L"\n"); + context.Reporter.Output(L"{}\n", Localization::WSLCCLI_VolumePruneSpaceReclaimed(wsl::shared::string::FormatBytes(result.SpaceReclaimed))); } } // namespace wsl::windows::wslc::task From 8a2c1e06f43788c3fc6d76710c8b2fce2b31079c Mon Sep 17 00:00:00 2001 From: David Bennett Date: Mon, 6 Jul 2026 16:46:43 -0700 Subject: [PATCH 2/2] Comment trimming --- src/windows/wslc/services/BuildImageCallback.cpp | 2 +- src/windows/wslc/services/ImageProgressCallback.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/windows/wslc/services/BuildImageCallback.cpp b/src/windows/wslc/services/BuildImageCallback.cpp index 39eaad855f..356174fbc1 100644 --- a/src/windows/wslc/services/BuildImageCallback.cpp +++ b/src/windows/wslc/services/BuildImageCallback.cpp @@ -47,7 +47,7 @@ CATCH_LOG() void BuildImageCallback::WriteTerminal(std::wstring_view content) const { // Route the scrolling build display through the Reporter's Info channel (stderr) so it - // respects the global output state. Each call is one atomic write, as before. + // respects the global output state. Each call is one atomic write. m_reporter.Write(Reporter::Level::Info, L"{}", content); } diff --git a/src/windows/wslc/services/ImageProgressCallback.cpp b/src/windows/wslc/services/ImageProgressCallback.cpp index 9c9f483da2..43259fa527 100644 --- a/src/windows/wslc/services/ImageProgressCallback.cpp +++ b/src/windows/wslc/services/ImageProgressCallback.cpp @@ -25,7 +25,7 @@ void ImageProgressCallback::WriteTerminal(std::wstring_view content) const { // Route progress rendering through the Reporter's Info channel (stderr) so it // respects the global output state and keeps stdout clean for scripting. Each - // call is emitted as a single atomic write, matching the previous WriteConsoleW. + // call is emitted as a single atomic write. m_reporter.Write(Reporter::Level::Info, L"{}", content); } @@ -56,8 +56,8 @@ HRESULT ImageProgressCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG cu } // Hide the cursor while rendering so the user doesn't see it bouncing through the - // cursor movements, then restore it at the final position. Uses VT sequences to match - // BuildImageCallback. scope_exit guarantees the cursor is shown again on every path. + // cursor movements, then restore it at the final position. scope_exit guarantees the + // cursor is shown again on every exit path. WriteTerminal(Cursor::Hide.Get()); auto showCursor = wil::scope_exit([this]() { WriteTerminal(Cursor::Show.Get()); });