Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects

This commit is contained in:
donlaci
2024-07-17 18:48:44 +02:00
10 changed files with 81 additions and 61 deletions

View File

@@ -33,7 +33,7 @@ namespace ProjectsEditor.Data
public string CommandLineArguments { get; set; }
public bool LaunchesAsAdmin { get; set; }
public bool IsElevated { get; set; }
public bool Minimized { get; set; }

View File

@@ -47,14 +47,14 @@ namespace ProjectsEditor.Models
public string CommandLineArguments { get; set; }
private bool _launchesAsAdmin;
private bool _isElevated;
public bool LaunchesAsAdmin
public bool IsElevated
{
get => _launchesAsAdmin;
get => _isElevated;
set
{
_launchesAsAdmin = value;
_isElevated = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppMainParams)));
}
}
@@ -109,7 +109,7 @@ namespace ProjectsEditor.Models
{
get
{
_appMainParams = _launchesAsAdmin ? Properties.Resources.Admin : string.Empty;
_appMainParams = _isElevated ? Properties.Resources.Admin : string.Empty;
if (!string.IsNullOrWhiteSpace(CommandLineArguments))
{
_appMainParams += (_appMainParams == string.Empty ? string.Empty : " | ") + Properties.Resources.Args + ": " + CommandLineArguments;

View File

@@ -231,7 +231,7 @@ namespace ProjectsEditor.Models
AppTitle = item.AppTitle,
CommandLineArguments = item.CommandLineArguments,
PackageFullName = item.PackageFullName,
LaunchesAsAdmin = item.LaunchesAsAdmin,
IsElevated = item.IsElevated,
Minimized = item.Minimized,
Maximized = item.Maximized,
MonitorNumber = item.MonitorNumber,

View File

@@ -160,11 +160,10 @@
Margin="100 5 0 0">
<CheckBox
Content="{x:Static props:Resources.LaunchAsAdmin}"
IsChecked="{Binding LaunchesAsAdmin, Mode=TwoWay}"
Visibility="Collapsed"
IsChecked="{Binding IsElevated, Mode=TwoWay}"
MinWidth="10"/>
<CheckBox
Margin="0 0 0 0"
Margin="15 0 0 0"
Content="{x:Static props:Resources.Maximized}"
IsChecked="{Binding Maximized, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinWidth="10"

View File

@@ -18,7 +18,7 @@ namespace ProjectsEditor
/// </summary>
public partial class ProjectEditor : Page
{
private const double ScrollSpeed = 20;
private const double ScrollSpeed = 15;
private MainViewModel _mainViewModel;
public ProjectEditor(MainViewModel mainViewModel)

View File

@@ -94,7 +94,7 @@ namespace ProjectsEditor.Utils
PackageFullName = app.PackageFullName,
Parent = newProject,
CommandLineArguments = app.CommandLineArguments,
LaunchesAsAdmin = app.LaunchesAsAdmin,
IsElevated = app.IsElevated,
Maximized = app.Maximized,
Minimized = app.Minimized,
IsNotFound = false,
@@ -149,7 +149,7 @@ namespace ProjectsEditor.Utils
Title = app.AppTitle,
PackageFullName = app.PackageFullName,
CommandLineArguments = app.CommandLineArguments,
LaunchesAsAdmin = app.LaunchesAsAdmin,
IsElevated = app.IsElevated,
Maximized = app.Maximized,
Minimized = app.Minimized,
Position = new ProjectData.ApplicationWrapper.WindowPositionWrapper

View File

@@ -134,53 +134,25 @@ namespace FancyZones
}
}
bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs)
bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
{
STARTUPINFO startupInfo;
ZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
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;
PROCESS_INFORMATION processInfo;
ZeroMemory(&processInfo, sizeof(processInfo));
std::wstring fullCommandLine = L"\"" + appPath + L"\" " + commandLineArgs;
if (CreateProcess(nullptr, fullCommandLine.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &processInfo))
{
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
return true;
}
else
if (!ShellExecuteEx(&sei))
{
Logger::error(L"Failed to launch process. {}", get_last_error_or_default(GetLastError()));
return false;
}
return false;
}
bool LaunchUsingUriProtocolName(const std::wstring& uriProtocolName, const std::wstring& commandLineArgs)
{
std::wstring command = std::wstring(uriProtocolName + (commandLineArgs.starts_with(L":") ? L"" : L":") + commandLineArgs);
HINSTANCE result = ShellExecute(
nullptr,
L"open",
command.c_str(),
nullptr,
nullptr,
SW_SHOWNORMAL
);
// If the function succeeds, it returns a value greater than 32.
if (result > reinterpret_cast<HINSTANCE>(32))
{
return true;
}
else
{
Logger::error(L"Failed to launch process. {}", get_last_error_or_default(GetLastError()));
return false;
}
return true;
}
bool LaunchPackagedApp(const std::wstring& packageFullName)
@@ -219,24 +191,29 @@ bool LaunchPackagedApp(const std::wstring& packageFullName)
bool Launch(const Project::Application& app)
{
bool launched { false };
if (!app.packageFullName.empty() && app.commandLineArgs.empty())
if (!app.packageFullName.empty() && app.commandLineArgs.empty() && !app.isElevated)
{
Logger::trace(L"Launching packaged without command line args {}", app.name);
Logger::trace(L"Launching packaged app {}", app.name);
launched = LaunchPackagedApp(app.packageFullName);
}
else if (!app.packageFullName.empty() && !app.commandLineArgs.empty())
if (!launched && !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);
launched = LaunchUsingUriProtocolName(names[0], app.commandLineArgs);
}
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);
}
else
{
Logger::info(L"Uri protocol names not found for {}", app.packageFullName);
}
}
}
if (!launched)
{
@@ -249,7 +226,7 @@ bool Launch(const Project::Application& app)
return false;
}
launched = LaunchApp(app.path, app.commandLineArgs);
launched = LaunchApp(app.path, app.commandLineArgs, app.isElevated);
}
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path);

