mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 18:57:19 +02:00
[FancyZones] Split zones-settings: app zone history (#15690)
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
#include "../pch.h"
|
||||
#include "AppZoneHistory.h"
|
||||
|
||||
#include <common/logger/call_tracer.h>
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/utils/process_path.h>
|
||||
|
||||
#include <FancyZonesLib/FancyZonesWindowProperties.h>
|
||||
#include <FancyZonesLib/JsonHelpers.h>
|
||||
|
||||
AppZoneHistory::AppZoneHistory()
|
||||
{
|
||||
}
|
||||
|
||||
AppZoneHistory& AppZoneHistory::instance()
|
||||
{
|
||||
static AppZoneHistory self;
|
||||
return self;
|
||||
}
|
||||
|
||||
void AppZoneHistory::SetVirtualDesktopCheckCallback(std::function<bool(GUID)> callback)
|
||||
{
|
||||
m_virtualDesktopCheckCallback = callback;
|
||||
}
|
||||
|
||||
void AppZoneHistory::LoadData()
|
||||
{
|
||||
auto file = AppZoneHistoryFileName();
|
||||
auto data = json::from_file(file);
|
||||
|
||||
try
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
m_history = JSONHelpers::ParseAppZoneHistory(data.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_history.clear();
|
||||
Logger::error(L"app-zone-history.json file is missing or malformed");
|
||||
}
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
Logger::error(L"Parsing app-zone-history error: {}", e.message());
|
||||
}
|
||||
}
|
||||
|
||||
void AppZoneHistory::SaveData()
|
||||
{
|
||||
_TRACER_;
|
||||
|
||||
bool dirtyFlag = false;
|
||||
std::unordered_map<std::wstring, std::vector<FancyZonesDataTypes::AppZoneHistoryData>> updatedHistory;
|
||||
if (m_virtualDesktopCheckCallback)
|
||||
{
|
||||
for (const auto& [path, dataVector] : m_history)
|
||||
{
|
||||
auto updatedVector = dataVector;
|
||||
for (auto& data : updatedVector)
|
||||
{
|
||||
if (!m_virtualDesktopCheckCallback(data.deviceId.virtualDesktopId))
|
||||
{
|
||||
data.deviceId.virtualDesktopId = GUID_NULL;
|
||||
dirtyFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
updatedHistory.insert(std::make_pair(path, updatedVector));
|
||||
}
|
||||
}
|
||||
|
||||
if (dirtyFlag)
|
||||
{
|
||||
JSONHelpers::SaveAppZoneHistory(AppZoneHistoryFileName(), updatedHistory);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSONHelpers::SaveAppZoneHistory(AppZoneHistoryFileName(), m_history);
|
||||
}
|
||||
}
|
||||
|
||||
bool AppZoneHistory::SetAppLastZones(HWND window, const FancyZonesDataTypes::DeviceIdData& deviceId, const std::wstring& zoneSetId, const ZoneIndexSet& zoneIndexSet)
|
||||
{
|
||||
_TRACER_;
|
||||
|
||||
if (IsAnotherWindowOfApplicationInstanceZoned(window, deviceId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto processPath = get_process_path(window);
|
||||
if (processPath.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD processId = 0;
|
||||
GetWindowThreadProcessId(window, &processId);
|
||||
|
||||
auto history = m_history.find(processPath);
|
||||
if (history != std::end(m_history))
|
||||
{
|
||||
auto& perDesktopData = history->second;
|
||||
for (auto& data : perDesktopData)
|
||||
{
|
||||
if (data.deviceId.isEqualWithNullVirtualDesktopId(deviceId))
|
||||
{
|
||||
// application already has history on this work area, update it with new window position
|
||||
data.processIdToHandleMap[processId] = window;
|
||||
data.zoneSetUuid = zoneSetId;
|
||||
data.zoneIndexSet = zoneIndexSet;
|
||||
SaveData();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<DWORD, HWND> processIdToHandleMap{};
|
||||
processIdToHandleMap[processId] = window;
|
||||
FancyZonesDataTypes::AppZoneHistoryData data{ .processIdToHandleMap = processIdToHandleMap,
|
||||
.zoneSetUuid = zoneSetId,
|
||||
.deviceId = deviceId,
|
||||
.zoneIndexSet = zoneIndexSet };
|
||||
|
||||
if (m_history.contains(processPath))
|
||||
{
|
||||
// application already has history but on other desktop, add with new desktop info
|
||||
m_history[processPath].push_back(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
// new application, create entry in app zone history map
|
||||
m_history[processPath] = std::vector<FancyZonesDataTypes::AppZoneHistoryData>{ data };
|
||||
}
|
||||
|
||||
SaveData();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppZoneHistory::RemoveAppLastZone(HWND window, const FancyZonesDataTypes::DeviceIdData& deviceId, const std::wstring_view& zoneSetId)
|
||||
{
|
||||
_TRACER_;
|
||||
|
||||
auto processPath = get_process_path(window);
|
||||
if (!processPath.empty())
|
||||
{
|
||||
auto history = m_history.find(processPath);
|
||||
if (history != std::end(m_history))
|
||||
{
|
||||
auto& perDesktopData = history->second;
|
||||
for (auto data = std::begin(perDesktopData); data != std::end(perDesktopData);)
|
||||
{
|
||||
if (data->deviceId.isEqualWithNullVirtualDesktopId(deviceId) && data->zoneSetUuid == zoneSetId)
|
||||
{
|
||||
if (!IsAnotherWindowOfApplicationInstanceZoned(window, deviceId))
|
||||
{
|
||||
DWORD processId = 0;
|
||||
GetWindowThreadProcessId(window, &processId);
|
||||
|
||||
data->processIdToHandleMap.erase(processId);
|
||||
}
|
||||
|
||||
// if there is another instance of same application placed in the same zone don't erase history
|
||||
ZoneIndex windowZoneStamp = reinterpret_cast<ZoneIndex>(::GetProp(window, ZonedWindowProperties::PropertyMultipleZoneID));
|
||||
for (auto placedWindow : data->processIdToHandleMap)
|
||||
{
|
||||
ZoneIndex placedWindowZoneStamp = reinterpret_cast<ZoneIndex>(::GetProp(placedWindow.second, ZonedWindowProperties::PropertyMultipleZoneID));
|
||||
if (IsWindow(placedWindow.second) && (windowZoneStamp == placedWindowZoneStamp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
data = perDesktopData.erase(data);
|
||||
if (perDesktopData.empty())
|
||||
{
|
||||
m_history.erase(processPath);
|
||||
}
|
||||
SaveData();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
++data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AppZoneHistory::RemoveApp(const std::wstring& appPath)
|
||||
{
|
||||
m_history.erase(appPath);
|
||||
}
|
||||
|
||||
const AppZoneHistory::TAppZoneHistoryMap& AppZoneHistory::GetFullAppZoneHistory() const noexcept
|
||||
{
|
||||
return m_history;
|
||||
}
|
||||
|
||||
std::optional<FancyZonesDataTypes::AppZoneHistoryData> AppZoneHistory::GetZoneHistory(const std::wstring& appPath, const FancyZonesDataTypes::DeviceIdData& deviceId) const noexcept
|
||||
{
|
||||
auto iter = m_history.find(appPath);
|
||||
if (iter != m_history.end())
|
||||
{
|
||||
auto historyVector = iter->second;
|
||||
for (const auto& history : historyVector)
|
||||
{
|
||||
if (history.deviceId.isEqualWithNullVirtualDesktopId(deviceId))
|
||||
{
|
||||
return history;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool AppZoneHistory::IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::DeviceIdData& deviceId) const noexcept
|
||||
{
|
||||
auto processPath = get_process_path(window);
|
||||
if (!processPath.empty())
|
||||
{
|
||||
auto history = m_history.find(processPath);
|
||||
if (history != std::end(m_history))
|
||||
{
|
||||
auto& perDesktopData = history->second;
|
||||
for (auto& data : perDesktopData)
|
||||
{
|
||||
if (data.deviceId.isEqualWithNullVirtualDesktopId(deviceId))
|
||||
{
|
||||
DWORD processId = 0;
|
||||
GetWindowThreadProcessId(window, &processId);
|
||||
|
||||
auto processIdIt = data.processIdToHandleMap.find(processId);
|
||||
|
||||
if (processIdIt == std::end(data.processIdToHandleMap))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (processIdIt->second != window && IsWindow(processIdIt->second))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AppZoneHistory::UpdateProcessIdToHandleMap(HWND window, const FancyZonesDataTypes::DeviceIdData& deviceId)
|
||||
{
|
||||
auto processPath = get_process_path(window);
|
||||
if (!processPath.empty())
|
||||
{
|
||||
auto history = m_history.find(processPath);
|
||||
if (history != std::end(m_history))
|
||||
{
|
||||
auto& perDesktopData = history->second;
|
||||
for (auto& data : perDesktopData)
|
||||
{
|
||||
if (data.deviceId.isEqualWithNullVirtualDesktopId(deviceId))
|
||||
{
|
||||
DWORD processId = 0;
|
||||
GetWindowThreadProcessId(window, &processId);
|
||||
data.processIdToHandleMap[processId] = window;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::DeviceIdData& deviceId, const std::wstring_view& zoneSetId) const
|
||||
{
|
||||
auto processPath = get_process_path(window);
|
||||
if (!processPath.empty())
|
||||
{
|
||||
auto history = m_history.find(processPath);
|
||||
if (history != std::end(m_history))
|
||||
{
|
||||
const auto& perDesktopData = history->second;
|
||||
for (const auto& data : perDesktopData)
|
||||
{
|
||||
if (data.zoneSetUuid == zoneSetId && data.deviceId.isEqualWithNullVirtualDesktopId(deviceId))
|
||||
{
|
||||
return data.zoneIndexSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void AppZoneHistory::RemoveDesktopAppZoneHistory(GUID desktopId)
|
||||
{
|
||||
for (auto it = std::begin(m_history); it != std::end(m_history);)
|
||||
{
|
||||
auto& perDesktopData = it->second;
|
||||
for (auto desktopIt = std::begin(perDesktopData); desktopIt != std::end(perDesktopData);)
|
||||
{
|
||||
if (desktopIt->deviceId.virtualDesktopId == desktopId)
|
||||
{
|
||||
desktopIt = perDesktopData.erase(desktopIt);
|
||||
}
|
||||
else
|
||||
{
|
||||
++desktopIt;
|
||||
}
|
||||
}
|
||||
|
||||
if (perDesktopData.empty())
|
||||
{
|
||||
it = m_history.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <FancyZonesLib/FancyZonesDataTypes.h>
|
||||
#include <FancyZonesLib/ModuleConstants.h>
|
||||
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
|
||||
class AppZoneHistory
|
||||
{
|
||||
public:
|
||||
using TAppZoneHistoryMap = std::unordered_map<std::wstring, std::vector<FancyZonesDataTypes::AppZoneHistoryData>>;
|
||||
|
||||
static AppZoneHistory& instance();
|
||||
|
||||
inline static std::wstring AppZoneHistoryFileName()
|
||||
{
|
||||
std::wstring saveFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
|
||||
#if defined(UNIT_TESTS)
|
||||
return saveFolderPath + L"\\test-app-zone-history.json";
|
||||
#endif
|
||||
return saveFolderPath + L"\\app-zone-history.json";
|
||||
}
|
||||
|
||||
void SetVirtualDesktopCheckCallback(std::function<bool(GUID)> callback);
|
||||
|
||||
void LoadData();
|
||||
void SaveData();
|
||||
|
||||
bool SetAppLastZones(HWND window, const FancyZonesDataTypes::DeviceIdData& deviceId, const std::wstring& zoneSetId, const ZoneIndexSet& zoneIndexSet);
|
||||
bool RemoveAppLastZone(HWND window, const FancyZonesDataTypes::DeviceIdData& deviceId, const std::wstring_view& zoneSetId);
|
||||
|
||||
void RemoveApp(const std::wstring& appPath);
|
||||
|
||||
const TAppZoneHistoryMap& GetFullAppZoneHistory() const noexcept;
|
||||
std::optional<FancyZonesDataTypes::AppZoneHistoryData> GetZoneHistory(const std::wstring& appPath, const FancyZonesDataTypes::DeviceIdData& deviceId) const noexcept;
|
||||
|
||||
bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::DeviceIdData& deviceId) const noexcept;
|
||||
void UpdateProcessIdToHandleMap(HWND window, const FancyZonesDataTypes::DeviceIdData& deviceId);
|
||||
ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::DeviceIdData& deviceId, const std::wstring_view& zoneSetId) const;
|
||||
|
||||
void RemoveDesktopAppZoneHistory(GUID desktopId);
|
||||
|
||||
private:
|
||||
AppZoneHistory();
|
||||
~AppZoneHistory() = default;
|
||||
|
||||
TAppZoneHistoryMap m_history;
|
||||
std::function<bool(GUID)> m_virtualDesktopCheckCallback;
|
||||
};
|
||||
Reference in New Issue
Block a user