mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 02:06:36 +02:00
[Workspaces] implement the move feature (#35480)
* [Workspaces] Add move functionality * spell checker * [Workspaces] Modify Arranger to move apps without launch * moved ipc helper * removed callback * use LauncherStatus in WindowArranger * wait for launching next app * launch in a separate thread and protect by mutexes * update app version in advance * changed canceling launch * increased waiting time * Fix optional parameter load from json * changed arranger waiting time * additional waiting time for Outlook * added app id * ensure ids before launch * set id in editor * minor updates * [Workspaces] Move: Get the nearest window when moving a window * [Workspaces] convert optional boolean to enum to avoid json problems * Handle case when the new Application Property "moveIfExists" does not exist * Re-implementing app-window pairing for moving feature. * spell checker * XAML formatting * Fixing bug: IPC message not arriving * spell checker * Removing app-level-setting for move app. Also fixed compiler errors due styling. * Updating editor window layout * Re-implementing window positioning UI elements * XAML formatting * Code review findings * Code cleanup * Code cleanup * Code cleanup * code cleanup * Code cleanup * Code cleanup * fix Move attribute after launch and snapshot * Extend WindowArranger with PWA functionality to detect different PWA apps. PwaHelper moved to the common library * fix repeat counter in the editor * Code optimization * code cleanup, optimization * fix double-processing window --------- Co-authored-by: Seraphima <zykovas91@gmail.com> Co-authored-by: donlaci <donlaci@yahoo.com>
This commit is contained in:
@@ -12,9 +12,18 @@
|
||||
#include <workspaces-common/WindowUtils.h>
|
||||
|
||||
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
|
||||
#include <WorkspacesLib/PwaHelper.h>
|
||||
|
||||
namespace FancyZones
|
||||
namespace PlacementHelper
|
||||
{
|
||||
// When calculating the coordinates difference (== 'distance') between 2 windows, there are additional values added to the real distance
|
||||
// if both windows are minimized, the 'distance' is 0, the minimal value, this is the best match, we prefer this 'pairing'
|
||||
// if both are in normal state (non-minimized), we add 1 to the calculated 'distance', this is the 2nd best match
|
||||
// if one window is minimized and the other is maximized, we add a high value (10.000) to the result as
|
||||
// this case is the least desired match, we want this pairing (matching) only if there is no other possibility left
|
||||
const int PlacementDistanceAdditionBothNormal = 1;
|
||||
const int PlacementDistanceAdditionNormalAndMinimized = 10000;
|
||||
|
||||
inline void ScreenToWorkAreaCoords(HWND window, HMONITOR monitor, RECT& rect)
|
||||
{
|
||||
MONITORINFOEXW monitorInfo{ sizeof(MONITORINFOEXW) };
|
||||
@@ -52,14 +61,7 @@ namespace FancyZones
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
|
||||
(placement.showCmd != SW_MINIMIZE))
|
||||
{
|
||||
if (placement.showCmd == SW_SHOWMAXIMIZED)
|
||||
placement.flags &= ~WPF_RESTORETOMAXIMIZED;
|
||||
|
||||
placement.showCmd = SW_RESTORE;
|
||||
}
|
||||
placement.showCmd = SW_RESTORE;
|
||||
|
||||
ScreenToWorkAreaCoords(window, monitor, rect);
|
||||
placement.rcNormalPosition = rect;
|
||||
@@ -91,18 +93,191 @@ namespace FancyZones
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int CalculateDistance(const WorkspacesData::WorkspacesProject::Application& app, HWND window)
|
||||
{
|
||||
WINDOWPLACEMENT placement{};
|
||||
::GetWindowPlacement(window, &placement);
|
||||
|
||||
if (app.isMinimized && (placement.showCmd == SW_SHOWMINIMIZED))
|
||||
{
|
||||
// The most preferred case: both windows are minimized. The 'distance' between these 2 windows is 0, the lowest value
|
||||
return 0;
|
||||
}
|
||||
|
||||
int placementDiffPenalty = PlacementDistanceAdditionBothNormal;
|
||||
if (app.isMinimized || (placement.showCmd == SW_SHOWMINIMIZED))
|
||||
{
|
||||
// The least preferred case: one window is minimized the other one isn't.
|
||||
// We add a high number to the real distance, as we want this 2 windows be matched only if there is no other match
|
||||
placementDiffPenalty = PlacementDistanceAdditionNormalAndMinimized;
|
||||
}
|
||||
|
||||
RECT windowPosition;
|
||||
GetWindowRect(window, &windowPosition);
|
||||
DPIAware::InverseConvert(MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY), windowPosition);
|
||||
|
||||
return placementDiffPenalty + abs(app.position.x - windowPosition.left) + abs(app.position.y - windowPosition.top) + abs(app.position.x + app.position.width - windowPosition.right) + abs(app.position.y + app.position.height - windowPosition.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowArranger::TryMoveWindow(const WorkspacesData::WorkspacesProject::Application& app, HWND windowToMove)
|
||||
{
|
||||
Logger::info(L"The app {} is found at launch, moving it", app.name);
|
||||
auto appState = m_launchingStatus.Get(app);
|
||||
if (!appState.has_value())
|
||||
{
|
||||
Logger::info(L"The app {} is not found in the map of apps", app.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = moveWindow(windowToMove, app);
|
||||
if (success)
|
||||
{
|
||||
m_launchingStatus.Update(appState.value().application, windowToMove, LaunchingState::LaunchedAndMoved);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info(L"Failed to move the existing app {} ", app.name);
|
||||
m_launchingStatus.Update(appState.value().application, windowToMove, LaunchingState::Failed);
|
||||
}
|
||||
|
||||
auto updatedState = m_launchingStatus.Get(app);
|
||||
if (updatedState.has_value())
|
||||
{
|
||||
m_ipcHelper.send(WorkspacesData::AppLaunchInfoJSON::ToJson(updatedState.value()).ToString().c_str());
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
std::optional<WindowWithDistance> WindowArranger::GetNearestWindow(const WorkspacesData::WorkspacesProject::Application& app, const std::vector<HWND>& movedWindows, Utils::PwaHelper& pwaHelper)
|
||||
{
|
||||
std::optional<Utils::Apps::AppData> appDataNearest = std::nullopt;
|
||||
WindowWithDistance nearestWindowWithDistance{};
|
||||
|
||||
for (HWND window : m_windowsBefore)
|
||||
{
|
||||
if (std::find(movedWindows.begin(), movedWindows.end(), window) != movedWindows.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::wstring processPath = get_process_path(window);
|
||||
if (processPath.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DWORD pid{};
|
||||
GetWindowThreadProcessId(window, &pid);
|
||||
|
||||
auto data = Utils::Apps::GetApp(processPath, pid, m_installedApps);
|
||||
if (!data.has_value())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
pwaHelper.UpdatePwaApp(&data.value(), window);
|
||||
|
||||
if ((app.name == data.value().name || app.path == data.value().installPath) && (app.pwaAppId == data.value().pwaAppId))
|
||||
{
|
||||
if (!appDataNearest.has_value())
|
||||
{
|
||||
appDataNearest = data;
|
||||
nearestWindowWithDistance.distance = PlacementHelper::CalculateDistance(app, window);
|
||||
nearestWindowWithDistance.window = window;
|
||||
}
|
||||
else
|
||||
{
|
||||
int currentDistance = PlacementHelper::CalculateDistance(app, window);
|
||||
if (currentDistance < nearestWindowWithDistance.distance)
|
||||
{
|
||||
appDataNearest = data;
|
||||
nearestWindowWithDistance.distance = currentDistance;
|
||||
nearestWindowWithDistance.window = window;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (appDataNearest.has_value())
|
||||
{
|
||||
return nearestWindowWithDistance;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
WindowArranger::WindowArranger(WorkspacesData::WorkspacesProject project) :
|
||||
m_project(project),
|
||||
m_windowsBefore(WindowEnumerator::Enumerate(WindowFilter::Filter)),
|
||||
m_monitors(MonitorUtils::IdentifyMonitors()),
|
||||
m_installedApps(Utils::Apps::GetAppsList()),
|
||||
//m_windowCreationHandler(std::bind(&WindowArranger::onWindowCreated, this, std::placeholders::_1)),
|
||||
m_ipcHelper(IPCHelperStrings::WindowArrangerPipeName, IPCHelperStrings::LauncherArrangerPipeName, std::bind(&WindowArranger::receiveIpcMessage, this, std::placeholders::_1)),
|
||||
m_launchingStatus(m_project)
|
||||
{
|
||||
if (project.moveExistingWindows)
|
||||
{
|
||||
Logger::info(L"Moving existing windows");
|
||||
bool isMovePhase = true;
|
||||
bool movedAny = false;
|
||||
std::vector<HWND> movedWindows;
|
||||
std::vector<WorkspacesData::WorkspacesProject::Application> movedApps;
|
||||
Utils::PwaHelper pwaHelper{};
|
||||
|
||||
while (isMovePhase)
|
||||
{
|
||||
isMovePhase = false;
|
||||
int minDistance = INT_MAX;
|
||||
WorkspacesData::WorkspacesProject::Application appToMove;
|
||||
HWND windowToMove = NULL;
|
||||
for (auto& app : project.apps)
|
||||
{
|
||||
// move the apps which are set to "Move-If-Exists" and are already present (launched, running)
|
||||
if (std::find(movedApps.begin(), movedApps.end(), app) != movedApps.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::optional<WindowWithDistance> nearestWindowWithDistance;
|
||||
nearestWindowWithDistance = GetNearestWindow(app, movedWindows, pwaHelper);
|
||||
if (nearestWindowWithDistance.has_value())
|
||||
{
|
||||
if (nearestWindowWithDistance.value().distance < minDistance)
|
||||
{
|
||||
minDistance = nearestWindowWithDistance.value().distance;
|
||||
appToMove = app;
|
||||
windowToMove = nearestWindowWithDistance.value().window;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info(L"The app {} is not found at launch, cannot be moved, has to be started", app.name);
|
||||
movedApps.push_back(app);
|
||||
}
|
||||
}
|
||||
if (minDistance < INT_MAX)
|
||||
{
|
||||
isMovePhase = true;
|
||||
movedAny = true;
|
||||
bool success = TryMoveWindow(appToMove, windowToMove);
|
||||
movedApps.push_back(appToMove);
|
||||
if (success)
|
||||
{
|
||||
movedWindows.push_back(windowToMove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (movedAny)
|
||||
{
|
||||
// Wait if there were moved windows. This message might not arrive if sending immediately after the last "moved" message (status update)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
Logger::info(L"Finished moving existing windows");
|
||||
}
|
||||
|
||||
m_ipcHelper.send(L"ready");
|
||||
|
||||
const long maxLaunchingWaitingTime = 10000, maxRepositionWaitingTime = 3000, ms = 300;
|
||||
@@ -138,27 +313,17 @@ WindowArranger::WindowArranger(WorkspacesData::WorkspacesProject project) :
|
||||
}
|
||||
}
|
||||
|
||||
//void WindowArranger::onWindowCreated(HWND window)
|
||||
//{
|
||||
// if (!WindowFilter::Filter(window))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// processWindow(window);
|
||||
//}
|
||||
|
||||
void WindowArranger::processWindows(bool processAll)
|
||||
{
|
||||
std::vector<HWND> windows = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
||||
|
||||
|
||||
if (!processAll)
|
||||
{
|
||||
std::vector<HWND> windowsDiff{};
|
||||
std::copy_if(windows.begin(), windows.end(), std::back_inserter(windowsDiff), [&](HWND window) { return std::find(m_windowsBefore.begin(), m_windowsBefore.end(), window) == m_windowsBefore.end(); });
|
||||
windows = windowsDiff;
|
||||
}
|
||||
|
||||
|
||||
for (HWND window : windows)
|
||||
{
|
||||
processWindow(window);
|
||||
@@ -194,12 +359,11 @@ void WindowArranger::processWindow(HWND window)
|
||||
}
|
||||
|
||||
const auto& apps = m_launchingStatus.Get();
|
||||
auto iter = std::find_if(apps.begin(), apps.end(), [&](const auto& val)
|
||||
{
|
||||
return val.second.state == LaunchingState::Launched &&
|
||||
!val.second.window &&
|
||||
(val.first.name == data.value().name || val.first.path == data.value().installPath);
|
||||
});
|
||||
auto iter = std::find_if(apps.begin(), apps.end(), [&](const auto& val) {
|
||||
return val.second.state == LaunchingState::Launched &&
|
||||
!val.second.window &&
|
||||
(val.first.name == data.value().name || val.first.path == data.value().installPath);
|
||||
});
|
||||
|
||||
if (iter == apps.end())
|
||||
{
|
||||
@@ -258,7 +422,7 @@ bool WindowArranger::moveWindow(HWND window, const WorkspacesData::WorkspacesPro
|
||||
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))
|
||||
if (PlacementHelper::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);
|
||||
|
||||
@@ -5,8 +5,15 @@
|
||||
#include <WorkspacesLib/AppUtils.h>
|
||||
#include <WorkspacesLib/IPCHelper.h>
|
||||
#include <WorkspacesLib/LaunchingStatus.h>
|
||||
#include <WorkspacesLib/PwaHelper.h>
|
||||
#include <WorkspacesLib/WorkspacesData.h>
|
||||
|
||||
struct WindowWithDistance
|
||||
{
|
||||
int distance;
|
||||
HWND window;
|
||||
};
|
||||
|
||||
class WindowArranger
|
||||
{
|
||||
public:
|
||||
@@ -21,7 +28,9 @@ private:
|
||||
//const WindowCreationHandler m_windowCreationHandler;
|
||||
IPCHelper m_ipcHelper;
|
||||
LaunchingStatus m_launchingStatus;
|
||||
|
||||
std::optional<WindowWithDistance> GetNearestWindow(const WorkspacesData::WorkspacesProject::Application& app, const std::vector<HWND>& movedWindows, Utils::PwaHelper& pwaHelper);
|
||||
bool TryMoveWindow(const WorkspacesData::WorkspacesProject::Application& app, HWND windowToMove);
|
||||
|
||||
//void onWindowCreated(HWND window);
|
||||
void processWindows(bool processAll);
|
||||
void processWindow(HWND window);
|
||||
|
||||
Reference in New Issue
Block a user