mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
[New Module] Workspaces (#34324)
* spell checker
* Adding OOBE Projects page
* changed the default hotkey
* module interface
* rename projects editor
* bug report tool
* installer
* gpo
* exit event constant
* extend search for projects by search over the containing apps' names
* [Projects] fix grammatical issue #43 (1 app - many apps)
* [Projects] Editor: Main page: fix layout if there are many apps, launch button not disappearing on the right side
* dsc
* github
* pipeline
* guid prefix
* [Projects] fixing general settings gpo handling in runner + minor changes
* arm build fix
* Do not allow saving project if name or applist is empty. Also minor UI changes
* version
* editor version
* spellcheck
* editor dll signing
* update projects names to filter them out
* shortcut saving fix
* [Projects] Editor: brining the highlighted app's icon into the foreground. + minor UI fixes
* spell checker
* spellcheck
* [Projects] re-implementing icon size calculation to have similar sized icons for every app.
* [projects] Adding info message for cases: there are no projects or no results for the search
* [Projects] Adding Edit button to the popup. + minor changes
* [Projects] Making popup having rounded corners
* changed "no projects" text color and position
* remove opening the first proj
* fix placing windows of the same app in the project
* [Projects] bringing back the breadcrumb on the editor page. Make it clickable.
* [Projects] optimizing click handlers
* [Projects] Removing not selected apps on save
* moved on thread executor to common
* moved display utils
* added convert rect
* unsigned monitor number
* set awareness
* app placement
* [Projects] Re-implementing preview drawing - one common image
* [Projects] fix boundary calculation, use DPI aware values
* fix launching with command line args
* Fix ARM64 CI build
* launch packaged apps using names when possible
* spell-check
* update packaged apps path
* projects editor single instance
* [Projects] Add Select all checkbox, Delete selected button
* Add Checkbox for per monitor selection
* modifying highlight in preview
* spell checker
* logs
* exclude help windows
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/49
* Add intermediate step to project creation
* minor bugfix
* mutex fix
* modifying highlight for minimized apps
* Fixing bug: re-draw the preview on app deletion in the editor
* Adding helper class for getting the right bounds for screens
* spell checker
* spell checker
* Minor fixes in the capture dialog
* get dpi unaware screen bounds
* refactoring: added utils
* changed window filter
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/2
* clean up
* refactoring
* projects common lib
* localizable default project prefix
* launcher resources
* clean up
* change snapshot project saving
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
* changed project data
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
* changed project creation save-cancel handles
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
* spell-check
* Remove checkboxes, delete feature
* remove unused from the project
* get command line args in the snapshot
* minimized settings snap fix
* set window property after launching
* FZ: ignore projects launched windows
* Implementing major new features: remove button, position manipulation, arguments, admin, minimized, maximized
* modifying colors
* launcher project filters
* clean up
* Hide Admin checkbox
* hide WIP
* spell-check
* Revert "Hide Admin checkbox"
This reverts commit 3036df9d7f.
* get app elevated property
* Implementing Launch and Edit feature
* fixing: update of listed projects on the main page after hitting save in editor
* Fix for packaged app's icons
* fixing scroll speed issue
* change scroll speed to 15
* launch elevated apps
* minor fixes
* minor fix
* enhancing shortcut handling
* can-launch-elevated check
* projects module interface telemetry
* Implementing store of setting "order by".
* minor string correction
* moved projects data parsing
* telemetry
* add move apps checkbox
* notification about elevated apps
* restart unelevated
* move existing windows
* keep opened windows at the same positions
* handle powertoys settings
* use common theme
* fix corrupted data: project id and monitor id
* project launch on "launch and edit"
* clean up
* show screen numbers instead of monitor names
* launcher error messages
* fix default shortcut
* Adding launch button to projects settings, dashboard and flyout
* Adding new app which is launched when launching a project. It shows the status of the launch process
* spell checker
* Renaming Projects to App Layouts. Replacing only string values, not the variable names
* Re-ordering modules after Renaming Projects + spell checker
* setting window size according to the screen (making it bigger)
* commenting out feature "move apps if exist"
* spell checker
* Add ProjectsLauncherUI to signing
* opening apps in minimized state which are placed on a monitor, which is not found at the moment of launching
* consistent file name
* removed unused sln
* telemetry: create event
* WindowPosition comparison
* telemetry: edit event
* fix muted Launch as admin checkbox
* telemetry: delete event
* updated Edit telemetry event
* added invoke point to launcher args
* added utils
* parse invoke point
* replaced tuple with struct
* telemetry: launch event
* MonitorRect comparison
* resources
* rename: folders
* remove outdated
* rename: window property
* rename: files and folders
* rename: common data structures
* rename: telemetry namespace
* rename: workspaces data
* rename ProjectsLib -> WorkspacesLib
* rename: gpo
* rename: settings
* rename: launcher UI
* rename: other
* rename: pt run
* rename: fz
* rename: module interface
* rename: icon
* rename: snapshot tool
* rename: editor
* rename: common files
* rename: launcher
* rename: editor resources
* fix empty file crash
* rename: json
* rename: module interface
* fix custom actions build
* added launch editor event constant
* xaml formatting
* Add missing method defition to interop::Constants idl
Remove Any CPU config
* more .sln cleanup
* [Run][PowerToys] Fix Workspaces utility (#34336)
polished workspaces utility
* build fix - align CppWinRT version
* address PR comment: fix isdigit
* indentation
* address PR comment: rename function
* address PR comment: changed version for workspaces and revision
* added supported version definition
* addressPR comment: use BringToForeground
* address PR comments: updated projects
* address PR comment: uncomment gpo in settings
* address PR comment: rename oobe view
* update OOBE image with current module name
* moved AppUtils
* launching with AppUserModel.ID
* fixed module order in settings
* fix xaml formatting
* [Workspaces] Close launcher if there are failed launches. Plus adding new spinner gif
* fix topmost LauncherUI
* clean up
* UI closing
* BugReportTool - omit cmd arg data
* Delete icon on workspace removal
* Adding cancellation to launcher UI.
* reordered launching
* fix terminating UI
* Removing old shortcut on workspace renaming
* Sentence case labels
* get process path without waiting
* comment out unused
* remove unused argument
* logs
* New icon
* fix launch and edit for the new project
* fix launch and edit: save new project
* Update exe icons
---------
Co-authored-by: donlaci <laszlo@janeasystems.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Stefan Markovic <stefan@janeasystems.com>
Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>
Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
479
src/modules/Workspaces/WorkspacesLauncher/AppLauncher.cpp
Normal file
479
src/modules/Workspaces/WorkspacesLauncher/AppLauncher.cpp
Normal file
@@ -0,0 +1,479 @@
|
||||
#include "pch.h"
|
||||
#include "AppLauncher.h"
|
||||
|
||||
#include <winrt/Windows.Management.Deployment.h>
|
||||
#include <winrt/Windows.ApplicationModel.Core.h>
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <ShellScalingApi.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <workspaces-common/MonitorEnumerator.h>
|
||||
#include <workspaces-common/WindowEnumerator.h>
|
||||
#include <workspaces-common/WindowFilter.h>
|
||||
|
||||
#include <WorkspacesLib/AppUtils.h>
|
||||
|
||||
#include <common/Display/dpi_aware.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
|
||||
#include <LaunchingApp.h>
|
||||
#include <LauncherUIHelper.h>
|
||||
#include <RegistryUtils.h>
|
||||
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Management::Deployment;
|
||||
|
||||
namespace FancyZones
|
||||
{
|
||||
inline bool allMonitorsHaveSameDpiScaling()
|
||||
{
|
||||
auto monitors = MonitorEnumerator::Enumerate();
|
||||
if (monitors.size() < 2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
UINT firstMonitorDpiX;
|
||||
UINT firstMonitorDpiY;
|
||||
|
||||
if (S_OK != GetDpiForMonitor(monitors[0].first, MDT_EFFECTIVE_DPI, &firstMonitorDpiX, &firstMonitorDpiY))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 1; i < monitors.size(); i++)
|
||||
{
|
||||
UINT iteratedMonitorDpiX;
|
||||
UINT iteratedMonitorDpiY;
|
||||
|
||||
if (S_OK != GetDpiForMonitor(monitors[i].first, MDT_EFFECTIVE_DPI, &iteratedMonitorDpiX, &iteratedMonitorDpiY) ||
|
||||
iteratedMonitorDpiX != firstMonitorDpiX)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void ScreenToWorkAreaCoords(HWND window, HMONITOR monitor, RECT& rect)
|
||||
{
|
||||
MONITORINFOEXW monitorInfo{ sizeof(MONITORINFOEXW) };
|
||||
GetMonitorInfoW(monitor, &monitorInfo);
|
||||
|
||||
auto xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
|
||||
auto yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
|
||||
|
||||
DPIAware::Convert(monitor, rect);
|
||||
|
||||
auto referenceRect = RECT(rect.left - xOffset, rect.top - yOffset, rect.right - xOffset, rect.bottom - yOffset);
|
||||
|
||||
// Now, this rect should be used to determine the monitor and thus taskbar size. This fixes
|
||||
// scenarios where the zone lies approximately between two monitors, and the taskbar is on the left.
|
||||
monitor = MonitorFromRect(&referenceRect, MONITOR_DEFAULTTOPRIMARY);
|
||||
GetMonitorInfoW(monitor, &monitorInfo);
|
||||
|
||||
xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
|
||||
yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
|
||||
|
||||
rect.left -= xOffset;
|
||||
rect.right -= xOffset;
|
||||
rect.top -= yOffset;
|
||||
rect.bottom -= yOffset;
|
||||
}
|
||||
|
||||
inline bool SizeWindowToRect(HWND window, HMONITOR monitor, bool isMinimized, bool isMaximized, RECT rect) noexcept
|
||||
{
|
||||
WINDOWPLACEMENT placement{};
|
||||
::GetWindowPlacement(window, &placement);
|
||||
|
||||
if (isMinimized)
|
||||
{
|
||||
placement.showCmd = SW_MINIMIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
|
||||
(placement.showCmd != SW_MINIMIZE))
|
||||
{
|
||||
if (placement.showCmd == SW_SHOWMAXIMIZED)
|
||||
placement.flags &= ~WPF_RESTORETOMAXIMIZED;
|
||||
|
||||
placement.showCmd = SW_RESTORE;
|
||||
}
|
||||
|
||||
ScreenToWorkAreaCoords(window, monitor, rect);
|
||||
placement.rcNormalPosition = rect;
|
||||
}
|
||||
|
||||
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
|
||||
|
||||
auto result = ::SetWindowPlacement(window, &placement);
|
||||
if (!result)
|
||||
{
|
||||
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure window is moved to the correct monitor before maximize.
|
||||
if (isMaximized)
|
||||
{
|
||||
placement.showCmd = SW_SHOWMAXIMIZED;
|
||||
}
|
||||
|
||||
// Do it again, allowing Windows to resize the window and set correct scaling
|
||||
// This fixes Issue #365
|
||||
result = ::SetWindowPlacement(window, &placement);
|
||||
if (!result)
|
||||
{
|
||||
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
LaunchingApps Prepare(std::vector<WorkspacesData::WorkspacesProject::Application>& apps, const Utils::Apps::AppList& installedApps)
|
||||
{
|
||||
LaunchingApps launchedApps{};
|
||||
launchedApps.reserve(apps.size());
|
||||
|
||||
for (auto& app : apps)
|
||||
{
|
||||
// Packaged apps have version in the path, it will be outdated after update.
|
||||
// We need make sure the current package is up to date.
|
||||
if (!app.packageFullName.empty())
|
||||
{
|
||||
auto installedApp = std::find_if(installedApps.begin(), installedApps.end(), [&](const Utils::Apps::AppData& val) { return val.name == app.name; });
|
||||
if (installedApp != installedApps.end() && app.packageFullName != installedApp->packageFullName)
|
||||
{
|
||||
std::wstring exeFileName = app.path.substr(app.path.find_last_of(L"\\") + 1);
|
||||
app.packageFullName = installedApp->packageFullName;
|
||||
app.path = installedApp->installPath + L"\\" + exeFileName;
|
||||
Logger::trace(L"Updated package full name for {}: {}", app.name, app.packageFullName);
|
||||
}
|
||||
}
|
||||
|
||||
launchedApps.push_back({ app, nullptr, L"waiting" });
|
||||
}
|
||||
|
||||
return launchedApps;
|
||||
}
|
||||
|
||||
bool AllWindowsFound(const LaunchingApps& launchedApps)
|
||||
{
|
||||
return std::find_if(launchedApps.begin(), launchedApps.end(), [&](const LaunchingApp& val) {
|
||||
return val.window == nullptr;
|
||||
}) == launchedApps.end();
|
||||
};
|
||||
|
||||
bool AddOpenedWindows(LaunchingApps& launchedApps, const std::vector<HWND>& windows, const Utils::Apps::AppList& installedApps)
|
||||
{
|
||||
bool statusChanged = false;
|
||||
for (HWND window : windows)
|
||||
{
|
||||
auto installedAppData = Utils::Apps::GetApp(window, installedApps);
|
||||
if (!installedAppData.has_value())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto insertionIter = launchedApps.end();
|
||||
for (auto iter = launchedApps.begin(); iter != launchedApps.end(); ++iter)
|
||||
{
|
||||
if (iter->window == nullptr && installedAppData.value().name == iter->application.name)
|
||||
{
|
||||
insertionIter = iter;
|
||||
}
|
||||
|
||||
// keep the window at the same position if it's already opened
|
||||
WINDOWPLACEMENT placement{};
|
||||
::GetWindowPlacement(window, &placement);
|
||||
HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
|
||||
UINT dpi = DPIAware::DEFAULT_DPI;
|
||||
DPIAware::GetScreenDPIForMonitor(monitor, dpi);
|
||||
|
||||
float x = static_cast<float>(placement.rcNormalPosition.left);
|
||||
float y = static_cast<float>(placement.rcNormalPosition.top);
|
||||
float width = static_cast<float>(placement.rcNormalPosition.right - placement.rcNormalPosition.left);
|
||||
float height = static_cast<float>(placement.rcNormalPosition.bottom - placement.rcNormalPosition.top);
|
||||
|
||||
DPIAware::InverseConvert(monitor, x, y);
|
||||
DPIAware::InverseConvert(monitor, width, height);
|
||||
|
||||
WorkspacesData::WorkspacesProject::Application::Position windowPosition{
|
||||
.x = static_cast<int>(std::round(x)),
|
||||
.y = static_cast<int>(std::round(y)),
|
||||
.width = static_cast<int>(std::round(width)),
|
||||
.height = static_cast<int>(std::round(height)),
|
||||
};
|
||||
if (iter->application.position == windowPosition)
|
||||
{
|
||||
Logger::debug(L"{} window already found at {} {}.", iter->application.name, iter->application.position.x, iter->application.position.y);
|
||||
insertionIter = iter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertionIter != launchedApps.end())
|
||||
{
|
||||
insertionIter->window = window;
|
||||
insertionIter->state = L"launched";
|
||||
statusChanged = true;
|
||||
}
|
||||
|
||||
if (AllWindowsFound(launchedApps))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return statusChanged;
|
||||
}
|
||||
}
|
||||
|
||||
bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated, ErrorList& launchErrors)
|
||||
{
|
||||
SHELLEXECUTEINFO sei = { 0 };
|
||||
sei.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||
sei.hwnd = nullptr;
|
||||
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
|
||||
sei.lpVerb = elevated ? L"runas" : L"open";
|
||||
sei.lpFile = appPath.c_str();
|
||||
sei.lpParameters = commandLineArgs.c_str();
|
||||
sei.lpDirectory = nullptr;
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
|
||||
if (!ShellExecuteEx(&sei))
|
||||
{
|
||||
auto error = GetLastError();
|
||||
Logger::error(L"Failed to launch process. {}", get_last_error_or_default(error));
|
||||
launchErrors.push_back({ std::filesystem::path(appPath).filename(), get_last_error_or_default(error) });
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LaunchPackagedApp(const std::wstring& packageFullName, ErrorList& launchErrors)
|
||||
{
|
||||
try
|
||||
{
|
||||
PackageManager packageManager;
|
||||
for (const auto& package : packageManager.FindPackagesForUser({}))
|
||||
{
|
||||
if (package.Id().FullName() == packageFullName)
|
||||
{
|
||||
auto getAppListEntriesOperation = package.GetAppListEntriesAsync();
|
||||
auto appEntries = getAppListEntriesOperation.get();
|
||||
|
||||
if (appEntries.Size() > 0)
|
||||
{
|
||||
IAsyncOperation<bool> launchOperation = appEntries.GetAt(0).LaunchAsync();
|
||||
bool launchResult = launchOperation.get();
|
||||
return launchResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"No app entries found for the package.");
|
||||
launchErrors.push_back({ packageFullName, L"No app entries found for the package." });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const hresult_error& ex)
|
||||
{
|
||||
Logger::error(L"Packaged app launching error: {}", ex.message());
|
||||
launchErrors.push_back({ packageFullName, ex.message().c_str() });
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Launch(const WorkspacesData::WorkspacesProject::Application& app, ErrorList& launchErrors)
|
||||
{
|
||||
bool launched{ false };
|
||||
|
||||
// packaged apps: check protocol in registry
|
||||
// usage example: Settings with cmd args
|
||||
if (!app.packageFullName.empty())
|
||||
{
|
||||
auto names = RegistryUtils::GetUriProtocolNames(app.packageFullName);
|
||||
if (!names.empty())
|
||||
{
|
||||
Logger::trace(L"Launching packaged by protocol with command line args {}", app.name);
|
||||
|
||||
std::wstring uriProtocolName = names[0];
|
||||
std::wstring command = std::wstring(uriProtocolName + (app.commandLineArgs.starts_with(L":") ? L"" : L":") + app.commandLineArgs);
|
||||
|
||||
launched = LaunchApp(command, L"", app.isElevated, launchErrors);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info(L"Uri protocol names not found for {}", app.packageFullName);
|
||||
}
|
||||
}
|
||||
|
||||
// packaged apps: try launching first by AppUserModel.ID
|
||||
// usage example: elevated Terminal
|
||||
if (!launched && !app.appUserModelId.empty() && !app.packageFullName.empty())
|
||||
{
|
||||
Logger::trace(L"Launching {} as {}", app.name, app.appUserModelId);
|
||||
launched = LaunchApp(L"shell:AppsFolder\\" + app.appUserModelId, app.commandLineArgs, app.isElevated, launchErrors);
|
||||
}
|
||||
|
||||
// packaged apps: try launching by package full name
|
||||
// doesn't work for elevated apps or apps with command line args
|
||||
if (!launched && !app.packageFullName.empty() && app.commandLineArgs.empty() && !app.isElevated)
|
||||
{
|
||||
Logger::trace(L"Launching packaged app {}", app.name);
|
||||
launched = LaunchPackagedApp(app.packageFullName, launchErrors);
|
||||
}
|
||||
|
||||
if (!launched)
|
||||
{
|
||||
Logger::trace(L"Launching {} at {}", app.name, app.path);
|
||||
|
||||
DWORD dwAttrib = GetFileAttributesW(app.path.c_str());
|
||||
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
Logger::error(L"File not found at {}", app.path);
|
||||
launchErrors.push_back({ std::filesystem::path(app.path).filename(), L"File not found" });
|
||||
return false;
|
||||
}
|
||||
|
||||
launched = LaunchApp(app.path, app.commandLineArgs, app.isElevated, launchErrors);
|
||||
}
|
||||
|
||||
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path);
|
||||
return launched;
|
||||
}
|
||||
|
||||
bool Launch(WorkspacesData::WorkspacesProject& project, const std::vector<WorkspacesData::WorkspacesProject::Monitor>& monitors, ErrorList& launchErrors)
|
||||
{
|
||||
bool launchedSuccessfully{ true };
|
||||
|
||||
LauncherUIHelper uiHelper;
|
||||
uiHelper.LaunchUI();
|
||||
|
||||
// Get the set of windows before launching the app
|
||||
std::vector<HWND> windowsBefore = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
||||
auto installedApps = Utils::Apps::GetAppsList();
|
||||
auto launchedApps = Prepare(project.apps, installedApps);
|
||||
|
||||
uiHelper.UpdateLaunchStatus(launchedApps);
|
||||
|
||||
// Launch apps
|
||||
for (auto& app : launchedApps)
|
||||
{
|
||||
if (!app.window)
|
||||
{
|
||||
if (!Launch(app.application, launchErrors))
|
||||
{
|
||||
Logger::error(L"Failed to launch {}", app.application.name);
|
||||
app.state = L"failed";
|
||||
uiHelper.UpdateLaunchStatus(launchedApps);
|
||||
launchedSuccessfully = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get newly opened windows after launching apps, keep retrying for 5 seconds
|
||||
Logger::trace(L"Find new windows");
|
||||
for (int attempt = 0; attempt < 50 && !AllWindowsFound(launchedApps); attempt++)
|
||||
{
|
||||
std::vector<HWND> windowsAfter = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
||||
std::vector<HWND> windowsDiff{};
|
||||
std::copy_if(windowsAfter.begin(), windowsAfter.end(), std::back_inserter(windowsDiff), [&](HWND window) { return std::find(windowsBefore.begin(), windowsBefore.end(), window) == windowsBefore.end(); });
|
||||
if (AddOpenedWindows(launchedApps, windowsDiff, installedApps))
|
||||
{
|
||||
uiHelper.UpdateLaunchStatus(launchedApps);
|
||||
}
|
||||
|
||||
// check if all windows were found
|
||||
if (AllWindowsFound(launchedApps))
|
||||
{
|
||||
Logger::trace(L"All windows found.");
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"Not all windows found, retry.");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
|
||||
// Check single-instance app windows
|
||||
Logger::trace(L"Find single-instance app windows");
|
||||
if (!AllWindowsFound(launchedApps))
|
||||
{
|
||||
if (AddOpenedWindows(launchedApps, WindowEnumerator::Enumerate(WindowFilter::Filter), installedApps))
|
||||
{
|
||||
uiHelper.UpdateLaunchStatus(launchedApps);
|
||||
}
|
||||
}
|
||||
|
||||
// Place windows
|
||||
for (const auto& [app, window, status] : launchedApps)
|
||||
{
|
||||
if (window == nullptr)
|
||||
{
|
||||
Logger::warn(L"{} window not found.", app.name);
|
||||
launchedSuccessfully = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto snapMonitorIter = std::find_if(project.monitors.begin(), project.monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.number == app.monitor; });
|
||||
if (snapMonitorIter == project.monitors.end())
|
||||
{
|
||||
Logger::error(L"No monitor saved for launching the app");
|
||||
continue;
|
||||
}
|
||||
|
||||
bool launchMinimized = app.isMinimized;
|
||||
bool launchMaximized = app.isMaximized;
|
||||
|
||||
HMONITOR currentMonitor{};
|
||||
UINT currentDpi = DPIAware::DEFAULT_DPI;
|
||||
auto currentMonitorIter = std::find_if(monitors.begin(), monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.number == app.monitor; });
|
||||
if (currentMonitorIter != monitors.end())
|
||||
{
|
||||
currentMonitor = currentMonitorIter->monitor;
|
||||
currentDpi = currentMonitorIter->dpi;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentMonitor = MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY);
|
||||
DPIAware::GetScreenDPIForMonitor(currentMonitor, currentDpi);
|
||||
launchMinimized = true;
|
||||
launchMaximized = false;
|
||||
|
||||
}
|
||||
|
||||
RECT rect = app.position.toRect();
|
||||
float mult = static_cast<float>(snapMonitorIter->dpi) / currentDpi;
|
||||
rect.left = static_cast<long>(std::round(rect.left * mult));
|
||||
rect.right = static_cast<long>(std::round(rect.right * mult));
|
||||
rect.top = static_cast<long>(std::round(rect.top * mult));
|
||||
rect.bottom = static_cast<long>(std::round(rect.bottom * mult));
|
||||
|
||||
if (FancyZones::SizeWindowToRect(window, currentMonitor, launchMinimized, launchMaximized, rect))
|
||||
{
|
||||
WorkspacesWindowProperties::StampWorkspacesLaunchedProperty(window);
|
||||
Logger::trace(L"Placed {} to ({},{}) [{}x{}]", app.name, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Failed placing {}", app.name);
|
||||
launchedSuccessfully = false;
|
||||
}
|
||||
}
|
||||
|
||||
return launchedSuccessfully;
|
||||
}
|
||||
7
src/modules/Workspaces/WorkspacesLauncher/AppLauncher.h
Normal file
7
src/modules/Workspaces/WorkspacesLauncher/AppLauncher.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <WorkspacesLib/WorkspacesData.h>
|
||||
|
||||
using ErrorList = std::vector<std::pair<std::wstring, std::wstring>>;
|
||||
|
||||
bool Launch(WorkspacesData::WorkspacesProject& project, const std::vector<WorkspacesData::WorkspacesProject::Monitor>& monitors, ErrorList& launchErrors);
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "pch.h"
|
||||
#include "LauncherUIHelper.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <common/utils/OnThreadExecutor.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
|
||||
LauncherUIHelper::~LauncherUIHelper()
|
||||
{
|
||||
OnThreadExecutor().submit(OnThreadExecutor::task_t{ [&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
HANDLE uiProcess = OpenProcess(PROCESS_ALL_ACCESS, false, uiProcessId);
|
||||
if (uiProcess)
|
||||
{
|
||||
bool res = TerminateProcess(uiProcess, 0);
|
||||
if (!res)
|
||||
{
|
||||
Logger::error(L"Unable to terminate UI process: {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Unable to find UI process: {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
std::filesystem::remove(WorkspacesData::LaunchWorkspacesFile());
|
||||
} }).wait();
|
||||
}
|
||||
|
||||
void LauncherUIHelper::LaunchUI()
|
||||
{
|
||||
Logger::trace(L"Starting WorkspacesLauncherUI");
|
||||
|
||||
STARTUPINFO info = { sizeof(info) };
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
TCHAR buffer[MAX_PATH] = { 0 };
|
||||
GetModuleFileName(NULL, buffer, MAX_PATH);
|
||||
std::wstring path = std::filesystem::path(buffer).parent_path();
|
||||
path.append(L"\\PowerToys.WorkspacesLauncherUI.exe");
|
||||
auto succeeded = CreateProcessW(path.c_str(), nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &info, &pi);
|
||||
if (succeeded)
|
||||
{
|
||||
if (pi.hProcess)
|
||||
{
|
||||
uiProcessId = pi.dwProcessId;
|
||||
CloseHandle(pi.hProcess);
|
||||
}
|
||||
if (pi.hThread)
|
||||
{
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"CreateProcessW() failed. {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherUIHelper::UpdateLaunchStatus(LaunchingApps launchedApps)
|
||||
{
|
||||
WorkspacesData::AppLaunchData appData = WorkspacesData::AppLaunchData();
|
||||
appData.appLaunchInfoList.reserve(launchedApps.size());
|
||||
appData.launcherProcessID = GetCurrentProcessId();
|
||||
for (auto& app : launchedApps)
|
||||
{
|
||||
WorkspacesData::AppLaunchInfo appLaunchInfo = WorkspacesData::AppLaunchInfo();
|
||||
appLaunchInfo.name = app.application.name;
|
||||
appLaunchInfo.path = app.application.path;
|
||||
appLaunchInfo.state = app.state;
|
||||
|
||||
appData.appLaunchInfoList.push_back(appLaunchInfo);
|
||||
}
|
||||
|
||||
json::to_file(WorkspacesData::LaunchWorkspacesFile(), WorkspacesData::AppLaunchDataJSON::ToJson(appData));
|
||||
}
|
||||
16
src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.h
Normal file
16
src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <LaunchingApp.h>
|
||||
|
||||
class LauncherUIHelper
|
||||
{
|
||||
public:
|
||||
LauncherUIHelper() = default;
|
||||
~LauncherUIHelper();
|
||||
|
||||
void LaunchUI();
|
||||
void UpdateLaunchStatus(LaunchingApps launchedApps);
|
||||
|
||||
private:
|
||||
DWORD uiProcessId;
|
||||
};
|
||||
13
src/modules/Workspaces/WorkspacesLauncher/LaunchingApp.h
Normal file
13
src/modules/Workspaces/WorkspacesLauncher/LaunchingApp.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <WorkspacesLib/WorkspacesData.h>
|
||||
|
||||
struct LaunchingApp
|
||||
{
|
||||
WorkspacesData::WorkspacesProject::Application application;
|
||||
HWND window;
|
||||
std::wstring state;
|
||||
};
|
||||
|
||||
using LaunchingApps = std::vector<LaunchingApp>;
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Label="PropertySheets" />
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<!--
|
||||
To customize common C++/WinRT project properties:
|
||||
* right-click the project node
|
||||
* expand the Common Properties item
|
||||
* select the C++/WinRT property page
|
||||
|
||||
For more advanced scenarios, and complete documentation, please see:
|
||||
https://github.com/Microsoft/cppwinrt/tree/master/nuget
|
||||
-->
|
||||
<PropertyGroup />
|
||||
<ItemDefinitionGroup />
|
||||
</Project>
|
||||
84
src/modules/Workspaces/WorkspacesLauncher/RegistryUtils.cpp
Normal file
84
src/modules/Workspaces/WorkspacesLauncher/RegistryUtils.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "pch.h"
|
||||
#include "RegistryUtils.h"
|
||||
|
||||
#include <strsafe.h>
|
||||
|
||||
#include <common/utils/winapi_error.h>
|
||||
|
||||
namespace RegistryUtils
|
||||
{
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const wchar_t RegKeyPackageId[] = L"Extensions\\ContractId\\Windows.Protocol\\PackageId\\";
|
||||
const wchar_t RegKeyPackageActivatableClassId[] = L"\\ActivatableClassId";
|
||||
const wchar_t RegKeyPackageCustomProperties[] = L"\\CustomProperties";
|
||||
const wchar_t RegValueName[] = L"Name";
|
||||
}
|
||||
|
||||
HKEY OpenRootRegKey(const wchar_t* key)
|
||||
{
|
||||
HKEY hKey{ nullptr };
|
||||
if (RegOpenKeyEx(HKEY_CLASSES_ROOT, key, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
return hKey;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::wstring> GetUriProtocolNames(const std::wstring& packageFullPath)
|
||||
{
|
||||
std::vector<std::wstring> names{};
|
||||
|
||||
std::wstring keyPath = std::wstring(NonLocalizable::RegKeyPackageId) + packageFullPath + std::wstring(NonLocalizable::RegKeyPackageActivatableClassId);
|
||||
HKEY key = OpenRootRegKey(keyPath.c_str());
|
||||
if (key != nullptr)
|
||||
{
|
||||
LSTATUS result;
|
||||
|
||||
// iterate over all the subkeys to get the protocol names
|
||||
DWORD index = 0;
|
||||
wchar_t keyName[256];
|
||||
DWORD keyNameSize = sizeof(keyName) / sizeof(keyName[0]);
|
||||
FILETIME lastWriteTime;
|
||||
|
||||
while ((result = RegEnumKeyEx(key, index, keyName, &keyNameSize, NULL, NULL, NULL, &lastWriteTime)) != ERROR_NO_MORE_ITEMS)
|
||||
{
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
std::wstring subkeyPath = std::wstring(keyPath) + L"\\" + std::wstring(keyName, keyNameSize) + std::wstring(NonLocalizable::RegKeyPackageCustomProperties);
|
||||
HKEY subkey = OpenRootRegKey(subkeyPath.c_str());
|
||||
if (subkey != nullptr)
|
||||
{
|
||||
DWORD dataSize;
|
||||
wchar_t value[256];
|
||||
result = RegGetValueW(subkey, nullptr, NonLocalizable::RegValueName, RRF_RT_REG_SZ, nullptr, value, &dataSize);
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
names.emplace_back(std::wstring(value, dataSize / sizeof(wchar_t) - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Failed to query registry value. Error: {}", get_last_error_or_default(result));
|
||||
}
|
||||
|
||||
RegCloseKey(subkey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Failed to enumerate subkey. Error: {}", get_last_error_or_default(result));
|
||||
break;
|
||||
}
|
||||
|
||||
keyNameSize = sizeof(keyName) / sizeof(keyName[0]); // Reset the buffer size
|
||||
++index;
|
||||
}
|
||||
|
||||
// Close the registry key
|
||||
RegCloseKey(key);
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace RegistryUtils
|
||||
{
|
||||
std::vector<std::wstring> GetUriProtocolNames(const std::wstring& packageFullPath);
|
||||
};
|
||||
139
src/modules/Workspaces/WorkspacesLauncher/Resource.resx
Normal file
139
src/modules/Workspaces/WorkspacesLauncher/Resource.resx
Normal file
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Empty_file" xml:space="preserve">
|
||||
<value>File {0} is empty.</value>
|
||||
</data>
|
||||
<data name="File_reading_error" xml:space="preserve">
|
||||
<value>Error reading file {0}.</value>
|
||||
</data>
|
||||
<data name="Incorrect_args" xml:space="preserve">
|
||||
<value>Incorrect command line arguments</value>
|
||||
</data>
|
||||
<data name="Incorrect_file_error" xml:space="preserve">
|
||||
<value>Incorrect {0} file.</value>
|
||||
</data>
|
||||
<data name="Workspaces" xml:space="preserve">
|
||||
<value>Workspaces</value>
|
||||
<comment>Name of the module</comment>
|
||||
</data>
|
||||
<data name="Project_not_found" xml:space="preserve">
|
||||
<value>Workspace {0} not found.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!-- Project configurations -->
|
||||
<!-- Props that should be disabled while building on CI server -->
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
|
||||
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h WorkspacesLauncherResource.base.rc WorkspacesLauncherResource.rc" />
|
||||
</Target>
|
||||
<!-- C++ source compile-specific things for all configurations -->
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
|
||||
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
</Link>
|
||||
<Lib>
|
||||
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- C++ source compile-specific things for Debug/Release configurations -->
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- Global props -->
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{2cac093e-5fcf-4102-9c2c-ac7dd5d9eb96}</ProjectGuid>
|
||||
<RootNamespace>WorkspacesLauncher</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<!-- Props that are constant for both Debug and Release configurations -->
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<SpectreMitigation>Spectre</SpectreMitigation>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<TargetName>PowerToys.$(MSBuildProjectName)</TargetName>
|
||||
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AppLauncher.cpp" />
|
||||
<ClCompile Include="LauncherUIHelper.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RegistryUtils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AppLauncher.h" />
|
||||
<ClInclude Include="LauncherUIHelper.h" />
|
||||
<ClInclude Include="LaunchingApp.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="RegistryUtils.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="utils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Display\Display.vcxproj">
|
||||
<Project>{caba8dfb-823b-4bf2-93ac-3f31984150d9}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\WorkspacesLib\WorkspacesLib.vcxproj">
|
||||
<Project>{b31fcc55-b5a4-4ea7-b414-2dceae6af332}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Generated Files/WorkspacesLauncherResource.rc" />
|
||||
<None Include="WorkspacesLauncherResource.base.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resource.resx">
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AppLauncher.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="RegistryUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="utils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="LauncherUIHelper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="LaunchingApp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AppLauncher.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RegistryUtils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="LauncherUIHelper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="WorkspacesLauncherResource.base.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Generated Files/WorkspacesLauncherResource.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resource.resx">
|
||||
<Filter>Resource Files</Filter>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Binary file not shown.
244
src/modules/Workspaces/WorkspacesLauncher/main.cpp
Normal file
244
src/modules/Workspaces/WorkspacesLauncher/main.cpp
Normal file
@@ -0,0 +1,244 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <WorkspacesLib/WorkspacesData.h>
|
||||
#include <WorkspacesLib/trace.h>
|
||||
|
||||
#include <AppLauncher.h>
|
||||
#include <utils.h>
|
||||
|
||||
#include <Generated Files/resource.h>
|
||||
|
||||
#include <workspaces-common/InvokePoint.h>
|
||||
#include <workspaces-common/MonitorUtils.h>
|
||||
|
||||
#include <common/utils/elevation.h>
|
||||
#include <common/utils/gpo.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/utils/process_path.h>
|
||||
#include <common/utils/UnhandledExceptionHandler.h>
|
||||
#include <common/utils/resources.h>
|
||||
|
||||
const std::wstring moduleName = L"Workspaces\\WorkspacesLauncher";
|
||||
const std::wstring internalPath = L"";
|
||||
|
||||
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cmdShow)
|
||||
{
|
||||
LoggerHelpers::init_logger(moduleName, internalPath, LogSettings::workspacesLauncherLoggerName);
|
||||
InitUnhandledExceptionHandler();
|
||||
|
||||
if (powertoys_gpo::getConfiguredWorkspacesEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
||||
{
|
||||
Logger::warn(L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (is_process_elevated())
|
||||
{
|
||||
Logger::warn("Workspaces Launcher is elevated, restart");
|
||||
|
||||
constexpr DWORD exe_path_size = 0xFFFF;
|
||||
auto exe_path = std::make_unique<wchar_t[]>(exe_path_size);
|
||||
GetModuleFileNameW(nullptr, exe_path.get(), exe_path_size);
|
||||
|
||||
const auto modulePath = get_module_folderpath();
|
||||
|
||||
std::string cmdLineStr(cmdline);
|
||||
std::wstring cmdLineWStr(cmdLineStr.begin(), cmdLineStr.end());
|
||||
|
||||
run_non_elevated(exe_path.get(), cmdLineWStr, nullptr, modulePath.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// COM should be initialized before ShellExecuteEx is called.
|
||||
if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
|
||||
{
|
||||
Logger::error("CoInitializeEx failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
|
||||
std::string cmdLineStr(cmdline);
|
||||
auto cmdArgs = split(cmdLineStr, " ");
|
||||
if (cmdArgs.size() < 1)
|
||||
{
|
||||
Logger::warn("Incorrect command line arguments");
|
||||
MessageBox(NULL, GET_RESOURCE_STRING(IDS_INCORRECT_ARGS).c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::wstring id(cmdArgs[0].begin(), cmdArgs[0].end());
|
||||
if (id.empty())
|
||||
{
|
||||
Logger::warn("Incorrect command line arguments: no workspace id");
|
||||
MessageBox(NULL, GET_RESOURCE_STRING(IDS_INCORRECT_ARGS).c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
|
||||
InvokePoint invokePoint = InvokePoint::EditorButton;
|
||||
if (cmdArgs.size() > 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
invokePoint = static_cast<InvokePoint>(std::stoi(cmdArgs[1]));
|
||||
}
|
||||
catch (std::exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Logger::trace(L"Invoke point: {}", invokePoint);
|
||||
|
||||
// read workspaces
|
||||
std::vector<WorkspacesData::WorkspacesProject> workspaces;
|
||||
WorkspacesData::WorkspacesProject projectToLaunch{};
|
||||
if (invokePoint == InvokePoint::LaunchAndEdit)
|
||||
{
|
||||
// check the temp file in case the project is just created and not saved to the workspaces.json yet
|
||||
if (std::filesystem::exists(WorkspacesData::TempWorkspacesFile()))
|
||||
{
|
||||
try
|
||||
{
|
||||
auto savedWorkspacesJson = json::from_file(WorkspacesData::TempWorkspacesFile());
|
||||
if (savedWorkspacesJson.has_value())
|
||||
{
|
||||
auto savedWorkspaces = WorkspacesData::WorkspacesProjectJSON::FromJson(savedWorkspacesJson.value());
|
||||
if (savedWorkspaces.has_value())
|
||||
{
|
||||
projectToLaunch = savedWorkspaces.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::critical("Incorrect Workspaces file");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::critical("Incorrect Workspaces file");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
catch (std::exception ex)
|
||||
{
|
||||
Logger::critical("Exception on reading Workspaces file: {}", ex.what());
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), WorkspacesData::TempWorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectToLaunch.id.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto savedWorkspacesJson = json::from_file(WorkspacesData::WorkspacesFile());
|
||||
if (savedWorkspacesJson.has_value())
|
||||
{
|
||||
auto savedWorkspaces = WorkspacesData::WorkspacesListJSON::FromJson(savedWorkspacesJson.value());
|
||||
if (savedWorkspaces.has_value())
|
||||
{
|
||||
workspaces = savedWorkspaces.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::critical("Incorrect Workspaces file");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::WorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::critical("Incorrect Workspaces file");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::WorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
catch (std::exception ex)
|
||||
{
|
||||
Logger::critical("Exception on reading Workspaces file: {}", ex.what());
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), WorkspacesData::WorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (workspaces.empty())
|
||||
{
|
||||
Logger::warn("Workspaces file is empty");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_EMPTY_FILE), WorkspacesData::WorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (const auto& proj : workspaces)
|
||||
{
|
||||
if (proj.id == id)
|
||||
{
|
||||
projectToLaunch = proj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectToLaunch.id.empty())
|
||||
{
|
||||
Logger::critical(L"Workspace {} not found", id);
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_PROJECT_NOT_FOUND), id);
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// launch apps
|
||||
Logger::info(L"Launch Workspace {} : {}", projectToLaunch.name, projectToLaunch.id);
|
||||
auto monitors = MonitorUtils::IdentifyMonitors();
|
||||
std::vector<std::pair<std::wstring, std::wstring>> launchErrors{};
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
bool launchedSuccessfully = Launch(projectToLaunch, monitors, launchErrors);
|
||||
|
||||
// update last-launched time
|
||||
if (invokePoint != InvokePoint::LaunchAndEdit)
|
||||
{
|
||||
time_t launchedTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
projectToLaunch.lastLaunchedTime = launchedTime;
|
||||
for (int i = 0; i < workspaces.size(); i++)
|
||||
{
|
||||
if (workspaces[i].id == projectToLaunch.id)
|
||||
{
|
||||
workspaces[i] = projectToLaunch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
json::to_file(WorkspacesData::WorkspacesFile(), WorkspacesData::WorkspacesListJSON::ToJson(workspaces));
|
||||
}
|
||||
|
||||
// telemetry
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> duration = end - start;
|
||||
Logger::trace(L"Launching time: {} s", duration.count());
|
||||
|
||||
bool differentSetup = monitors.size() != projectToLaunch.monitors.size();
|
||||
if (!differentSetup)
|
||||
{
|
||||
for (const auto& monitor : projectToLaunch.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Trace::Workspaces::Launch(launchedSuccessfully, projectToLaunch, invokePoint, duration.count(), differentSetup, launchErrors);
|
||||
|
||||
CoUninitialize();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
|
||||
</packages>
|
||||
1
src/modules/Workspaces/WorkspacesLauncher/pch.cpp
Normal file
1
src/modules/Workspaces/WorkspacesLauncher/pch.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
6
src/modules/Workspaces/WorkspacesLauncher/pch.h
Normal file
6
src/modules/Workspaces/WorkspacesLauncher/pch.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <common/logger/logger.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
13
src/modules/Workspaces/WorkspacesLauncher/resource.base.h
Normal file
13
src/modules/Workspaces/WorkspacesLauncher/resource.base.h
Normal file
@@ -0,0 +1,13 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by ProjectLauncherResource.rc
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "PowerToys Workspaces Launcher"
|
||||
#define INTERNAL_NAME "PowerToys.WorkspacesLauncher"
|
||||
#define ORIGINAL_FILENAME "PowerToys.WorkspacesLauncher.exe"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
20
src/modules/Workspaces/WorkspacesLauncher/utils.h
Normal file
20
src/modules/Workspaces/WorkspacesLauncher/utils.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
std::vector<std::string> split(std::string s, const std::string& delimiter)
|
||||
{
|
||||
std::vector<std::string> tokens;
|
||||
size_t pos = 0;
|
||||
std::string token;
|
||||
while ((pos = s.find(delimiter)) != std::string::npos)
|
||||
{
|
||||
token = s.substr(0, pos);
|
||||
tokens.push_back(token);
|
||||
s.erase(0, pos + delimiter.length());
|
||||
}
|
||||
tokens.push_back(s);
|
||||
|
||||
return tokens;
|
||||
}
|
||||
Reference in New Issue
Block a user