View File

@@ -24,6 +24,13 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
return 0;
}
// COM should be initialized before ShellExecuteEx is called.
if (FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)))
{
Logger::error("CoInitializeEx failed");
return 1;
}
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
// read projects
@@ -91,5 +98,6 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
}
json::to_file(JsonUtils::ProjectsFile(), JsonUtils::ProjectsListJSON::ToJson(projects));
CoUninitialize();
return 0;
}

View File

@@ -147,6 +147,32 @@ namespace SnapshotUtils
return commandLineArgs;
}
bool IsProcessElevated(DWORD processID)
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processID);
if (!hProcess)
{
return false;
}
HANDLE hToken = NULL;
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
{
return false;
}
TOKEN_ELEVATION elevation;
DWORD cbSize = sizeof(TOKEN_ELEVATION);
if (!GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &cbSize))
{
CloseHandle(hToken);
return false;
}
CloseHandle(hToken);
return elevation.TokenIsElevated;
}
std::vector<Project::Application> GetApps(const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle)
{
std::vector<Project::Application> apps{};
@@ -219,6 +245,7 @@ namespace SnapshotUtils
.path = processPath,
.packageFullName = data.value().packageFullName,
.commandLineArgs = L"", // GetCommandLineArgs(pid, wbemHelper),
.isElevated = IsProcessElevated(pid),
.isMinimized = WindowUtils::IsMinimized(window),
.isMaximized = WindowUtils::IsMaximized(window),
.position = Project::Application::Position{

View File

@@ -29,6 +29,7 @@ struct Project
std::wstring path;
std::wstring packageFullName;
std::wstring commandLineArgs;
bool isElevated{};
bool isMinimized{};
bool isMaximized{};
Position position{};
@@ -137,6 +138,7 @@ namespace JsonUtils
const static wchar_t* AppPackageFullNameID = L"package-full-name";
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* MinimizedID = L"minimized";
const static wchar_t* MaximizedID = L"maximized";
const static wchar_t* PositionID = L"position";
@@ -151,6 +153,7 @@ namespace JsonUtils
json.SetNamedValue(NonLocalizable::AppTitleID, json::value(data.title));
json.SetNamedValue(NonLocalizable::AppPackageFullNameID, json::value(data.packageFullName));
json.SetNamedValue(NonLocalizable::CommandLineArgsID, json::value(data.commandLineArgs));
json.SetNamedValue(NonLocalizable::ElevatedID, json::value(data.isElevated));
json.SetNamedValue(NonLocalizable::MinimizedID, json::value(data.isMinimized));
json.SetNamedValue(NonLocalizable::MaximizedID, json::value(data.isMaximized));
json.SetNamedValue(NonLocalizable::PositionID, PositionJSON::ToJson(data.position));
@@ -177,6 +180,12 @@ namespace JsonUtils
}
result.commandLineArgs = json.GetNamedString(NonLocalizable::CommandLineArgsID);
if (json.HasKey(NonLocalizable::ElevatedID))
{
result.isElevated = json.GetNamedBoolean(NonLocalizable::ElevatedID);
}
result.isMaximized = json.GetNamedBoolean(NonLocalizable::MaximizedID);
result.isMinimized = json.GetNamedBoolean(NonLocalizable::MinimizedID);
result.monitor = static_cast<int>(json.GetNamedNumber(NonLocalizable::MonitorID));