mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-06 10:30:29 +02:00
Completes the caller integration so the protected store is the single source of truth for the whole workspace lifecycle, not just the Editor. - JsonUtils: add ReadWorkspacesFromService() (GetBlob -> parse the same UTF-8 JSON -> WorkspacesListJSON::FromJson; NotFound -> empty; ServiceUnavailable -> legacy file fallback) and WriteWorkspacesToService() (ToJson -> Stringify -> PutBlob; ServiceUnavailable -> legacy file fallback). - WorkspacesLauncher: read the workspace list and write back the last-launched-time update through the service (main.cpp + Launcher.cpp). - WorkspacesWindowArranger: read the list through the service. - WorkspacesLib links WorkspacesSettingsClient (native PTSettingsClient) so the EXEs resolve GetBlob/PutBlob. - WorkspacesData: WorkspacesFile()/TempWorkspacesFile() now resolve to the user-writable %LocalAppData%\Microsoft\PowerToys\Workspaces folder — the no-service fallback location and the transient snapshot->editor temp handoff, matching the managed editor (FolderUtils.DataFolder). The protected store is reached only through the service. This fixes the previously-broken save->launch loop (the native tools wrote/read a different %ProgramData% path via direct IO, which would be denied under the protected DACL and would not see Editor-saved data) and the snapshot->editor handoff (temp file was in the protected folder). Verified locally: WorkspacesLib + Launcher + WindowArranger + SnapshotTool build clean in Debug AND Release (native NuGet restore via downloaded nuget.exe). The service round-trip these wrap is already proven (verify-prototype 9/9; editor harness 8/8) and they reuse the same PTSettingsClient + WorkspacesListJSON. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
204 lines
6.8 KiB
C++
204 lines
6.8 KiB
C++
#include "pch.h"
|
|
#include "Launcher.h"
|
|
|
|
#include <common/utils/json.h>
|
|
|
|
#include <workspaces-common/MonitorUtils.h>
|
|
|
|
#include <WorkspacesLib/trace.h>
|
|
|
|
#include <AppLauncher.h>
|
|
#include <WorkspacesLib/AppUtils.h>
|
|
#include <WorkspacesLib/JsonUtils.h>
|
|
|
|
Launcher::Launcher(const WorkspacesData::WorkspacesProject& project,
|
|
std::vector<WorkspacesData::WorkspacesProject>& workspaces,
|
|
InvokePoint invokePoint) :
|
|
m_project(project),
|
|
m_workspaces(workspaces),
|
|
m_invokePoint(invokePoint),
|
|
m_start(std::chrono::high_resolution_clock::now()),
|
|
m_uiHelper(std::make_unique<LauncherUIHelper>(std::bind(&Launcher::handleUIMessage, this, std::placeholders::_1))),
|
|
m_windowArrangerHelper(std::make_unique<WindowArrangerHelper>(std::bind(&Launcher::handleWindowArrangerMessage, this, std::placeholders::_1))),
|
|
m_launchingStatus(m_project)
|
|
{
|
|
// main thread
|
|
Logger::info(L"Launch Workspace {} : {}", m_project.name, m_project.id);
|
|
|
|
m_uiHelper->LaunchUI();
|
|
m_uiHelper->UpdateLaunchStatus(m_launchingStatus.Get());
|
|
|
|
bool launchElevated = std::find_if(m_project.apps.begin(), m_project.apps.end(), [](const WorkspacesData::WorkspacesProject::Application& app) { return app.isElevated; }) != m_project.apps.end();
|
|
m_windowArrangerHelper->Launch(m_project.id, launchElevated, [&]() -> bool
|
|
{
|
|
if (m_launchingStatus.AllLaunchedAndMoved())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_launchingStatus.AllLaunched())
|
|
{
|
|
static auto arrangerTimeDelay = std::chrono::high_resolution_clock::now();
|
|
auto currentTime = std::chrono::high_resolution_clock::now();
|
|
std::chrono::duration<double> timeDiff = currentTime - arrangerTimeDelay;
|
|
if (timeDiff.count() >= 5)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
Launcher::~Launcher()
|
|
{
|
|
// main thread, will wait until arranger is finished
|
|
Logger::trace(L"Finalizing launch");
|
|
|
|
// update last-launched time
|
|
if (m_invokePoint != InvokePoint::LaunchAndEdit)
|
|
{
|
|
time_t launchedTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
|
m_project.lastLaunchedTime = launchedTime;
|
|
for (int i = 0; i < m_workspaces.size(); i++)
|
|
{
|
|
if (m_workspaces[i].id == m_project.id)
|
|
{
|
|
m_workspaces[i] = m_project;
|
|
break;
|
|
}
|
|
}
|
|
JsonUtils::WriteWorkspacesToService(m_workspaces);
|
|
}
|
|
|
|
// telemetry
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
std::chrono::duration<double> duration = end - m_start;
|
|
Logger::trace(L"Launching time: {} s", duration.count());
|
|
|
|
auto monitors = MonitorUtils::IdentifyMonitors();
|
|
bool differentSetup = monitors.size() != m_project.monitors.size();
|
|
if (!differentSetup)
|
|
{
|
|
for (const auto& monitor : m_project.monitors)
|
|
{
|
|
auto setup = std::find_if(monitors.begin(), monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.dpi == monitor.dpi && val.monitorRectDpiAware == monitor.monitorRectDpiAware; });
|
|
if (setup == monitors.end())
|
|
{
|
|
differentSetup = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::lock_guard lock(m_launchErrorsMutex);
|
|
Trace::Workspaces::Launch(m_launchedSuccessfully, m_project, m_invokePoint, duration.count(), differentSetup, m_launchErrors);
|
|
}
|
|
|
|
void Launcher::Launch() // Launching thread
|
|
{
|
|
const long maxWaitTimeMs = 3000;
|
|
const long ms = 100;
|
|
|
|
// Launch apps
|
|
for (auto appState = m_launchingStatus.GetNext(LaunchingState::Waiting);appState.has_value();appState = m_launchingStatus.GetNext(LaunchingState::Waiting))
|
|
{
|
|
auto app = appState.value().application;
|
|
|
|
long waitingTime = 0;
|
|
bool additionalWait = false;
|
|
while (!m_launchingStatus.AllInstancesOfTheAppLaunchedAndMoved(app) && waitingTime < maxWaitTimeMs)
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
|
waitingTime += ms;
|
|
additionalWait = true;
|
|
}
|
|
|
|
if (additionalWait)
|
|
{
|
|
// Resolves an issue when Outlook does not launch when launching one after another.
|
|
// Launching Outlook instances right one after another causes error message.
|
|
// Launching Outlook instances with less than 1-second delay causes the second window not to appear
|
|
// even though there wasn't a launch error.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
|
}
|
|
|
|
if (waitingTime >= maxWaitTimeMs)
|
|
{
|
|
Logger::info(L"Waiting time for launching next {} instance expired", app.name);
|
|
}
|
|
|
|
bool launched{ false };
|
|
{
|
|
std::lock_guard lock(m_launchErrorsMutex);
|
|
launched = AppLauncher::Launch(app, m_launchErrors);
|
|
}
|
|
|
|
if (launched)
|
|
{
|
|
m_launchingStatus.Update(app, LaunchingState::Launched);
|
|
}
|
|
else
|
|
{
|
|
Logger::error(L"Failed to launch {}", app.name);
|
|
m_launchingStatus.Update(app, LaunchingState::Failed);
|
|
m_launchedSuccessfully = false;
|
|
}
|
|
|
|
auto status = m_launchingStatus.Get(app); // updated after launch status
|
|
if (status.has_value())
|
|
{
|
|
{
|
|
std::lock_guard lock(m_windowArrangerHelperMutex);
|
|
m_windowArrangerHelper->UpdateLaunchStatus(status.value());
|
|
}
|
|
}
|
|
|
|
{
|
|
std::lock_guard lock(m_uiHelperMutex);
|
|
m_uiHelper->UpdateLaunchStatus(m_launchingStatus.Get());
|
|
};
|
|
}
|
|
}
|
|
|
|
void Launcher::handleWindowArrangerMessage(const std::wstring& msg) // WorkspacesArranger IPC thread
|
|
{
|
|
if (msg == L"ready")
|
|
{
|
|
std::thread([&]() { Launch(); }).detach();
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
auto data = WorkspacesData::AppLaunchInfoJSON::FromJson(json::JsonValue::Parse(msg).GetObjectW());
|
|
if (data.has_value())
|
|
{
|
|
m_launchingStatus.Update(data.value().application, data.value().state);
|
|
|
|
{
|
|
std::lock_guard lock(m_uiHelperMutex);
|
|
m_uiHelper->UpdateLaunchStatus(m_launchingStatus.Get());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger::error(L"Failed to parse message from WorkspacesWindowArranger");
|
|
}
|
|
}
|
|
catch (const winrt::hresult_error&)
|
|
{
|
|
Logger::error(L"Failed to parse message from WorkspacesWindowArranger");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Launcher::handleUIMessage(const std::wstring& msg) // UI IPC thread
|
|
{
|
|
if (msg == L"cancel")
|
|
{
|
|
m_launchingStatus.Cancel();
|
|
}
|
|
}
|