Files
PowerToys/src/modules/Projects/ProjectsLauncher/AppLauncher.cpp

327 lines
10 KiB
C++
Raw Normal View History

2024-05-21 16:55:15 +02:00
#include "pch.h"
#include "AppLauncher.h"
2024-05-27 16:33:56 +02:00
#include <winrt/Windows.Management.Deployment.h>
#include <winrt/Windows.ApplicationModel.Core.h>
2024-05-21 16:55:15 +02:00
2024-05-27 16:33:56 +02:00
#include <iostream>
2024-05-21 16:55:15 +02:00
2024-06-12 19:31:43 +02:00
#include <projects-common/AppUtils.h>
#include <projects-common/MonitorEnumerator.h>
#include <projects-common/WindowEnumerator.h>
#include <projects-common/WindowFilter.h>
#include <common/Display/dpi_aware.h>
2024-06-12 21:45:39 +02:00
#include <common/utils/winapi_error.h>
2024-05-27 16:33:56 +02:00
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Management::Deployment;
2024-05-21 16:55:15 +02:00
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, RECT& rect)
{
// First, find the correct monitor. The monitor cannot be found using the given rect itself, we must first
// translate it to relative workspace coordinates.
HMONITOR monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTOPRIMARY);
MONITORINFOEXW monitorInfo{ sizeof(MONITORINFOEXW) };
GetMonitorInfoW(monitor, &monitorInfo);
auto xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
auto yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
auto referenceRect = rect;
referenceRect.left -= xOffset;
referenceRect.right -= xOffset;
referenceRect.top -= yOffset;
referenceRect.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;
2024-06-12 19:31:43 +02:00
const auto level = DPIAware::GetAwarenessLevel(GetWindowDpiAwarenessContext(window));
const bool accountForUnawareness = level < DPIAware::PER_MONITOR_AWARE;
2024-05-21 16:55:15 +02:00
if (accountForUnawareness && !allMonitorsHaveSameDpiScaling())
{
rect.left = max(monitorInfo.rcMonitor.left, rect.left);
rect.right = min(monitorInfo.rcMonitor.right - xOffset, rect.right);
rect.top = max(monitorInfo.rcMonitor.top, rect.top);
rect.bottom = min(monitorInfo.rcMonitor.bottom - yOffset, rect.bottom);
}
}
2024-06-05 11:28:35 +02:00
inline bool SizeWindowToRect(HWND window, bool isMinimized, bool isMaximized, RECT rect) noexcept
2024-05-21 16:55:15 +02:00
{
WINDOWPLACEMENT placement{};
::GetWindowPlacement(window, &placement);
2024-06-05 11:28:35 +02:00
if (isMinimized)
2024-05-21 16:55:15 +02:00
{
2024-06-05 11:28:35 +02:00
placement.showCmd = SW_MINIMIZE;
2024-05-21 16:55:15 +02:00
}
2024-06-05 11:28:35 +02:00
else
2024-05-21 16:55:15 +02:00
{
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
(placement.showCmd != SW_MINIMIZE))
{
if (placement.showCmd == SW_SHOWMAXIMIZED)
placement.flags &= ~WPF_RESTORETOMAXIMIZED;
placement.showCmd = SW_RESTORE;
}
2024-06-05 11:28:35 +02:00
ScreenToWorkAreaCoords(window, rect);
placement.rcNormalPosition = rect;
}
2024-05-21 16:55:15 +02:00
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
auto result = ::SetWindowPlacement(window, &placement);
if (!result)
{
2024-06-12 21:45:39 +02:00
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
2024-06-05 11:28:35 +02:00
return false;
2024-05-21 16:55:15 +02:00
}
// make sure window is moved to the correct monitor before maximize.
2024-06-05 11:28:35 +02:00
if (isMaximized)
2024-05-21 16:55:15 +02:00
{
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)
{
2024-06-12 21:45:39 +02:00
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
2024-06-05 11:28:35 +02:00
return false;
2024-05-21 16:55:15 +02:00
}
2024-06-05 11:28:35 +02:00
return true;
2024-05-21 16:55:15 +02:00
}
}
2024-06-05 11:28:35 +02:00
bool LaunchApp(const std::wstring& appPath, std::wstring commandLineArgs)
2024-05-21 16:55:15 +02:00
{
2024-06-05 11:28:35 +02:00
STARTUPINFO startupInfo;
ZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
PROCESS_INFORMATION processInfo;
ZeroMemory(&processInfo, sizeof(processInfo));
if (CreateProcess(appPath.c_str(), commandLineArgs.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &processInfo))
{
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
return true;
}
else
{
2024-06-12 21:45:39 +02:00
Logger::error(L"Failed to launch process. {}", get_last_error_or_default(GetLastError()));
2024-06-05 11:28:35 +02:00
}
return false;
2024-05-27 16:33:56 +02:00
}
2024-05-21 16:55:15 +02:00
2024-05-27 16:33:56 +02:00
bool LaunchPackagedApp(const std::wstring& packageFullName)
{
try
2024-05-21 16:55:15 +02:00
{
2024-05-27 16:33:56 +02:00
PackageManager packageManager;
for (const auto& package : packageManager.FindPackagesForUser({}))
2024-05-21 16:55:15 +02:00
{
2024-05-27 16:33:56 +02:00
if (package.Id().FullName() == packageFullName)
2024-05-21 16:55:15 +02:00
{
2024-05-27 16:33:56 +02:00
auto getAppListEntriesOperation = package.GetAppListEntriesAsync();
auto appEntries = getAppListEntriesOperation.get();
2024-05-21 16:55:15 +02:00
2024-05-27 16:33:56 +02:00
if (appEntries.Size() > 0)
2024-05-21 16:55:15 +02:00
{
2024-05-27 16:33:56 +02:00
IAsyncOperation<bool> launchOperation = appEntries.GetAt(0).LaunchAsync();
bool launchResult = launchOperation.get();
return launchResult;
2024-05-21 16:55:15 +02:00
}
else
{
2024-06-12 21:45:39 +02:00
Logger::error(L"No app entries found for the package.");
2024-05-21 16:55:15 +02:00
}
}
}
}
2024-05-27 16:33:56 +02:00
catch (const hresult_error& ex)
2024-05-21 16:55:15 +02:00
{
2024-06-12 21:45:39 +02:00
Logger::error(L"Packaged app launching error: {}", ex.message());
2024-05-21 16:55:15 +02:00
}
2024-05-27 16:33:56 +02:00
return false;
2024-05-21 16:55:15 +02:00
}
2024-05-27 16:33:56 +02:00
bool Launch(const Project::Application& app)
2024-05-21 16:55:15 +02:00
{
2024-06-11 15:43:42 +02:00
bool launched;
2024-05-27 16:33:56 +02:00
if (!app.packageFullName.empty() && app.commandLineArgs.empty())
2024-05-21 16:55:15 +02:00
{
2024-06-12 21:45:39 +02:00
Logger::trace(L"Launching packaged {}", app.name);
2024-06-11 15:43:42 +02:00
launched = LaunchPackagedApp(app.packageFullName);
2024-05-21 16:55:15 +02:00
}
else
{
2024-05-27 16:33:56 +02:00
// TODO: verify app path is up to date.
// Packaged apps have version in the path, it will be outdated after update.
2024-06-12 21:45:39 +02:00
Logger::trace(L"Launching {} at {}", app.name, app.path);
2024-05-21 16:55:15 +02:00
2024-05-27 16:33:56 +02:00
DWORD dwAttrib = GetFileAttributesW(app.path.c_str());
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
{
2024-06-12 21:45:39 +02:00
Logger::error(L"File not found at {}", app.path);
2024-05-27 16:33:56 +02:00
return false;
}
2024-06-11 15:43:42 +02:00
launched = LaunchApp(app.path, app.commandLineArgs);
2024-05-21 16:55:15 +02:00
}
2024-06-12 21:45:39 +02:00
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path);
2024-06-11 15:43:42 +02:00
return launched;
}
2024-06-05 11:28:35 +02:00
2024-06-11 15:43:42 +02:00
void Launch(const Project& project)
{
// Get the set of windows before launching the app
std::vector<HWND> windowsBefore = WindowEnumerator::Enumerate(WindowFilter::Filter);
std::vector<std::pair<Project::Application, HWND>> launchedWindows{};
2024-06-11 15:43:42 +02:00
auto apps = Utils::Apps::GetAppsList();
for (const auto& app : project.apps)
{
if (Launch(app))
{
launchedWindows.push_back({ app, nullptr });
2024-06-11 15:43:42 +02:00
}
}
// Get newly opened windows after launching apps, keep retrying for 5 seconds
for (int attempt = 0; attempt < 50; attempt++)
2024-05-21 16:55:15 +02:00
{
2024-06-05 11:28:35 +02:00
std::vector<HWND> windowsAfter = WindowEnumerator::Enumerate(WindowFilter::Filter);
for (HWND window : windowsAfter)
{
2024-06-11 15:43:42 +02:00
// Find the new window
2024-06-05 11:28:35 +02:00
if (std::find(windowsBefore.begin(), windowsBefore.end(), window) == windowsBefore.end())
{
2024-06-11 15:43:42 +02:00
// find the corresponding app in the list
auto app = Utils::Apps::GetApp(window, apps);
if (!app.has_value())
{
continue;
}
// set the window
auto res = std::find_if(launchedWindows.begin(), launchedWindows.end(), [&](const std::pair<Project::Application, HWND>& val) { return val.first.name == app->name && val.second == nullptr; });
2024-06-11 15:43:42 +02:00
if (res != launchedWindows.end())
{
res->second = window;
}
2024-06-05 11:28:35 +02:00
}
}
2024-06-06 15:19:08 +02:00
2024-06-11 15:43:42 +02:00
// check if all windows were found
auto res = std::find_if(launchedWindows.begin(), launchedWindows.end(), [&](const std::pair<Project::Application, HWND>& val) { return val.second == nullptr; });
if (res == launchedWindows.end())
{
2024-06-12 21:45:39 +02:00
Logger::trace(L"All windows found.");
2024-06-11 15:43:42 +02:00
break;
}
else
{
2024-06-12 21:45:39 +02:00
Logger::trace(L"Not all windows found, retry.");
2024-06-11 15:43:42 +02:00
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
2024-05-21 16:55:15 +02:00
}
2024-06-05 11:28:35 +02:00
2024-06-11 15:43:42 +02:00
// Check single-instance app windows if they were launched before
2024-06-05 11:28:35 +02:00
if (launchedWindows.empty())
2024-05-21 16:55:15 +02:00
{
2024-06-05 16:28:00 +02:00
auto windows = WindowEnumerator::Enumerate(WindowFilter::Filter);
for (HWND window : windows)
{
2024-06-11 15:43:42 +02:00
auto app = Utils::Apps::GetApp(window, apps);
if (!app.has_value())
2024-06-05 16:28:00 +02:00
{
continue;
}
2024-06-11 15:43:42 +02:00
auto res = std::find_if(launchedWindows.begin(), launchedWindows.end(), [&](const std::pair<Project::Application, HWND>& val) { return val.second == nullptr && val.first.name == app->name; });
if (res != launchedWindows.end())
2024-06-05 16:28:00 +02:00
{
2024-06-11 15:43:42 +02:00
res->second = window;
2024-06-05 16:28:00 +02:00
}
2024-06-11 15:43:42 +02:00
}
2024-06-05 16:28:00 +02:00
}
2024-06-11 15:43:42 +02:00
// Place windows
for (const auto& [app, window] : launchedWindows)
2024-06-05 16:28:00 +02:00
{
2024-06-11 15:43:42 +02:00
if (window == nullptr)
{
2024-06-12 21:45:39 +02:00
Logger::warn(L"{} window not found.", app.name);
2024-06-11 15:43:42 +02:00
continue;
}
2024-06-05 11:28:35 +02:00
2024-06-11 15:43:42 +02:00
if (FancyZones::SizeWindowToRect(window, app.isMinimized, app.isMaximized, app.position.toRect()))
{
2024-06-12 21:45:39 +02:00
Logger::trace(L"Placed {}", app.name);
2024-06-11 15:43:42 +02:00
}
else
2024-06-05 11:28:35 +02:00
{
2024-06-12 21:45:39 +02:00
Logger::error(L"Failed placing {}", app.name);
2024-06-05 11:28:35 +02:00
}
}
2024-05-21 16:55:15 +02:00
}