2020-02-10 21:59:51 +08:00
|
|
|
#include "pch.h"
|
|
|
|
#include "JsonHelpers.h"
|
|
|
|
#include "ZoneSet.h"
|
2020-02-18 00:40:02 +08:00
|
|
|
#include "trace.h"
|
2020-02-10 21:59:51 +08:00
|
|
|
|
|
|
|
#include <common/common.h>
|
|
|
|
|
|
|
|
#include <shlwapi.h>
|
|
|
|
#include <filesystem>
|
|
|
|
#include <fstream>
|
|
|
|
#include <regex>
|
2020-03-17 05:29:13 +08:00
|
|
|
#include <sstream>
|
2020-05-05 16:13:50 +08:00
|
|
|
#include <unordered_set>
|
2020-02-10 21:59:51 +08:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
// From Settings.cs
|
|
|
|
constexpr int c_focusModelId = 0xFFFF;
|
|
|
|
constexpr int c_rowsModelId = 0xFFFE;
|
|
|
|
constexpr int c_columnsModelId = 0xFFFD;
|
|
|
|
constexpr int c_gridModelId = 0xFFFC;
|
|
|
|
constexpr int c_priorityGridModelId = 0xFFFB;
|
|
|
|
constexpr int c_blankCustomModelId = 0xFFFA;
|
|
|
|
|
|
|
|
const wchar_t* FANCY_ZONES_DATA_FILE = L"zones-settings.json";
|
2020-05-26 22:01:12 +08:00
|
|
|
const wchar_t* FANCY_ZONES_APP_ZONE_HISTORY_FILE = L"app-zone-history.json";
|
2020-02-18 18:55:08 +08:00
|
|
|
const wchar_t* DEFAULT_GUID = L"{00000000-0000-0000-0000-000000000000}";
|
2020-04-30 17:16:25 +08:00
|
|
|
const wchar_t* REG_SETTINGS = L"Software\\SuperFancyZones";
|
2020-02-18 18:55:08 +08:00
|
|
|
|
|
|
|
std::wstring ExtractVirtualDesktopId(const std::wstring& deviceId)
|
|
|
|
{
|
|
|
|
// Format: <device-id>_<resolution>_<virtual-desktop-id>
|
|
|
|
return deviceId.substr(deviceId.rfind('_') + 1);
|
|
|
|
}
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace JSONHelpers
|
|
|
|
{
|
2020-03-17 05:29:13 +08:00
|
|
|
bool isValidGuid(const std::wstring& str)
|
|
|
|
{
|
|
|
|
GUID id;
|
|
|
|
return SUCCEEDED_LOG(CLSIDFromString(str.c_str(), &id));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isValidDeviceId(const std::wstring& str)
|
|
|
|
{
|
2020-04-03 00:51:36 +08:00
|
|
|
std::wstring monitorName;
|
2020-03-17 05:29:13 +08:00
|
|
|
std::wstring temp;
|
|
|
|
std::vector<std::wstring> parts;
|
|
|
|
std::wstringstream wss(str);
|
2020-04-03 00:51:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
Important fix for device info that contains a '_' in the name:
|
|
|
|
1. first search for '#'
|
|
|
|
2. Then split the remaining string by '_'
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Step 1: parse the name until the #, then to the '_'
|
|
|
|
if (str.find(L'#') != std::string::npos)
|
|
|
|
{
|
|
|
|
std::getline(wss, temp, L'#');
|
|
|
|
|
|
|
|
monitorName = temp;
|
|
|
|
|
|
|
|
if (!std::getline(wss, temp, L'_'))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
monitorName += L"#" + temp;
|
|
|
|
parts.push_back(monitorName);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 2: parse the rest of the id
|
2020-03-17 05:29:13 +08:00
|
|
|
while (std::getline(wss, temp, L'_'))
|
|
|
|
{
|
|
|
|
parts.push_back(temp);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parts.size() != 4)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Refer to ZoneWindowUtils::GenerateUniqueId parts contain:
|
|
|
|
1. monitor id [string]
|
|
|
|
2. width of device [int]
|
|
|
|
3. height of device [int]
|
|
|
|
4. virtual desktop id (GUID) [string]
|
|
|
|
*/
|
|
|
|
try
|
|
|
|
{
|
|
|
|
//check if resolution contain only digits
|
|
|
|
for (const auto& c : parts[1])
|
|
|
|
{
|
|
|
|
std::stoi(std::wstring(&c));
|
|
|
|
}
|
|
|
|
for (const auto& c : parts[2])
|
|
|
|
{
|
|
|
|
std::stoi(std::wstring(&c));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const std::exception&)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isValidGuid(parts[3]) || parts[0].empty())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
json::JsonArray NumVecToJsonArray(const std::vector<int>& vec)
|
|
|
|
{
|
|
|
|
json::JsonArray arr;
|
|
|
|
for (const auto& val : vec)
|
|
|
|
{
|
|
|
|
arr.Append(json::JsonValue::CreateNumberValue(val));
|
|
|
|
}
|
|
|
|
|
|
|
|
return arr;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<int> JsonArrayToNumVec(const json::JsonArray& arr)
|
|
|
|
{
|
|
|
|
std::vector<int> vec;
|
|
|
|
for (const auto& val : arr)
|
|
|
|
{
|
|
|
|
vec.emplace_back(static_cast<int>(val.GetNumber()));
|
|
|
|
}
|
|
|
|
|
|
|
|
return vec;
|
|
|
|
}
|
|
|
|
|
|
|
|
ZoneSetLayoutType TypeFromLayoutId(int layoutID)
|
|
|
|
{
|
|
|
|
switch (layoutID)
|
|
|
|
{
|
|
|
|
case c_focusModelId:
|
|
|
|
return ZoneSetLayoutType::Focus;
|
|
|
|
case c_columnsModelId:
|
|
|
|
return ZoneSetLayoutType::Columns;
|
|
|
|
case c_rowsModelId:
|
|
|
|
return ZoneSetLayoutType::Rows;
|
|
|
|
case c_gridModelId:
|
|
|
|
return ZoneSetLayoutType::Grid;
|
|
|
|
case c_priorityGridModelId:
|
|
|
|
return ZoneSetLayoutType::PriorityGrid;
|
|
|
|
case c_blankCustomModelId:
|
|
|
|
return ZoneSetLayoutType::Blank;
|
|
|
|
default:
|
|
|
|
return ZoneSetLayoutType::Custom;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::wstring TypeToString(ZoneSetLayoutType type)
|
|
|
|
{
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case ZoneSetLayoutType::Blank:
|
|
|
|
return L"blank";
|
|
|
|
case ZoneSetLayoutType::Focus:
|
|
|
|
return L"focus";
|
|
|
|
case ZoneSetLayoutType::Columns:
|
|
|
|
return L"columns";
|
|
|
|
case ZoneSetLayoutType::Rows:
|
|
|
|
return L"rows";
|
|
|
|
case ZoneSetLayoutType::Grid:
|
|
|
|
return L"grid";
|
|
|
|
case ZoneSetLayoutType::PriorityGrid:
|
|
|
|
return L"priority-grid";
|
|
|
|
case ZoneSetLayoutType::Custom:
|
|
|
|
return L"custom";
|
|
|
|
default:
|
|
|
|
return L"TypeToString_ERROR";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ZoneSetLayoutType TypeFromString(const std::wstring& typeStr)
|
|
|
|
{
|
|
|
|
if (typeStr == L"focus")
|
|
|
|
{
|
|
|
|
return JSONHelpers::ZoneSetLayoutType::Focus;
|
|
|
|
}
|
|
|
|
else if (typeStr == L"columns")
|
|
|
|
{
|
|
|
|
return JSONHelpers::ZoneSetLayoutType::Columns;
|
|
|
|
}
|
|
|
|
else if (typeStr == L"rows")
|
|
|
|
{
|
|
|
|
return JSONHelpers::ZoneSetLayoutType::Rows;
|
|
|
|
}
|
|
|
|
else if (typeStr == L"grid")
|
|
|
|
{
|
|
|
|
return JSONHelpers::ZoneSetLayoutType::Grid;
|
|
|
|
}
|
|
|
|
else if (typeStr == L"priority-grid")
|
|
|
|
{
|
|
|
|
return JSONHelpers::ZoneSetLayoutType::PriorityGrid;
|
|
|
|
}
|
|
|
|
else if (typeStr == L"custom")
|
|
|
|
{
|
|
|
|
return JSONHelpers::ZoneSetLayoutType::Custom;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return JSONHelpers::ZoneSetLayoutType::Blank;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FancyZonesData& FancyZonesDataInstance()
|
|
|
|
{
|
|
|
|
static FancyZonesData instance;
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
FancyZonesData::FancyZonesData()
|
|
|
|
{
|
|
|
|
std::wstring result = PTSettingsHelper::get_module_save_folder_location(L"FancyZones");
|
|
|
|
jsonFilePath = result + L"\\" + std::wstring(FANCY_ZONES_DATA_FILE);
|
2020-05-26 22:01:12 +08:00
|
|
|
appZoneHistoryFilePath = result + L"\\" + std::wstring(FANCY_ZONES_APP_ZONE_HISTORY_FILE);
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
json::JsonObject FancyZonesData::GetPersistFancyZonesJSON()
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
std::wstring save_file_path = GetPersistFancyZonesJSONPath();
|
|
|
|
|
|
|
|
auto result = json::from_file(save_file_path);
|
|
|
|
if (result)
|
|
|
|
{
|
2020-05-26 22:01:12 +08:00
|
|
|
if (!result->HasKey(L"app-zone-history"))
|
|
|
|
{
|
|
|
|
auto appZoneHistory = json::from_file(appZoneHistoryFilePath);
|
|
|
|
if (appZoneHistory)
|
|
|
|
{
|
|
|
|
result->SetNamedValue(L"app-zone-history", appZoneHistory->GetNamedArray(L"app-zone-history"));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result->SetNamedValue(L"app-zone-history", json::JsonArray());
|
|
|
|
}
|
|
|
|
}
|
2020-02-10 21:59:51 +08:00
|
|
|
return *result;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return json::JsonObject();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-17 23:28:49 +08:00
|
|
|
std::optional<DeviceInfoData> FancyZonesData::FindDeviceInfo(const std::wstring& zoneWindowId) const
|
|
|
|
{
|
|
|
|
std::scoped_lock lock{ dataLock };
|
|
|
|
auto it = deviceInfoMap.find(zoneWindowId);
|
|
|
|
return it != end(deviceInfoMap) ? std::optional{ it->second } : std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<CustomZoneSetData> FancyZonesData::FindCustomZoneSet(const std::wstring& guuid) const
|
|
|
|
{
|
|
|
|
std::scoped_lock lock{ dataLock };
|
|
|
|
auto it = customZoneSetsMap.find(guuid);
|
|
|
|
return it != end(customZoneSetsMap) ? std::optional{ it->second } : std::nullopt;
|
|
|
|
}
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
void FancyZonesData::AddDevice(const std::wstring& deviceId)
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
if (!deviceInfoMap.contains(deviceId))
|
|
|
|
{
|
|
|
|
// Creates default entry in map when ZoneWindow is created
|
2020-02-17 23:28:49 +08:00
|
|
|
deviceInfoMap[deviceId] = DeviceInfoData{ ZoneSetData{ L"null", ZoneSetLayoutType::Blank } };
|
2020-02-18 18:55:08 +08:00
|
|
|
}
|
|
|
|
}
|
2020-02-10 21:59:51 +08:00
|
|
|
|
2020-02-18 18:55:08 +08:00
|
|
|
bool FancyZonesData::RemoveDevicesByVirtualDesktopId(const std::wstring& virtualDesktopId)
|
|
|
|
{
|
2020-02-27 16:52:14 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-18 18:55:08 +08:00
|
|
|
if (virtualDesktopId == DEFAULT_GUID)
|
|
|
|
{
|
|
|
|
return false;
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
2020-02-18 18:55:08 +08:00
|
|
|
bool modified{ false };
|
|
|
|
for (auto it = deviceInfoMap.begin(); it != deviceInfoMap.end();)
|
|
|
|
{
|
|
|
|
if (ExtractVirtualDesktopId(it->first) == virtualDesktopId)
|
|
|
|
{
|
|
|
|
it = deviceInfoMap.erase(it);
|
|
|
|
modified = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return modified;
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void FancyZonesData::CloneDeviceInfo(const std::wstring& source, const std::wstring& destination)
|
|
|
|
{
|
2020-03-03 01:52:38 +08:00
|
|
|
if (source == destination)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-03-03 01:52:38 +08:00
|
|
|
|
|
|
|
// The source virtual desktop is deleted, simply ignore it.
|
|
|
|
if (!deviceInfoMap.contains(source))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
// Clone information from source device if destination device is uninitialized (Blank).
|
|
|
|
auto& destInfo = deviceInfoMap[destination];
|
|
|
|
if (destInfo.activeZoneSet.type == ZoneSetLayoutType::Blank)
|
|
|
|
{
|
|
|
|
destInfo = deviceInfoMap[source];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-30 17:16:25 +08:00
|
|
|
void FancyZonesData::UpdatePrimaryDesktopData(const std::wstring& desktopId)
|
|
|
|
{
|
|
|
|
// Explorer persists current virtual desktop identifier to registry on a per session basis,
|
|
|
|
// but only after first virtual desktop switch happens. If the user hasn't switched virtual
|
|
|
|
// desktops in this session value in registry will be empty and we will use default GUID in
|
|
|
|
// that case (00000000-0000-0000-0000-000000000000).
|
|
|
|
// This method will go through all our persisted data with default GUID and update it with
|
|
|
|
// valid one.
|
|
|
|
auto replaceDesktopId = [&desktopId](const std::wstring& deviceId) {
|
|
|
|
return deviceId.substr(0, deviceId.rfind('_') + 1) + desktopId;
|
|
|
|
};
|
|
|
|
std::scoped_lock lock{ dataLock };
|
|
|
|
for (auto& [path, data] : appZoneHistoryMap)
|
|
|
|
{
|
|
|
|
if (ExtractVirtualDesktopId(data.deviceId) == DEFAULT_GUID)
|
|
|
|
{
|
|
|
|
data.deviceId = replaceDesktopId(data.deviceId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
std::vector<std::wstring> toReplace{};
|
|
|
|
for (const auto& [id, data] : deviceInfoMap)
|
|
|
|
{
|
|
|
|
if (ExtractVirtualDesktopId(id) == DEFAULT_GUID)
|
|
|
|
{
|
|
|
|
toReplace.push_back(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const auto& id : toReplace)
|
|
|
|
{
|
|
|
|
auto mapEntry = deviceInfoMap.extract(id);
|
|
|
|
mapEntry.key() = replaceDesktopId(id);
|
|
|
|
deviceInfoMap.insert(std::move(mapEntry));
|
|
|
|
}
|
|
|
|
if (activeDeviceId == DEFAULT_GUID)
|
|
|
|
{
|
|
|
|
activeDeviceId = replaceDesktopId(activeDeviceId);
|
|
|
|
}
|
|
|
|
SaveFancyZonesData();
|
|
|
|
}
|
|
|
|
|
2020-05-05 16:13:50 +08:00
|
|
|
void FancyZonesData::RemoveDeletedDesktops(const std::vector<std::wstring>& activeDesktops)
|
|
|
|
{
|
|
|
|
std::unordered_set<std::wstring> active(std::begin(activeDesktops), std::end(activeDesktops));
|
|
|
|
std::scoped_lock lock{ dataLock };
|
|
|
|
for (auto it = std::begin(deviceInfoMap); it != std::end(deviceInfoMap);)
|
|
|
|
{
|
|
|
|
auto foundId = active.find(ExtractVirtualDesktopId(it->first));
|
|
|
|
if (foundId == std::end(active))
|
|
|
|
{
|
|
|
|
it = deviceInfoMap.erase(it);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SaveFancyZonesData();
|
|
|
|
}
|
|
|
|
|
2020-05-27 22:52:59 +08:00
|
|
|
bool FancyZonesData::IsAnotherWindowOfApplicationInstanceZoned(HWND window) const
|
|
|
|
{
|
|
|
|
std::scoped_lock lock{ dataLock };
|
|
|
|
auto processPath = get_process_path(window);
|
|
|
|
if (!processPath.empty())
|
|
|
|
{
|
|
|
|
auto history = appZoneHistoryMap.find(processPath);
|
|
|
|
if (history != appZoneHistoryMap.end())
|
|
|
|
{
|
|
|
|
DWORD processId = 0;
|
|
|
|
GetWindowThreadProcessId(window, &processId);
|
|
|
|
|
|
|
|
auto processIdIt = history->second.processIdToHandleMap.find(processId);
|
|
|
|
|
|
|
|
if (processIdIt == history->second.processIdToHandleMap.end())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (processIdIt->second != window && IsWindow(processIdIt->second))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FancyZonesData::UpdateProcessIdToHandleMap(HWND window)
|
|
|
|
{
|
|
|
|
std::scoped_lock lock{ dataLock };
|
|
|
|
auto processPath = get_process_path(window);
|
|
|
|
if (!processPath.empty())
|
|
|
|
{
|
|
|
|
auto history = appZoneHistoryMap.find(processPath);
|
|
|
|
if (history != appZoneHistoryMap.end())
|
|
|
|
{
|
|
|
|
DWORD processId = 0;
|
|
|
|
GetWindowThreadProcessId(window, &processId);
|
|
|
|
|
|
|
|
history->second.processIdToHandleMap[processId] = window;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:01:12 +08:00
|
|
|
std::vector<int> FancyZonesData::GetAppLastZoneIndexSet(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId) const
|
2020-02-10 21:59:51 +08:00
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
auto processPath = get_process_path(window);
|
|
|
|
if (!processPath.empty())
|
|
|
|
{
|
|
|
|
auto history = appZoneHistoryMap.find(processPath);
|
|
|
|
if (history != appZoneHistoryMap.end())
|
|
|
|
{
|
|
|
|
const auto& data = history->second;
|
|
|
|
if (data.zoneSetUuid == zoneSetId && data.deviceId == deviceId)
|
|
|
|
{
|
2020-05-26 22:01:12 +08:00
|
|
|
return history->second.zoneIndexSet;
|
2020-02-17 23:28:49 +08:00
|
|
|
}
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:01:12 +08:00
|
|
|
return {};
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool FancyZonesData::RemoveAppLastZone(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId)
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
auto processPath = get_process_path(window);
|
|
|
|
if (!processPath.empty())
|
|
|
|
{
|
|
|
|
auto history = appZoneHistoryMap.find(processPath);
|
|
|
|
if (history != appZoneHistoryMap.end())
|
|
|
|
{
|
2020-05-27 22:52:59 +08:00
|
|
|
auto& data = history->second;
|
2020-02-10 21:59:51 +08:00
|
|
|
if (data.zoneSetUuid == zoneSetId && data.deviceId == deviceId)
|
|
|
|
{
|
2020-05-27 22:52:59 +08:00
|
|
|
if (!IsAnotherWindowOfApplicationInstanceZoned(window))
|
|
|
|
{
|
|
|
|
DWORD processId = 0;
|
|
|
|
GetWindowThreadProcessId(window, &processId);
|
|
|
|
|
|
|
|
data.processIdToHandleMap.erase(processId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there is another instance placed don't erase history
|
|
|
|
for (auto placedWindow : data.processIdToHandleMap)
|
|
|
|
{
|
|
|
|
if (IsWindow(placedWindow.second))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
appZoneHistoryMap.erase(processPath);
|
2020-02-11 19:34:37 +08:00
|
|
|
SaveFancyZonesData();
|
2020-02-10 21:59:51 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:01:12 +08:00
|
|
|
bool FancyZonesData::SetAppLastZones(HWND window, const std::wstring& deviceId, const std::wstring& zoneSetId, const std::vector<int>& zoneIndexSet)
|
2020-02-10 21:59:51 +08:00
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-05-27 22:52:59 +08:00
|
|
|
|
|
|
|
if (IsAnotherWindowOfApplicationInstanceZoned(window))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
auto processPath = get_process_path(window);
|
|
|
|
if (processPath.empty())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-05-27 22:52:59 +08:00
|
|
|
DWORD processId = 0;
|
|
|
|
GetWindowThreadProcessId(window, &processId);
|
|
|
|
|
|
|
|
auto history = appZoneHistoryMap.find(processPath);
|
|
|
|
|
|
|
|
if (history != appZoneHistoryMap.end())
|
|
|
|
{
|
|
|
|
auto& data = history->second;
|
|
|
|
|
|
|
|
for (auto placedWindow : data.processIdToHandleMap)
|
|
|
|
{
|
|
|
|
if (IsWindow(placedWindow.second) && processId != placedWindow.first)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto windowMap = appZoneHistoryMap[processPath].processIdToHandleMap;
|
|
|
|
windowMap[processId] = window;
|
|
|
|
appZoneHistoryMap[processPath] = AppZoneHistoryData{ .processIdToHandleMap = windowMap, .zoneSetUuid = zoneSetId, .deviceId = deviceId, .zoneIndexSet = zoneIndexSet };
|
2020-02-11 19:34:37 +08:00
|
|
|
SaveFancyZonesData();
|
2020-02-10 21:59:51 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FancyZonesData::SetActiveZoneSet(const std::wstring& deviceId, const ZoneSetData& data)
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
auto it = deviceInfoMap.find(deviceId);
|
|
|
|
if (it != deviceInfoMap.end())
|
|
|
|
{
|
|
|
|
it->second.activeZoneSet = data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FancyZonesData::SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath) const
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
json::JsonObject deviceInfoJson = DeviceInfoJSON::ToJson(deviceInfo);
|
|
|
|
json::to_file(tmpFilePath, deviceInfoJson);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FancyZonesData::ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath)
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
if (std::filesystem::exists(tmpFilePath))
|
|
|
|
{
|
|
|
|
if (auto zoneSetJson = json::from_file(tmpFilePath); zoneSetJson.has_value())
|
|
|
|
{
|
|
|
|
if (auto deviceInfo = DeviceInfoJSON::FromJson(zoneSetJson.value()); deviceInfo.has_value())
|
|
|
|
{
|
|
|
|
activeDeviceId = deviceInfo->deviceId;
|
|
|
|
deviceInfoMap[activeDeviceId] = std::move(deviceInfo->data);
|
|
|
|
DeleteTmpFile(tmpFilePath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
activeDeviceId.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath)
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
bool res = true;
|
|
|
|
if (std::filesystem::exists(tmpFilePath))
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (auto customZoneSetJson = json::from_file(tmpFilePath); customZoneSetJson.has_value())
|
|
|
|
{
|
|
|
|
if (auto customZoneSet = CustomZoneSetJSON::FromJson(customZoneSetJson.value()); customZoneSet.has_value())
|
|
|
|
{
|
|
|
|
customZoneSetsMap[customZoneSet->uuid] = std::move(customZoneSet->data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
res = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
DeleteTmpFile(tmpFilePath);
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FancyZonesData::ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath)
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
bool res = true;
|
|
|
|
if (std::filesystem::exists(tmpFilePath))
|
|
|
|
{
|
|
|
|
auto deletedZoneSetsJson = json::from_file(tmpFilePath);
|
|
|
|
try
|
|
|
|
{
|
|
|
|
auto deletedCustomZoneSets = deletedZoneSetsJson->GetNamedArray(L"deleted-custom-zone-sets");
|
|
|
|
for (auto zoneSet : deletedCustomZoneSets)
|
|
|
|
{
|
|
|
|
std::wstring uuid = L"{" + std::wstring{ zoneSet.GetString() } + L"}";
|
|
|
|
customZoneSetsMap.erase(std::wstring{ uuid });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
res = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
DeleteTmpFile(tmpFilePath);
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FancyZonesData::ParseAppZoneHistory(const json::JsonObject& fancyZonesDataJSON)
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
try
|
|
|
|
{
|
|
|
|
auto appLastZones = fancyZonesDataJSON.GetNamedArray(L"app-zone-history");
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < appLastZones.Size(); ++i)
|
|
|
|
{
|
|
|
|
json::JsonObject appLastZone = appLastZones.GetObjectAt(i);
|
|
|
|
if (auto appZoneHistory = AppZoneHistoryJSON::FromJson(appLastZone); appZoneHistory.has_value())
|
|
|
|
{
|
|
|
|
appZoneHistoryMap[appZoneHistory->appPath] = std::move(appZoneHistory->data);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json::JsonArray FancyZonesData::SerializeAppZoneHistory() const
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
json::JsonArray appHistoryArray;
|
|
|
|
|
|
|
|
for (const auto& [appPath, appZoneHistoryData] : appZoneHistoryMap)
|
|
|
|
{
|
|
|
|
appHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ appPath, appZoneHistoryData }));
|
|
|
|
}
|
|
|
|
|
|
|
|
return appHistoryArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FancyZonesData::ParseDeviceInfos(const json::JsonObject& fancyZonesDataJSON)
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
try
|
|
|
|
{
|
|
|
|
auto devices = fancyZonesDataJSON.GetNamedArray(L"devices");
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < devices.Size(); ++i)
|
|
|
|
{
|
|
|
|
if (auto device = DeviceInfoJSON::DeviceInfoJSON::FromJson(devices.GetObjectAt(i)); device.has_value())
|
|
|
|
{
|
|
|
|
deviceInfoMap[device->deviceId] = std::move(device->data);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json::JsonArray FancyZonesData::SerializeDeviceInfos() const
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
json::JsonArray DeviceInfosJSON{};
|
|
|
|
|
|
|
|
for (const auto& [deviceID, deviceData] : deviceInfoMap)
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
if (deviceData.activeZoneSet.type != ZoneSetLayoutType::Blank)
|
|
|
|
{
|
2020-02-10 21:59:51 +08:00
|
|
|
DeviceInfosJSON.Append(DeviceInfoJSON::DeviceInfoJSON::ToJson(DeviceInfoJSON{ deviceID, deviceData }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return DeviceInfosJSON;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FancyZonesData::ParseCustomZoneSets(const json::JsonObject& fancyZonesDataJSON)
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
try
|
|
|
|
{
|
|
|
|
auto customZoneSets = fancyZonesDataJSON.GetNamedArray(L"custom-zone-sets");
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < customZoneSets.Size(); ++i)
|
|
|
|
{
|
|
|
|
if (auto zoneSet = CustomZoneSetJSON::FromJson(customZoneSets.GetObjectAt(i)); zoneSet.has_value())
|
|
|
|
{
|
|
|
|
customZoneSetsMap[zoneSet->uuid] = std::move(zoneSet->data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json::JsonArray FancyZonesData::SerializeCustomZoneSets() const
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
json::JsonArray customZoneSetsJSON{};
|
|
|
|
|
|
|
|
for (const auto& [zoneSetId, zoneSetData] : customZoneSetsMap)
|
|
|
|
{
|
|
|
|
customZoneSetsJSON.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ zoneSetId, zoneSetData }));
|
|
|
|
}
|
|
|
|
|
|
|
|
return customZoneSetsJSON;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FancyZonesData::CustomZoneSetsToJsonFile(std::wstring_view filePath) const
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
const auto& customZoneSetsJson = SerializeCustomZoneSets();
|
|
|
|
json::JsonObject root{};
|
|
|
|
root.SetNamedValue(L"custom-zone-sets", customZoneSetsJson);
|
|
|
|
json::to_file(filePath, root);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FancyZonesData::LoadFancyZonesData()
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
std::wstring jsonFilePath = GetPersistFancyZonesJSONPath();
|
|
|
|
|
|
|
|
if (!std::filesystem::exists(jsonFilePath))
|
|
|
|
{
|
|
|
|
MigrateCustomZoneSetsFromRegistry();
|
|
|
|
|
|
|
|
SaveFancyZonesData();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
json::JsonObject fancyZonesDataJSON = GetPersistFancyZonesJSON();
|
|
|
|
|
|
|
|
ParseAppZoneHistory(fancyZonesDataJSON);
|
|
|
|
ParseDeviceInfos(fancyZonesDataJSON);
|
|
|
|
ParseCustomZoneSets(fancyZonesDataJSON);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FancyZonesData::SaveFancyZonesData() const
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
json::JsonObject root{};
|
2020-05-26 22:01:12 +08:00
|
|
|
json::JsonObject appZoneHistoryRoot{};
|
2020-02-10 21:59:51 +08:00
|
|
|
|
2020-05-26 22:01:12 +08:00
|
|
|
appZoneHistoryRoot.SetNamedValue(L"app-zone-history", SerializeAppZoneHistory());
|
2020-02-10 21:59:51 +08:00
|
|
|
root.SetNamedValue(L"devices", SerializeDeviceInfos());
|
|
|
|
root.SetNamedValue(L"custom-zone-sets", SerializeCustomZoneSets());
|
|
|
|
|
2020-02-18 00:40:02 +08:00
|
|
|
auto before = json::from_file(jsonFilePath);
|
|
|
|
if (!before.has_value() || before.value().Stringify() != root.Stringify())
|
|
|
|
{
|
|
|
|
Trace::FancyZones::DataChanged();
|
|
|
|
}
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
json::to_file(jsonFilePath, root);
|
2020-05-26 22:01:12 +08:00
|
|
|
json::to_file(appZoneHistoryFilePath, appZoneHistoryRoot);
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void FancyZonesData::MigrateCustomZoneSetsFromRegistry()
|
|
|
|
{
|
2020-02-17 23:28:49 +08:00
|
|
|
std::scoped_lock lock{ dataLock };
|
2020-02-10 21:59:51 +08:00
|
|
|
wchar_t key[256];
|
2020-04-30 17:16:25 +08:00
|
|
|
StringCchPrintf(key, ARRAYSIZE(key), L"%s\\%s", REG_SETTINGS, L"Layouts");
|
2020-02-10 21:59:51 +08:00
|
|
|
HKEY hkey;
|
|
|
|
if (RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
BYTE data[256];
|
|
|
|
DWORD dataSize = ARRAYSIZE(data);
|
|
|
|
wchar_t value[256]{};
|
|
|
|
DWORD valueLength = ARRAYSIZE(value);
|
|
|
|
DWORD i = 0;
|
|
|
|
while (RegEnumValueW(hkey, i++, value, &valueLength, nullptr, nullptr, reinterpret_cast<BYTE*>(&data), &dataSize) == ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
CustomZoneSetData zoneSetData;
|
|
|
|
zoneSetData.name = std::wstring{ value };
|
|
|
|
zoneSetData.type = static_cast<CustomLayoutType>(data[2]);
|
|
|
|
// int version = data[0] * 256 + data[1]; - Not used anymore
|
|
|
|
|
2020-04-08 21:43:19 +08:00
|
|
|
GUID guid;
|
|
|
|
auto result = CoCreateGuid(&guid);
|
|
|
|
if (result != S_OK)
|
2020-02-10 21:59:51 +08:00
|
|
|
{
|
2020-04-08 21:43:19 +08:00
|
|
|
continue;
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
2020-04-08 21:43:19 +08:00
|
|
|
wil::unique_cotaskmem_string guidString;
|
|
|
|
if (!SUCCEEDED_LOG(StringFromCLSID(guid, &guidString)))
|
2020-03-04 04:48:22 +08:00
|
|
|
{
|
2020-04-08 21:43:19 +08:00
|
|
|
continue;
|
2020-03-04 04:48:22 +08:00
|
|
|
}
|
|
|
|
|
2020-04-08 21:43:19 +08:00
|
|
|
std::wstring uuid = guidString.get();
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
switch (zoneSetData.type)
|
|
|
|
{
|
2020-05-27 22:52:59 +08:00
|
|
|
case CustomLayoutType::Grid:
|
|
|
|
{
|
2020-02-10 21:59:51 +08:00
|
|
|
int j = 5;
|
|
|
|
GridLayoutInfo zoneSetInfo(GridLayoutInfo::Minimal{ .rows = data[j++], .columns = data[j++] });
|
|
|
|
|
2020-05-27 22:52:59 +08:00
|
|
|
for (int row = 0; row < zoneSetInfo.rows(); row++, j += 2)
|
2020-02-10 21:59:51 +08:00
|
|
|
{
|
2020-05-27 22:52:59 +08:00
|
|
|
zoneSetInfo.rowsPercents()[row] = data[j] * 256 + data[j + 1];
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
|
|
|
|
2020-05-27 22:52:59 +08:00
|
|
|
for (int col = 0; col < zoneSetInfo.columns(); col++, j += 2)
|
2020-02-10 21:59:51 +08:00
|
|
|
{
|
2020-05-27 22:52:59 +08:00
|
|
|
zoneSetInfo.columnsPercents()[col] = data[j] * 256 + data[j + 1];
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for (int row = 0; row < zoneSetInfo.rows(); row++)
|
|
|
|
{
|
|
|
|
for (int col = 0; col < zoneSetInfo.columns(); col++)
|
|
|
|
{
|
|
|
|
zoneSetInfo.cellChildMap()[row][col] = data[j++];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
zoneSetData.info = zoneSetInfo;
|
|
|
|
break;
|
|
|
|
}
|
2020-05-27 22:52:59 +08:00
|
|
|
case CustomLayoutType::Canvas:
|
|
|
|
{
|
2020-02-10 21:59:51 +08:00
|
|
|
CanvasLayoutInfo info;
|
|
|
|
|
|
|
|
int j = 5;
|
|
|
|
info.referenceWidth = data[j] * 256 + data[j + 1];
|
|
|
|
j += 2;
|
|
|
|
info.referenceHeight = data[j] * 256 + data[j + 1];
|
|
|
|
j += 2;
|
|
|
|
|
|
|
|
int count = data[j++];
|
|
|
|
info.zones.reserve(count);
|
|
|
|
while (count-- > 0)
|
|
|
|
{
|
|
|
|
int x = data[j] * 256 + data[j + 1];
|
|
|
|
j += 2;
|
|
|
|
int y = data[j] * 256 + data[j + 1];
|
|
|
|
j += 2;
|
|
|
|
int width = data[j] * 256 + data[j + 1];
|
|
|
|
j += 2;
|
|
|
|
int height = data[j] * 256 + data[j + 1];
|
|
|
|
j += 2;
|
|
|
|
info.zones.push_back(CanvasLayoutInfo::Rect{
|
|
|
|
x, y, width, height });
|
|
|
|
}
|
|
|
|
zoneSetData.info = info;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
2020-05-25 16:59:05 +08:00
|
|
|
continue;
|
2020-02-10 21:59:51 +08:00
|
|
|
}
|
|
|
|
customZoneSetsMap[uuid] = zoneSetData;
|
|
|
|
|
|
|
|
valueLength = ARRAYSIZE(value);
|
|
|
|
dataSize = ARRAYSIZE(data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json::JsonObject ZoneSetData::ToJson(const ZoneSetData& zoneSet)
|
|
|
|
{
|
|
|
|
json::JsonObject result{};
|
|
|
|
|
|
|
|
result.SetNamedValue(L"uuid", json::value(zoneSet.uuid));
|
|
|
|
result.SetNamedValue(L"type", json::value(TypeToString(zoneSet.type)));
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<ZoneSetData> ZoneSetData::FromJson(const json::JsonObject& zoneSet)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
ZoneSetData zoneSetData;
|
|
|
|
zoneSetData.uuid = zoneSet.GetNamedString(L"uuid");
|
|
|
|
zoneSetData.type = TypeFromString(std::wstring{ zoneSet.GetNamedString(L"type") });
|
|
|
|
|
2020-03-17 05:29:13 +08:00
|
|
|
if (!isValidGuid(zoneSetData.uuid))
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
return zoneSetData;
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json::JsonObject AppZoneHistoryJSON::ToJson(const AppZoneHistoryJSON& appZoneHistory)
|
|
|
|
{
|
|
|
|
json::JsonObject result{};
|
|
|
|
|
|
|
|
result.SetNamedValue(L"app-path", json::value(appZoneHistory.appPath));
|
2020-05-26 22:01:12 +08:00
|
|
|
|
|
|
|
json::JsonArray jsonIndexSet;
|
|
|
|
for (int index : appZoneHistory.data.zoneIndexSet)
|
|
|
|
{
|
|
|
|
jsonIndexSet.Append(json::value(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
result.SetNamedValue(L"zone-index-set", jsonIndexSet);
|
2020-02-10 21:59:51 +08:00
|
|
|
result.SetNamedValue(L"device-id", json::value(appZoneHistory.data.deviceId));
|
|
|
|
result.SetNamedValue(L"zoneset-uuid", json::value(appZoneHistory.data.zoneSetUuid));
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<AppZoneHistoryJSON> AppZoneHistoryJSON::FromJson(const json::JsonObject& zoneSet)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
AppZoneHistoryJSON result;
|
|
|
|
|
|
|
|
result.appPath = zoneSet.GetNamedString(L"app-path");
|
2020-05-26 22:01:12 +08:00
|
|
|
if (zoneSet.HasKey(L"zone-index-set"))
|
|
|
|
{
|
|
|
|
result.data.zoneIndexSet = {};
|
|
|
|
for (auto& value : zoneSet.GetNamedArray(L"zone-index-set"))
|
|
|
|
{
|
|
|
|
result.data.zoneIndexSet.push_back(static_cast<int>(value.GetNumber()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (zoneSet.HasKey(L"zone-index"))
|
|
|
|
{
|
|
|
|
result.data.zoneIndexSet = { static_cast<int>(zoneSet.GetNamedNumber(L"zone-index")) };
|
|
|
|
}
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
result.data.deviceId = zoneSet.GetNamedString(L"device-id");
|
|
|
|
result.data.zoneSetUuid = zoneSet.GetNamedString(L"zoneset-uuid");
|
|
|
|
|
2020-03-17 05:29:13 +08:00
|
|
|
if (!isValidGuid(result.data.zoneSetUuid) || !isValidDeviceId(result.data.deviceId))
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json::JsonObject DeviceInfoJSON::ToJson(const DeviceInfoJSON& device)
|
|
|
|
{
|
|
|
|
json::JsonObject result{};
|
|
|
|
|
|
|
|
result.SetNamedValue(L"device-id", json::value(device.deviceId));
|
|
|
|
result.SetNamedValue(L"active-zoneset", ZoneSetData::ToJson(device.data.activeZoneSet));
|
|
|
|
result.SetNamedValue(L"editor-show-spacing", json::value(device.data.showSpacing));
|
|
|
|
result.SetNamedValue(L"editor-spacing", json::value(device.data.spacing));
|
|
|
|
result.SetNamedValue(L"editor-zone-count", json::value(device.data.zoneCount));
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<DeviceInfoJSON> DeviceInfoJSON::FromJson(const json::JsonObject& device)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
DeviceInfoJSON result;
|
|
|
|
|
|
|
|
result.deviceId = device.GetNamedString(L"device-id");
|
2020-03-17 05:29:13 +08:00
|
|
|
if (!isValidDeviceId(result.deviceId))
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
2020-02-10 21:59:51 +08:00
|
|
|
|
|
|
|
if (auto zoneSet = ZoneSetData::FromJson(device.GetNamedObject(L"active-zoneset")); zoneSet.has_value())
|
|
|
|
{
|
|
|
|
result.data.activeZoneSet = std::move(zoneSet.value());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.data.showSpacing = device.GetNamedBoolean(L"editor-show-spacing");
|
|
|
|
result.data.spacing = static_cast<int>(device.GetNamedNumber(L"editor-spacing"));
|
|
|
|
result.data.zoneCount = static_cast<int>(
|
|
|
|
device.GetNamedNumber(L"editor-zone-count"));
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json::JsonObject CanvasLayoutInfo::ToJson(const CanvasLayoutInfo& canvasInfo)
|
|
|
|
{
|
|
|
|
json::JsonObject infoJson{};
|
|
|
|
infoJson.SetNamedValue(L"ref-width", json::value(canvasInfo.referenceWidth));
|
|
|
|
infoJson.SetNamedValue(L"ref-height", json::value(canvasInfo.referenceHeight));
|
|
|
|
json::JsonArray zonesJson;
|
|
|
|
|
|
|
|
for (const auto& [x, y, width, height] : canvasInfo.zones)
|
|
|
|
{
|
|
|
|
json::JsonObject zoneJson;
|
|
|
|
zoneJson.SetNamedValue(L"X", json::value(x));
|
|
|
|
zoneJson.SetNamedValue(L"Y", json::value(y));
|
|
|
|
zoneJson.SetNamedValue(L"width", json::value(width));
|
|
|
|
zoneJson.SetNamedValue(L"height", json::value(height));
|
|
|
|
zonesJson.Append(zoneJson);
|
|
|
|
}
|
|
|
|
infoJson.SetNamedValue(L"zones", zonesJson);
|
|
|
|
return infoJson;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<CanvasLayoutInfo> CanvasLayoutInfo::FromJson(const json::JsonObject& infoJson)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
CanvasLayoutInfo info;
|
|
|
|
info.referenceWidth = static_cast<int>(infoJson.GetNamedNumber(L"ref-width"));
|
|
|
|
info.referenceHeight = static_cast<int>(infoJson.GetNamedNumber(L"ref-height"));
|
|
|
|
json::JsonArray zonesJson = infoJson.GetNamedArray(L"zones");
|
|
|
|
uint32_t size = zonesJson.Size();
|
|
|
|
info.zones.reserve(size);
|
|
|
|
for (uint32_t i = 0; i < size; ++i)
|
|
|
|
{
|
|
|
|
json::JsonObject zoneJson = zonesJson.GetObjectAt(i);
|
|
|
|
const int x = static_cast<int>(zoneJson.GetNamedNumber(L"X"));
|
|
|
|
const int y = static_cast<int>(zoneJson.GetNamedNumber(L"Y"));
|
|
|
|
const int width = static_cast<int>(zoneJson.GetNamedNumber(L"width"));
|
|
|
|
const int height = static_cast<int>(zoneJson.GetNamedNumber(L"height"));
|
|
|
|
CanvasLayoutInfo::Rect zone{ x, y, width, height };
|
|
|
|
info.zones.push_back(zone);
|
|
|
|
}
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GridLayoutInfo::GridLayoutInfo(const Minimal& info) :
|
|
|
|
m_rows(info.rows),
|
|
|
|
m_columns(info.columns)
|
|
|
|
{
|
|
|
|
m_rowsPercents.resize(m_rows, 0);
|
|
|
|
m_columnsPercents.resize(m_columns, 0);
|
|
|
|
m_cellChildMap.resize(m_rows, {});
|
|
|
|
for (auto& cellRow : m_cellChildMap)
|
|
|
|
{
|
|
|
|
cellRow.resize(m_columns, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GridLayoutInfo::GridLayoutInfo(const Full& info) :
|
|
|
|
m_rows(info.rows),
|
|
|
|
m_columns(info.columns),
|
|
|
|
m_rowsPercents(info.rowsPercents),
|
|
|
|
m_columnsPercents(info.columnsPercents),
|
|
|
|
m_cellChildMap(info.cellChildMap)
|
|
|
|
{
|
|
|
|
m_rowsPercents.resize(m_rows, 0);
|
|
|
|
m_columnsPercents.resize(m_columns, 0);
|
|
|
|
m_cellChildMap.resize(m_rows, {});
|
|
|
|
for (auto& cellRow : m_cellChildMap)
|
|
|
|
{
|
|
|
|
cellRow.resize(m_columns, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json::JsonObject GridLayoutInfo::ToJson(const GridLayoutInfo& gridInfo)
|
|
|
|
{
|
|
|
|
json::JsonObject infoJson;
|
|
|
|
infoJson.SetNamedValue(L"rows", json::value(gridInfo.m_rows));
|
|
|
|
infoJson.SetNamedValue(L"columns", json::value(gridInfo.m_columns));
|
|
|
|
infoJson.SetNamedValue(L"rows-percentage", NumVecToJsonArray(gridInfo.m_rowsPercents));
|
|
|
|
infoJson.SetNamedValue(L"columns-percentage", NumVecToJsonArray(gridInfo.m_columnsPercents));
|
|
|
|
|
|
|
|
json::JsonArray cellChildMapJson;
|
|
|
|
for (int i = 0; i < gridInfo.m_cellChildMap.size(); ++i)
|
|
|
|
{
|
|
|
|
cellChildMapJson.Append(NumVecToJsonArray(gridInfo.m_cellChildMap[i]));
|
|
|
|
}
|
|
|
|
infoJson.SetNamedValue(L"cell-child-map", cellChildMapJson);
|
|
|
|
|
|
|
|
return infoJson;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<GridLayoutInfo> GridLayoutInfo::FromJson(const json::JsonObject& infoJson)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
GridLayoutInfo info(GridLayoutInfo::Minimal{});
|
|
|
|
|
|
|
|
info.m_rows = static_cast<int>(infoJson.GetNamedNumber(L"rows"));
|
|
|
|
info.m_columns = static_cast<int>(infoJson.GetNamedNumber(L"columns"));
|
|
|
|
|
|
|
|
json::JsonArray rowsPercentage = infoJson.GetNamedArray(L"rows-percentage");
|
|
|
|
json::JsonArray columnsPercentage = infoJson.GetNamedArray(L"columns-percentage");
|
|
|
|
json::JsonArray cellChildMap = infoJson.GetNamedArray(L"cell-child-map");
|
|
|
|
|
|
|
|
if (rowsPercentage.Size() != info.m_rows || columnsPercentage.Size() != info.m_columns || cellChildMap.Size() != info.m_rows)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
info.m_rowsPercents = JsonArrayToNumVec(rowsPercentage);
|
|
|
|
info.m_columnsPercents = JsonArrayToNumVec(columnsPercentage);
|
|
|
|
for (const auto& cellsRow : cellChildMap)
|
|
|
|
{
|
|
|
|
const auto cellsArray = cellsRow.GetArray();
|
|
|
|
if (cellsArray.Size() != info.m_columns)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
info.cellChildMap().push_back(JsonArrayToNumVec(cellsArray));
|
|
|
|
}
|
|
|
|
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json::JsonObject CustomZoneSetJSON::ToJson(const CustomZoneSetJSON& customZoneSet)
|
|
|
|
{
|
|
|
|
json::JsonObject result{};
|
|
|
|
|
|
|
|
result.SetNamedValue(L"uuid", json::value(customZoneSet.uuid));
|
|
|
|
result.SetNamedValue(L"name", json::value(customZoneSet.data.name));
|
|
|
|
switch (customZoneSet.data.type)
|
|
|
|
{
|
2020-05-27 22:52:59 +08:00
|
|
|
case CustomLayoutType::Canvas:
|
|
|
|
{
|
2020-02-10 21:59:51 +08:00
|
|
|
result.SetNamedValue(L"type", json::value(L"canvas"));
|
|
|
|
|
|
|
|
CanvasLayoutInfo info = std::get<CanvasLayoutInfo>(customZoneSet.data.info);
|
|
|
|
result.SetNamedValue(L"info", CanvasLayoutInfo::ToJson(info));
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2020-05-27 22:52:59 +08:00
|
|
|
case CustomLayoutType::Grid:
|
|
|
|
{
|
2020-02-10 21:59:51 +08:00
|
|
|
result.SetNamedValue(L"type", json::value(L"grid"));
|
|
|
|
|
|
|
|
GridLayoutInfo gridInfo = std::get<GridLayoutInfo>(customZoneSet.data.info);
|
|
|
|
result.SetNamedValue(L"info", GridLayoutInfo::ToJson(gridInfo));
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<CustomZoneSetJSON> CustomZoneSetJSON::FromJson(const json::JsonObject& customZoneSet)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
CustomZoneSetJSON result;
|
|
|
|
|
|
|
|
result.uuid = customZoneSet.GetNamedString(L"uuid");
|
2020-03-17 05:29:13 +08:00
|
|
|
if (!isValidGuid(result.uuid))
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
2020-05-27 22:52:59 +08:00
|
|
|
|
2020-02-10 21:59:51 +08:00
|
|
|
result.data.name = customZoneSet.GetNamedString(L"name");
|
|
|
|
|
|
|
|
json::JsonObject infoJson = customZoneSet.GetNamedObject(L"info");
|
|
|
|
std::wstring zoneSetType = std::wstring{ customZoneSet.GetNamedString(L"type") };
|
|
|
|
if (zoneSetType.compare(L"canvas") == 0)
|
|
|
|
{
|
|
|
|
if (auto info = CanvasLayoutInfo::FromJson(infoJson); info.has_value())
|
|
|
|
{
|
|
|
|
result.data.type = CustomLayoutType::Canvas;
|
|
|
|
result.data.info = std::move(info.value());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (zoneSetType.compare(L"grid") == 0)
|
|
|
|
{
|
|
|
|
if (auto info = GridLayoutInfo::FromJson(infoJson); info.has_value())
|
|
|
|
{
|
|
|
|
result.data.type = CustomLayoutType::Grid;
|
|
|
|
result.data.info = std::move(info.value());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
catch (const winrt::hresult_error&)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|