[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:
Seraphima Zykova
2024-08-23 09:28:13 +02:00
committed by GitHub
parent 2a8e211cfd
commit 579619952d
221 changed files with 12805 additions and 12 deletions

View File

@@ -0,0 +1,475 @@
#include "pch.h"
#include "WorkspacesData.h"
#include <common/SettingsAPI/settings_helpers.h>
namespace NonLocalizable
{
const inline wchar_t ModuleKey[] = L"Workspaces";
}
namespace WorkspacesData
{
std::wstring WorkspacesFile()
{
std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
return settingsFolderPath + L"\\workspaces.json";
}
std::wstring TempWorkspacesFile()
{
std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
return settingsFolderPath + L"\\temp-workspaces.json";
}
std::wstring LaunchWorkspacesFile()
{
std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
return settingsFolderPath + L"\\launch-workspaces.json";
}
RECT WorkspacesProject::Application::Position::toRect() const noexcept
{
return RECT{ .left = x, .top = y, .right = x + width, .bottom = y + height };
}
namespace WorkspacesProjectJSON
{
namespace ApplicationJSON
{
namespace PositionJSON
{
namespace NonLocalizable
{
const static wchar_t* XAxisID = L"X";
const static wchar_t* YAxisID = L"Y";
const static wchar_t* WidthID = L"width";
const static wchar_t* HeightID = L"height";
}
json::JsonObject ToJson(const WorkspacesProject::Application::Position& position)
{
json::JsonObject json{};
json.SetNamedValue(NonLocalizable::XAxisID, json::value(position.x));
json.SetNamedValue(NonLocalizable::YAxisID, json::value(position.y));
json.SetNamedValue(NonLocalizable::WidthID, json::value(position.width));
json.SetNamedValue(NonLocalizable::HeightID, json::value(position.height));
return json;
}
std::optional<WorkspacesProject::Application::Position> FromJson(const json::JsonObject& json)
{
WorkspacesProject::Application::Position result;
try
{
result.x = static_cast<int>(json.GetNamedNumber(NonLocalizable::XAxisID, 0));
result.y = static_cast<int>(json.GetNamedNumber(NonLocalizable::YAxisID, 0));
result.width = static_cast<int>(json.GetNamedNumber(NonLocalizable::WidthID, 0));
result.height = static_cast<int>(json.GetNamedNumber(NonLocalizable::HeightID, 0));
}
catch (const winrt::hresult_error&)
{
return std::nullopt;
}
return result;
}
}
namespace NonLocalizable
{
const static wchar_t* AppNameID = L"application";
const static wchar_t* AppPathID = L"application-path";
const static wchar_t* AppPackageFullNameID = L"package-full-name";
const static wchar_t* AppUserModelId = L"app-user-model-id";
const static wchar_t* AppTitleID = L"title";
const static wchar_t* CommandLineArgsID = L"command-line-arguments";
const static wchar_t* ElevatedID = L"is-elevated";
const static wchar_t* CanLaunchElevatedID = L"can-launch-elevated";
const static wchar_t* MinimizedID = L"minimized";
const static wchar_t* MaximizedID = L"maximized";
const static wchar_t* PositionID = L"position";
const static wchar_t* MonitorID = L"monitor";
}
json::JsonObject ToJson(const WorkspacesProject::Application& data)
{
json::JsonObject json{};
json.SetNamedValue(NonLocalizable::AppNameID, json::value(data.name));
json.SetNamedValue(NonLocalizable::AppPathID, json::value(data.path));
json.SetNamedValue(NonLocalizable::AppTitleID, json::value(data.title));
json.SetNamedValue(NonLocalizable::AppPackageFullNameID, json::value(data.packageFullName));
json.SetNamedValue(NonLocalizable::AppUserModelId, json::value(data.appUserModelId));
json.SetNamedValue(NonLocalizable::CommandLineArgsID, json::value(data.commandLineArgs));
json.SetNamedValue(NonLocalizable::ElevatedID, json::value(data.isElevated));
json.SetNamedValue(NonLocalizable::CanLaunchElevatedID, json::value(data.canLaunchElevated));
json.SetNamedValue(NonLocalizable::MinimizedID, json::value(data.isMinimized));
json.SetNamedValue(NonLocalizable::MaximizedID, json::value(data.isMaximized));
json.SetNamedValue(NonLocalizable::PositionID, PositionJSON::ToJson(data.position));
json.SetNamedValue(NonLocalizable::MonitorID, json::value(data.monitor));
return json;
}
std::optional<WorkspacesProject::Application> FromJson(const json::JsonObject& json)
{
WorkspacesProject::Application result;
try
{
if (json.HasKey(NonLocalizable::AppNameID))
{
result.name = json.GetNamedString(NonLocalizable::AppNameID);
}
result.path = json.GetNamedString(NonLocalizable::AppPathID);
result.title = json.GetNamedString(NonLocalizable::AppTitleID);
if (json.HasKey(NonLocalizable::AppPackageFullNameID))
{
result.packageFullName = json.GetNamedString(NonLocalizable::AppPackageFullNameID);
}
if (json.HasKey(NonLocalizable::AppUserModelId))
{
result.appUserModelId = json.GetNamedString(NonLocalizable::AppUserModelId);
}
result.commandLineArgs = json.GetNamedString(NonLocalizable::CommandLineArgsID);
if (json.HasKey(NonLocalizable::ElevatedID))
{
result.isElevated = json.GetNamedBoolean(NonLocalizable::ElevatedID);
}
if (json.HasKey(NonLocalizable::CanLaunchElevatedID))
{
result.canLaunchElevated = json.GetNamedBoolean(NonLocalizable::CanLaunchElevatedID);
}
result.isMaximized = json.GetNamedBoolean(NonLocalizable::MaximizedID);
result.isMinimized = json.GetNamedBoolean(NonLocalizable::MinimizedID);
result.monitor = static_cast<int>(json.GetNamedNumber(NonLocalizable::MonitorID));
if (json.HasKey(NonLocalizable::PositionID))
{
auto position = PositionJSON::FromJson(json.GetNamedObject(NonLocalizable::PositionID));
if (!position.has_value())
{
return std::nullopt;
}
result.position = position.value();
}
}
catch (const winrt::hresult_error&)
{
return std::nullopt;
}
return result;
}
}
namespace MonitorJSON
{
namespace MonitorRectJSON
{
namespace NonLocalizable
{
const static wchar_t* TopID = L"top";
const static wchar_t* LeftID = L"left";
const static wchar_t* WidthID = L"width";
const static wchar_t* HeightID = L"height";
}
json::JsonObject ToJson(const WorkspacesProject::Monitor::MonitorRect& data)
{
json::JsonObject json{};
json.SetNamedValue(NonLocalizable::TopID, json::value(data.top));
json.SetNamedValue(NonLocalizable::LeftID, json::value(data.left));
json.SetNamedValue(NonLocalizable::WidthID, json::value(data.width));
json.SetNamedValue(NonLocalizable::HeightID, json::value(data.height));
return json;
}
std::optional<WorkspacesProject::Monitor::MonitorRect> FromJson(const json::JsonObject& json)
{
WorkspacesProject::Monitor::MonitorRect result;
try
{
result.top = static_cast<int>(json.GetNamedNumber(NonLocalizable::TopID));
result.left = static_cast<int>(json.GetNamedNumber(NonLocalizable::LeftID));
result.width = static_cast<int>(json.GetNamedNumber(NonLocalizable::WidthID));
result.height = static_cast<int>(json.GetNamedNumber(NonLocalizable::HeightID));
}
catch (const winrt::hresult_error&)
{
return std::nullopt;
}
return result;
}
}
namespace NonLocalizable
{
const static wchar_t* MonitorID = L"id";
const static wchar_t* InstanceID = L"instance-id";
const static wchar_t* NumberID = L"monitor-number";
const static wchar_t* DpiID = L"dpi";
const static wchar_t* MonitorRectDpiAwareID = L"monitor-rect-dpi-aware";
const static wchar_t* MonitorRectDpiUnawareID = L"monitor-rect-dpi-unaware";
}
json::JsonObject ToJson(const WorkspacesProject::Monitor& data)
{
json::JsonObject json{};
json.SetNamedValue(NonLocalizable::MonitorID, json::value(data.id));
json.SetNamedValue(NonLocalizable::InstanceID, json::value(data.instanceId));
json.SetNamedValue(NonLocalizable::NumberID, json::value(data.number));
json.SetNamedValue(NonLocalizable::DpiID, json::value(data.dpi));
json.SetNamedValue(NonLocalizable::MonitorRectDpiAwareID, MonitorRectJSON::ToJson(data.monitorRectDpiAware));
json.SetNamedValue(NonLocalizable::MonitorRectDpiUnawareID, MonitorRectJSON::ToJson(data.monitorRectDpiUnaware));
return json;
}
std::optional<WorkspacesProject::Monitor> FromJson(const json::JsonObject& json)
{
WorkspacesProject::Monitor result;
try
{
result.id = json.GetNamedString(NonLocalizable::MonitorID);
result.instanceId = json.GetNamedString(NonLocalizable::InstanceID);
result.number = static_cast<int>(json.GetNamedNumber(NonLocalizable::NumberID));
result.dpi = static_cast<int>(json.GetNamedNumber(NonLocalizable::DpiID));
auto rectDpiAware = MonitorRectJSON::FromJson(json.GetNamedObject(NonLocalizable::MonitorRectDpiAwareID));
if (!rectDpiAware.has_value())
{
return std::nullopt;
}
auto rectDpiUnaware = MonitorRectJSON::FromJson(json.GetNamedObject(NonLocalizable::MonitorRectDpiUnawareID));
if (!rectDpiUnaware.has_value())
{
return std::nullopt;
}
result.monitorRectDpiAware = rectDpiAware.value();
result.monitorRectDpiUnaware = rectDpiUnaware.value();
}
catch (const winrt::hresult_error&)
{
return std::nullopt;
}
return result;
}
}
namespace NonLocalizable
{
const static wchar_t* IdID = L"id";
const static wchar_t* NameID = L"name";
const static wchar_t* CreationTimeID = L"creation-time";
const static wchar_t* LastLaunchedTimeID = L"last-launched-time";
const static wchar_t* IsShortcutNeededID = L"is-shortcut-needed";
const static wchar_t* MoveExistingWindowsID = L"move-existing-windows";
const static wchar_t* MonitorConfigurationID = L"monitor-configuration";
const static wchar_t* AppsID = L"applications";
}
json::JsonObject ToJson(const WorkspacesProject& data)
{
json::JsonObject json{};
json::JsonArray appsArray{};
for (const auto& app : data.apps)
{
appsArray.Append(ApplicationJSON::ToJson(app));
}
json::JsonArray monitorsArray{};
for (const auto& monitor : data.monitors)
{
monitorsArray.Append(MonitorJSON::ToJson(monitor));
}
json.SetNamedValue(NonLocalizable::IdID, json::value(data.id));
json.SetNamedValue(NonLocalizable::NameID, json::value(data.name));
json.SetNamedValue(NonLocalizable::CreationTimeID, json::value(static_cast<long>(data.creationTime)));
if (data.lastLaunchedTime.has_value())
{
json.SetNamedValue(NonLocalizable::LastLaunchedTimeID, json::value(static_cast<long>(data.lastLaunchedTime.value())));
}
json.SetNamedValue(NonLocalizable::IsShortcutNeededID, json::value(data.isShortcutNeeded));
json.SetNamedValue(NonLocalizable::MoveExistingWindowsID, json::value(data.moveExistingWindows));
json.SetNamedValue(NonLocalizable::MonitorConfigurationID, monitorsArray);
json.SetNamedValue(NonLocalizable::AppsID, appsArray);
return json;
}
std::optional<WorkspacesProject> FromJson(const json::JsonObject& json)
{
WorkspacesProject result{};
try
{
result.id = json.GetNamedString(NonLocalizable::IdID);
result.name = json.GetNamedString(NonLocalizable::NameID);
result.creationTime = static_cast<time_t>(json.GetNamedNumber(NonLocalizable::CreationTimeID));
if (json.HasKey(NonLocalizable::LastLaunchedTimeID))
{
result.lastLaunchedTime = static_cast<time_t>(json.GetNamedNumber(NonLocalizable::LastLaunchedTimeID));
}
if (json.HasKey(NonLocalizable::IsShortcutNeededID))
{
result.isShortcutNeeded = json.GetNamedBoolean(NonLocalizable::IsShortcutNeededID);
}
if (json.HasKey(NonLocalizable::MoveExistingWindowsID))
{
result.moveExistingWindows = json.GetNamedBoolean(NonLocalizable::MoveExistingWindowsID);
}
auto appsArray = json.GetNamedArray(NonLocalizable::AppsID);
for (uint32_t i = 0; i < appsArray.Size(); ++i)
{
auto obj = ApplicationJSON::FromJson(appsArray.GetObjectAt(i));
if (!obj.has_value())
{
return std::nullopt;
}
result.apps.push_back(obj.value());
}
auto monitorsArray = json.GetNamedArray(NonLocalizable::MonitorConfigurationID);
for (uint32_t i = 0; i < monitorsArray.Size(); ++i)
{
auto obj = MonitorJSON::FromJson(monitorsArray.GetObjectAt(i));
if (!obj.has_value())
{
return std::nullopt;
}
result.monitors.push_back(obj.value());
}
}
catch (const winrt::hresult_error&)
{
return std::nullopt;
}
return result;
}
}
namespace WorkspacesListJSON
{
namespace NonLocalizable
{
const static wchar_t* WorkspacesID = L"workspaces";
}
json::JsonObject ToJson(const std::vector<WorkspacesProject>& data)
{
json::JsonObject json{};
json::JsonArray projectsArray{};
for (const auto& project : data)
{
projectsArray.Append(WorkspacesProjectJSON::ToJson(project));
}
json.SetNamedValue(NonLocalizable::WorkspacesID, projectsArray);
return json;
}
std::optional<std::vector<WorkspacesProject>> FromJson(const json::JsonObject& json)
{
std::vector<WorkspacesProject> result{};
try
{
auto array = json.GetNamedArray(NonLocalizable::WorkspacesID);
for (uint32_t i = 0; i < array.Size(); ++i)
{
auto obj = WorkspacesProjectJSON::FromJson(array.GetObjectAt(i));
if (obj.has_value())
{
result.push_back(obj.value());
}
else
{
return std::nullopt;
}
}
}
catch (const winrt::hresult_error&)
{
return std::nullopt;
}
return result;
}
}
namespace AppLaunchInfoJSON
{
namespace NonLocalizable
{
const static wchar_t* NameID = L"name";
const static wchar_t* PathID = L"path";
const static wchar_t* StateID = L"state";
}
json::JsonObject ToJson(const AppLaunchInfo& data)
{
json::JsonObject json{};
json.SetNamedValue(NonLocalizable::NameID, json::value(data.name));
json.SetNamedValue(NonLocalizable::PathID, json::value(data.path));
json.SetNamedValue(NonLocalizable::StateID, json::value(data.state));
return json;
}
}
namespace AppLaunchInfoListJSON
{
namespace NonLocalizable
{
const static wchar_t* AppLaunchInfoID = L"appLaunchInfos";
}
json::JsonObject ToJson(const std::vector<AppLaunchInfo>& data)
{
json::JsonObject json{};
json::JsonArray appLaunchInfoArray{};
for (const auto& appLaunchInfo : data)
{
appLaunchInfoArray.Append(AppLaunchInfoJSON::ToJson(appLaunchInfo));
}
json.SetNamedValue(NonLocalizable::AppLaunchInfoID, appLaunchInfoArray);
return json;
}
}
namespace AppLaunchDataJSON
{
namespace NonLocalizable
{
const static wchar_t* AppsID = L"apps";
const static wchar_t* ProcessID = L"processId";
}
json::JsonObject ToJson(const AppLaunchData& data)
{
json::JsonObject json{};
json.SetNamedValue(NonLocalizable::AppsID, AppLaunchInfoListJSON::ToJson(data.appLaunchInfoList));
json.SetNamedValue(NonLocalizable::ProcessID, json::value(data.launcherProcessID));
return json;
}
}
}