mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 02:36:19 +02:00
Migrate FancyZones data persisting from Registry to JSON file (#1194)
* Migrate FancyZones data persisting from Registry to JSON file * Address PR comment: Remove redundant check * Addres PR comment: Remove unused Dpi and add CmdArgs enum * Address PR comment: Make methods const and inline * Address PR comments: Expose GenerateUniqueId function and use const ref instead of passing wstring by value * Address PR comment: Use lamdba as callback * Address PR comment: Move GenerateUniqueId to ZoneWindowUtils namespace * Address PR comment: Use regular comparison instead of std::wstring::compare * Address PR comment: Use std::wstring_view for tmp file paths * Address PR comment: Use scoped lock when accessing member data * Address PR comment: Remove typedefs to increase code readability * Address PR comment: removed nullptr checks with corresponding tests * Address PR comment: Move ZoneSet object instead of copying * Address PR comment: Make FancyZonesData instance const where possible * Remove unnecessary gutter variable during calculating zone coordinates * Remove uneeded subclass * Avoid unnecessary copying and reserve space for vector if possible * Save FancyZones data after exiting editor * App zone history (#18) * added window and zone set ids to app zone history * Rename JSON file * Remove AppZoneHistory migration * Move parsing of ZoneWindow independent temp files outside of it * Unit tests update (#19) * check device existence in map * updated ZoneSet tests * updated JsonHelpers tests * Use single zone count information * Remove uneeded tests * Remove one more test * Remove uneeded line * Address PR comments - Missing whitespace * Update zoneset data for new virtual desktops (#21) * update active zone set with actual data * Introduce Blank zone set (used to indicate that no layout applied yet). Move parsing completely outside of ZoneWindow. * Fix unit tests to match modifications in implementation * Fix applying layouts on startup (second monitor) Co-authored-by: vldmr11080 <57061786+vldmr11080@users.noreply.github.com> Co-authored-by: Seraphima <zykovas91@gmail.com>
This commit is contained in:
@@ -6,14 +6,27 @@
|
||||
#include "lib/Settings.h"
|
||||
#include "lib/ZoneWindow.h"
|
||||
#include "lib/RegistryHelpers.h"
|
||||
#include "lib/JsonHelpers.h"
|
||||
#include "lib/ZoneSet.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include <functional>
|
||||
#include <common/common.h>
|
||||
#include <lib\util.h>
|
||||
|
||||
enum class DisplayChangeType
|
||||
{
|
||||
WorkArea,
|
||||
DisplayChange,
|
||||
VirtualDesktop,
|
||||
Editor,
|
||||
Initialization
|
||||
};
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<> struct hash<GUID>
|
||||
template<>
|
||||
struct hash<GUID>
|
||||
{
|
||||
size_t operator()(const GUID& Value) const
|
||||
{
|
||||
@@ -26,9 +39,9 @@ namespace std
|
||||
struct FancyZones : public winrt::implements<FancyZones, IFancyZones, IFancyZonesCallback, IZoneWindowHost>
|
||||
{
|
||||
public:
|
||||
FancyZones(HINSTANCE hinstance, IFancyZonesSettings* settings) noexcept
|
||||
: m_hinstance(hinstance)
|
||||
, m_settings(settings)
|
||||
FancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings) noexcept :
|
||||
m_hinstance(hinstance),
|
||||
m_settings(settings)
|
||||
{
|
||||
m_settings->SetCallback(this);
|
||||
}
|
||||
@@ -38,7 +51,11 @@ public:
|
||||
IFACEMETHODIMP_(void) Destroy() noexcept;
|
||||
|
||||
// IFancyZonesCallback
|
||||
IFACEMETHODIMP_(bool) InMoveSize() noexcept { std::shared_lock readLock(m_lock); return m_inMoveSize; }
|
||||
IFACEMETHODIMP_(bool) InMoveSize() noexcept
|
||||
{
|
||||
std::shared_lock readLock(m_lock);
|
||||
return m_inMoveSize;
|
||||
}
|
||||
IFACEMETHODIMP_(void) MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen) noexcept;
|
||||
IFACEMETHODIMP_(void) MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) noexcept;
|
||||
IFACEMETHODIMP_(void) MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept;
|
||||
@@ -61,12 +78,16 @@ public:
|
||||
const auto nB = (tmp & 0xFF);
|
||||
return RGB(nR, nG, nB);
|
||||
}
|
||||
IFACEMETHODIMP_(GUID) GetCurrentMonitorZoneSetId(HMONITOR monitor) noexcept
|
||||
IFACEMETHODIMP_(IZoneWindow*)GetParentZoneWindow(HMONITOR monitor) noexcept
|
||||
{
|
||||
if (auto it = m_zoneWindowMap.find(monitor); it != m_zoneWindowMap.end() && it->second->ActiveZoneSet()) {
|
||||
return it->second->ActiveZoneSet()->Id();
|
||||
//NOTE: as public method it's unsafe without lock, but it's called from AddZoneWindow through making ZoneWindow that causes deadlock
|
||||
//TODO: needs refactoring
|
||||
auto it = m_zoneWindowMap.find(monitor);
|
||||
if (it != m_zoneWindowMap.end())
|
||||
{
|
||||
return it->second.get();
|
||||
}
|
||||
return GUID_NULL;
|
||||
return nullptr;
|
||||
}
|
||||
IFACEMETHODIMP_(int) GetZoneHighlightOpacity() noexcept
|
||||
{
|
||||
@@ -85,16 +106,25 @@ private:
|
||||
struct require_read_lock
|
||||
{
|
||||
template<typename T>
|
||||
require_read_lock(const std::shared_lock<T>& lock) { lock; }
|
||||
require_read_lock(const std::shared_lock<T>& lock)
|
||||
{
|
||||
lock;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
require_read_lock(const std::unique_lock<T>& lock) { lock; }
|
||||
require_read_lock(const std::unique_lock<T>& lock)
|
||||
{
|
||||
lock;
|
||||
}
|
||||
};
|
||||
|
||||
struct require_write_lock
|
||||
{
|
||||
template<typename T>
|
||||
require_write_lock(const std::unique_lock<T>& lock) { lock; }
|
||||
require_write_lock(const std::unique_lock<T>& lock)
|
||||
{
|
||||
lock;
|
||||
}
|
||||
};
|
||||
|
||||
bool IsInterestingWindow(HWND window) noexcept;
|
||||
@@ -107,6 +137,7 @@ private:
|
||||
void MoveSizeEndInternal(HWND window, POINT const& ptScreen, require_write_lock) noexcept;
|
||||
void MoveSizeUpdateInternal(HMONITOR monitor, POINT const& ptScreen, require_write_lock) noexcept;
|
||||
void HandleVirtualDesktopUpdates(HANDLE fancyZonesDestroyedEvent) noexcept;
|
||||
void OnEditorExitEvent() noexcept;
|
||||
|
||||
const HINSTANCE m_hinstance{};
|
||||
|
||||
@@ -115,11 +146,11 @@ private:
|
||||
mutable std::shared_mutex m_lock;
|
||||
HWND m_window{};
|
||||
HWND m_windowMoveSize{}; // The window that is being moved/sized
|
||||
bool m_inMoveSize{}; // Whether or not a move/size operation is currently active
|
||||
bool m_inMoveSize{}; // Whether or not a move/size operation is currently active
|
||||
bool m_dragEnabled{}; // True if we should be showing zone hints while dragging
|
||||
std::map<HMONITOR, winrt::com_ptr<IZoneWindow>> m_zoneWindowMap; // Map of monitor to ZoneWindow (one per monitor)
|
||||
winrt::com_ptr<IZoneWindow> m_zoneWindowMoveSize; // "Active" ZoneWindow, where the move/size is happening. Will update as drag moves between monitors.
|
||||
IFancyZonesSettings* m_settings{};
|
||||
winrt::com_ptr<IFancyZonesSettings> m_settings{};
|
||||
GUID m_currentVirtualDesktopId{}; // UUID of the current virtual desktop. Is GUID_NULL until first VD switch per session.
|
||||
std::unordered_map<GUID, bool> m_virtualDesktopIds;
|
||||
wil::unique_handle m_terminateEditorEvent; // Handle of FancyZonesEditor.exe we launch and wait on
|
||||
@@ -159,18 +190,21 @@ IFACEMETHODIMP_(void) FancyZones::Run() noexcept
|
||||
BufferedPaintInit();
|
||||
|
||||
m_window = CreateWindowExW(WS_EX_TOOLWINDOW, L"SuperFancyZones", L"", WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, m_hinstance, this);
|
||||
if (!m_window) return;
|
||||
if (!m_window)
|
||||
return;
|
||||
|
||||
RegisterHotKey(m_window, 1, m_settings->GetSettings().editorHotkey.get_modifiers(), m_settings->GetSettings().editorHotkey.get_code());
|
||||
|
||||
VirtualDesktopInitialize();
|
||||
|
||||
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{[]{
|
||||
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
|
||||
SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED);
|
||||
}}).wait();
|
||||
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [] {
|
||||
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
|
||||
SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED);
|
||||
} })
|
||||
.wait();
|
||||
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops", 0, KEY_ALL_ACCESS, &m_virtualDesktopsRegKey) == ERROR_SUCCESS) {
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops", 0, KEY_ALL_ACCESS, &m_virtualDesktopsRegKey) == ERROR_SUCCESS)
|
||||
{
|
||||
m_terminateVirtualDesktopTrackerEvent.reset(CreateEvent(nullptr, FALSE, FALSE, nullptr));
|
||||
m_virtualDesktopTrackerThread.submit(
|
||||
OnThreadExecutor::task_t{ std::bind(&FancyZones::HandleVirtualDesktopUpdates, this, m_terminateVirtualDesktopTrackerEvent.get()) });
|
||||
@@ -188,10 +222,12 @@ IFACEMETHODIMP_(void) FancyZones::Destroy() noexcept
|
||||
DestroyWindow(m_window);
|
||||
m_window = nullptr;
|
||||
}
|
||||
if (m_terminateVirtualDesktopTrackerEvent) {
|
||||
if (m_terminateVirtualDesktopTrackerEvent)
|
||||
{
|
||||
SetEvent(m_terminateVirtualDesktopTrackerEvent.get());
|
||||
}
|
||||
if (m_virtualDesktopsRegKey) {
|
||||
if (m_virtualDesktopsRegKey)
|
||||
{
|
||||
RegCloseKey(m_virtualDesktopsRegKey);
|
||||
m_virtualDesktopsRegKey = nullptr;
|
||||
}
|
||||
@@ -229,6 +265,7 @@ IFACEMETHODIMP_(void) FancyZones::VirtualDesktopChanged() noexcept
|
||||
{
|
||||
// VirtualDesktopChanged is called from another thread but results in new windows being created.
|
||||
// Jump over to the UI thread to handle it.
|
||||
std::shared_lock readLock(m_lock);
|
||||
PostMessage(m_window, WM_PRIV_VDCHANGED, 0, 0);
|
||||
}
|
||||
|
||||
@@ -243,14 +280,28 @@ IFACEMETHODIMP_(void) FancyZones::WindowCreated(HWND window) noexcept
|
||||
{
|
||||
if (m_settings->GetSettings().appLastZone_moveWindows && IsInterestingWindow(window))
|
||||
{
|
||||
auto processPath = get_process_path(window);
|
||||
if (!processPath.empty())
|
||||
auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
|
||||
if (monitor)
|
||||
{
|
||||
INT zoneIndex = -1;
|
||||
LRESULT res = RegistryHelpers::GetAppLastZone(window, processPath.data(), &zoneIndex);
|
||||
if ((res == ERROR_SUCCESS) && (zoneIndex != -1))
|
||||
auto zoneWindow = m_zoneWindowMap.find(monitor);
|
||||
if (zoneWindow != m_zoneWindowMap.end())
|
||||
{
|
||||
MoveWindowIntoZoneByIndex(window, zoneIndex);
|
||||
const auto& zoneWindowPtr = zoneWindow->second;
|
||||
const auto activeZoneSet = zoneWindowPtr->ActiveZoneSet();
|
||||
if (activeZoneSet)
|
||||
{
|
||||
const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance();
|
||||
|
||||
wil::unique_cotaskmem_string guidString;
|
||||
if (SUCCEEDED_LOG(StringFromCLSID(activeZoneSet->Id(), &guidString)))
|
||||
{
|
||||
int zoneIndex = fancyZonesData.GetAppLastZoneIndex(window, zoneWindowPtr->UniqueId(), guidString.get());
|
||||
if (zoneIndex != -1)
|
||||
{
|
||||
MoveWindowIntoZoneByIndex(window, zoneIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,11 +397,12 @@ void FancyZones::ToggleEditor() noexcept
|
||||
MONITORINFOEX mi;
|
||||
mi.cbSize = sizeof(mi);
|
||||
|
||||
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{[&]{
|
||||
GetMonitorInfo(monitor, &mi);
|
||||
}}).wait();
|
||||
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
|
||||
GetMonitorInfo(monitor, &mi);
|
||||
} })
|
||||
.wait();
|
||||
|
||||
if(use_cursorpos_editor_startupscreen)
|
||||
if (use_cursorpos_editor_startupscreen)
|
||||
{
|
||||
DPIAware::GetScreenDPIForPoint(currentCursorPos, dpi_x, dpi_y);
|
||||
}
|
||||
@@ -359,6 +411,11 @@ void FancyZones::ToggleEditor() noexcept
|
||||
DPIAware::GetScreenDPIForWindow(foregroundWindow, dpi_x, dpi_y);
|
||||
}
|
||||
|
||||
auto zoneWindow = iter->second;
|
||||
|
||||
const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance();
|
||||
fancyZonesData.CustomZoneSetsToJsonFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath());
|
||||
|
||||
const auto taskbar_x_offset = MulDiv(mi.rcWork.left - mi.rcMonitor.left, DPIAware::DEFAULT_DPI, dpi_x);
|
||||
const auto taskbar_y_offset = MulDiv(mi.rcWork.top - mi.rcMonitor.top, DPIAware::DEFAULT_DPI, dpi_y);
|
||||
|
||||
@@ -367,22 +424,24 @@ void FancyZones::ToggleEditor() noexcept
|
||||
const auto y = mi.rcMonitor.top + taskbar_y_offset;
|
||||
const auto width = mi.rcWork.right - mi.rcWork.left;
|
||||
const auto height = mi.rcWork.bottom - mi.rcWork.top;
|
||||
const std::wstring editorLocation =
|
||||
const std::wstring editorLocation =
|
||||
std::to_wstring(x) + L"_" +
|
||||
std::to_wstring(y) + L"_" +
|
||||
std::to_wstring(width) + L"_" +
|
||||
std::to_wstring(height);
|
||||
|
||||
const auto activeZoneSet = iter->second->ActiveZoneSet();
|
||||
const std::wstring layoutID = activeZoneSet ? std::to_wstring(activeZoneSet->LayoutId()) : L"0";
|
||||
const auto& deviceInfo = fancyZonesData.GetDeviceInfoMap().at(zoneWindow->UniqueId());
|
||||
|
||||
JSONHelpers::DeviceInfoJSON deviceInfoJson{ zoneWindow->UniqueId(), deviceInfo };
|
||||
fancyZonesData.SerializeDeviceInfoToTmpFile(deviceInfoJson, ZoneWindowUtils::GetActiveZoneSetTmpPath());
|
||||
|
||||
const std::wstring params =
|
||||
iter->second->UniqueId() + L" " +
|
||||
layoutID + L" " +
|
||||
std::to_wstring(reinterpret_cast<UINT_PTR>(monitor)) + L" " +
|
||||
editorLocation + L" " +
|
||||
iter->second->WorkAreaKey() + L" " +
|
||||
std::to_wstring(static_cast<float>(dpi_x) / DPIAware::DEFAULT_DPI);
|
||||
/*1*/ std::to_wstring(reinterpret_cast<UINT_PTR>(monitor)) + L" " +
|
||||
/*2*/ editorLocation + L" " +
|
||||
/*3*/ zoneWindow->WorkAreaKey() + L" " +
|
||||
/*4*/ ZoneWindowUtils::GetActiveZoneSetTmpPath() + L" " +
|
||||
/*5*/ ZoneWindowUtils::GetAppliedZoneSetTmpPath() + L" " +
|
||||
/*6*/ ZoneWindowUtils::GetCustomZoneSetsTmpPath();
|
||||
|
||||
SHELLEXECUTEINFO sei{ sizeof(sei) };
|
||||
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
|
||||
@@ -394,8 +453,7 @@ void FancyZones::ToggleEditor() noexcept
|
||||
// Launch the editor on a background thread
|
||||
// Wait for the editor's process to exit
|
||||
// Post back to the main thread to update
|
||||
std::thread waitForEditorThread([window = m_window, processHandle = sei.hProcess, terminateEditorEvent = m_terminateEditorEvent.get()]()
|
||||
{
|
||||
std::thread waitForEditorThread([window = m_window, processHandle = sei.hProcess, terminateEditorEvent = m_terminateEditorEvent.get()]() {
|
||||
HANDLE waitEvents[2] = { processHandle, terminateEditorEvent };
|
||||
auto result = WaitForMultipleObjects(2, waitEvents, false, INFINITE);
|
||||
if (result == WAIT_OBJECT_0 + 0)
|
||||
@@ -419,6 +477,7 @@ void FancyZones::ToggleEditor() noexcept
|
||||
|
||||
void FancyZones::SettingsChanged() noexcept
|
||||
{
|
||||
std::shared_lock readLock(m_lock);
|
||||
// Update the hotkey
|
||||
UnregisterHotKey(m_window, 1);
|
||||
RegisterHotKey(m_window, 1, m_settings->GetSettings().editorHotkey.get_modifiers(), m_settings->GetSettings().editorHotkey.get_code());
|
||||
@@ -475,7 +534,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
|
||||
{
|
||||
if (lparam == static_cast<LPARAM>(EditorExitKind::Exit))
|
||||
{
|
||||
// Don't reload settings if we terminated the editor
|
||||
OnEditorExitEvent();
|
||||
OnDisplayChange(DisplayChangeType::Editor);
|
||||
}
|
||||
|
||||
@@ -504,10 +563,10 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept
|
||||
// the first virtual desktop switch happens. If the user hasn't switched virtual desktops in this session
|
||||
// then this value will be empty. This means loading the first virtual desktop's configuration can be
|
||||
// funky the first time we load up at boot since the user will not have switched virtual desktops yet.
|
||||
std::shared_lock readLock(m_lock);
|
||||
GUID currentVirtualDesktopId{};
|
||||
if (SUCCEEDED(RegistryHelpers::GetCurrentVirtualDesktop(¤tVirtualDesktopId)))
|
||||
{
|
||||
std::unique_lock writeLock(m_lock);
|
||||
m_currentVirtualDesktopId = currentVirtualDesktopId;
|
||||
}
|
||||
else
|
||||
@@ -548,13 +607,19 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept
|
||||
wil::unique_cotaskmem_string virtualDesktopId;
|
||||
if (SUCCEEDED_LOG(StringFromCLSID(m_currentVirtualDesktopId, &virtualDesktopId)))
|
||||
{
|
||||
std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get());
|
||||
bool newVirtualDesktop = true;
|
||||
if (auto it = m_virtualDesktopIds.find(m_currentVirtualDesktopId); it != end(m_virtualDesktopIds)) {
|
||||
newVirtualDesktop = it->second;
|
||||
}
|
||||
const bool flash = m_settings->GetSettings().zoneSetChange_flashZones && newVirtualDesktop;
|
||||
|
||||
if (auto zoneWindow = MakeZoneWindow(this, m_hinstance, monitor, deviceId, virtualDesktopId.get(), flash))
|
||||
auto it = m_virtualDesktopIds.find(m_currentVirtualDesktopId);
|
||||
if (it != end(m_virtualDesktopIds))
|
||||
{
|
||||
newVirtualDesktop = it->second;
|
||||
JSONHelpers::FancyZonesDataInstance().SetActiveDeviceId(uniqueId);
|
||||
}
|
||||
|
||||
const bool flash = m_settings->GetSettings().zoneSetChange_flashZones && newVirtualDesktop;
|
||||
auto zoneWindow = MakeZoneWindow(this, m_hinstance, monitor, uniqueId, flash);
|
||||
if (zoneWindow)
|
||||
{
|
||||
m_zoneWindowMap[monitor] = std::move(zoneWindow);
|
||||
}
|
||||
@@ -567,12 +632,14 @@ void FancyZones::MoveWindowIntoZoneByIndex(HWND window, int index) noexcept
|
||||
std::shared_lock readLock(m_lock);
|
||||
if (window != m_windowMoveSize)
|
||||
{
|
||||
if (const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL))
|
||||
const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
|
||||
if (monitor)
|
||||
{
|
||||
auto iter = m_zoneWindowMap.find(monitor);
|
||||
if (iter != m_zoneWindowMap.end())
|
||||
{
|
||||
iter->second->MoveWindowIntoZoneByIndex(window, index);
|
||||
const auto& zoneWindowPtr = iter->second;
|
||||
zoneWindowPtr->MoveWindowIntoZoneByIndex(window, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -589,7 +656,7 @@ LRESULT CALLBACK FancyZones::s_WndProc(HWND window, UINT message, WPARAM wparam,
|
||||
}
|
||||
|
||||
return thisRef ? thisRef->WndProc(window, message, wparam, lparam) :
|
||||
DefWindowProc(window, message, wparam, lparam);
|
||||
DefWindowProc(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
bool FancyZones::IsInterestingWindow(HWND window) noexcept
|
||||
@@ -616,8 +683,7 @@ bool FancyZones::IsInterestingWindow(HWND window) noexcept
|
||||
|
||||
void FancyZones::UpdateZoneWindows() noexcept
|
||||
{
|
||||
auto callback = [](HMONITOR monitor, HDC, RECT *, LPARAM data) -> BOOL
|
||||
{
|
||||
auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL {
|
||||
MONITORINFOEX mi;
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (GetMonitorInfo(monitor, &mi))
|
||||
@@ -643,8 +709,8 @@ void FancyZones::UpdateZoneWindows() noexcept
|
||||
if (!deviceId)
|
||||
{
|
||||
deviceId = GetSystemMetrics(SM_REMOTESESSION) ?
|
||||
L"\\\\?\\DISPLAY#REMOTEDISPLAY#" :
|
||||
L"\\\\?\\DISPLAY#LOCALDISPLAY#";
|
||||
L"\\\\?\\DISPLAY#REMOTEDISPLAY#" :
|
||||
L"\\\\?\\DISPLAY#LOCALDISPLAY#";
|
||||
}
|
||||
|
||||
auto strongThis = reinterpret_cast<FancyZones*>(data);
|
||||
@@ -659,14 +725,13 @@ void FancyZones::UpdateZoneWindows() noexcept
|
||||
|
||||
void FancyZones::MoveWindowsOnDisplayChange() noexcept
|
||||
{
|
||||
auto callback = [](HWND window, LPARAM data) -> BOOL
|
||||
{
|
||||
auto callback = [](HWND window, LPARAM data) -> BOOL {
|
||||
int i = static_cast<int>(reinterpret_cast<UINT_PTR>(::GetProp(window, ZONE_STAMP)));
|
||||
if (i != 0)
|
||||
{
|
||||
// i is off by 1 since 0 is special.
|
||||
auto strongThis = reinterpret_cast<FancyZones*>(data);
|
||||
strongThis->MoveWindowIntoZoneByIndex(window, i-1);
|
||||
strongThis->MoveWindowIntoZoneByIndex(window, i - 1);
|
||||
}
|
||||
return TRUE;
|
||||
};
|
||||
@@ -683,7 +748,7 @@ void FancyZones::UpdateDragState(require_write_lock) noexcept
|
||||
const bool mouseX2 = GetAsyncKeyState(VK_XBUTTON2) & 0x8000;
|
||||
|
||||
// Note, Middle, X1 and X2 can also be used in addition to R/L
|
||||
bool mouse = mouseM | mouseX1 | mouseX2;
|
||||
bool mouse = mouseM | mouseX1 | mouseX2;
|
||||
// If the user has swapped their Right and Left Buttons, use the "Right" equivalent
|
||||
if (GetSystemMetrics(SM_SWAPBUTTON))
|
||||
{
|
||||
@@ -709,13 +774,16 @@ void FancyZones::CycleActiveZoneSet(DWORD vkCode) noexcept
|
||||
auto window = GetForegroundWindow();
|
||||
if (IsInterestingWindow(window))
|
||||
{
|
||||
if (const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL))
|
||||
const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
|
||||
if (monitor)
|
||||
{
|
||||
std::shared_lock readLock(m_lock);
|
||||
|
||||
auto iter = m_zoneWindowMap.find(monitor);
|
||||
if (iter != m_zoneWindowMap.end())
|
||||
{
|
||||
iter->second->CycleActiveZoneSet(vkCode);
|
||||
const auto& zoneWindowPtr = iter->second;
|
||||
zoneWindowPtr->CycleActiveZoneSet(vkCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -726,13 +794,16 @@ bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
|
||||
auto window = GetForegroundWindow();
|
||||
if (IsInterestingWindow(window))
|
||||
{
|
||||
if (const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL))
|
||||
const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
|
||||
if (monitor)
|
||||
{
|
||||
std::shared_lock readLock(m_lock);
|
||||
|
||||
auto iter = m_zoneWindowMap.find(monitor);
|
||||
if (iter != m_zoneWindowMap.end())
|
||||
{
|
||||
iter->second->MoveWindowIntoZoneByDirection(window, vkCode);
|
||||
const auto& zoneWindowPtr = iter->second;
|
||||
zoneWindowPtr->MoveWindowIntoZoneByDirection(window, vkCode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -798,10 +869,23 @@ void FancyZones::MoveSizeEndInternal(HWND window, POINT const& ptScreen, require
|
||||
{
|
||||
::RemoveProp(window, ZONE_STAMP);
|
||||
|
||||
auto processPath = get_process_path(window);
|
||||
if (!processPath.empty())
|
||||
auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
|
||||
if (monitor)
|
||||
{
|
||||
RegistryHelpers::SaveAppLastZone(window, processPath.data(), -1);
|
||||
auto zoneWindow = m_zoneWindowMap.find(monitor);
|
||||
if (zoneWindow != m_zoneWindowMap.end())
|
||||
{
|
||||
const auto zoneWindowPtr = zoneWindow->second;
|
||||
const auto activeZoneSet = zoneWindowPtr->ActiveZoneSet();
|
||||
if (activeZoneSet)
|
||||
{
|
||||
wil::unique_cotaskmem_string guidString;
|
||||
if (SUCCEEDED_LOG(StringFromCLSID(activeZoneSet->Id(), &guidString)))
|
||||
{
|
||||
JSONHelpers::FancyZonesDataInstance().RemoveAppLastZone(window, zoneWindowPtr->UniqueId(), guidString.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -853,38 +937,48 @@ void FancyZones::HandleVirtualDesktopUpdates(HANDLE fancyZonesDestroyedEvent) no
|
||||
{
|
||||
HANDLE regKeyEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
HANDLE events[2] = { regKeyEvent, fancyZonesDestroyedEvent };
|
||||
while (1) {
|
||||
if (RegNotifyChangeKeyValue(HKEY_CURRENT_USER, TRUE, REG_NOTIFY_CHANGE_LAST_SET, regKeyEvent, TRUE) != ERROR_SUCCESS) {
|
||||
while (1)
|
||||
{
|
||||
if (RegNotifyChangeKeyValue(HKEY_CURRENT_USER, TRUE, REG_NOTIFY_CHANGE_LAST_SET, regKeyEvent, TRUE) != ERROR_SUCCESS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (WaitForMultipleObjects(2, events, FALSE, INFINITE) != (WAIT_OBJECT_0 + 0)) {
|
||||
if (WaitForMultipleObjects(2, events, FALSE, INFINITE) != (WAIT_OBJECT_0 + 0))
|
||||
{
|
||||
// if fancyZonesDestroyedEvent is signalized or WaitForMultipleObjects failed, terminate thread execution
|
||||
return;
|
||||
}
|
||||
DWORD bufferCapacity;
|
||||
const WCHAR* key = L"VirtualDesktopIDs";
|
||||
// request regkey binary buffer capacity only
|
||||
if (RegQueryValueExW(m_virtualDesktopsRegKey, key, 0, nullptr, nullptr, &bufferCapacity) != ERROR_SUCCESS) {
|
||||
if (RegQueryValueExW(m_virtualDesktopsRegKey, key, 0, nullptr, nullptr, &bufferCapacity) != ERROR_SUCCESS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::unique_ptr<BYTE[]> buffer = std::make_unique<BYTE[]>(bufferCapacity);
|
||||
// request regkey binary content
|
||||
if (RegQueryValueExW(m_virtualDesktopsRegKey, key, 0, nullptr, buffer.get(), &bufferCapacity) != ERROR_SUCCESS) {
|
||||
if (RegQueryValueExW(m_virtualDesktopsRegKey, key, 0, nullptr, buffer.get(), &bufferCapacity) != ERROR_SUCCESS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const int guidSize = sizeof(GUID);
|
||||
std::unordered_map<GUID, bool> temp;
|
||||
temp.reserve(bufferCapacity / guidSize);
|
||||
for (size_t i = 0; i < bufferCapacity; i += guidSize) {
|
||||
GUID *guid = reinterpret_cast<GUID*>(buffer.get() + i);
|
||||
for (size_t i = 0; i < bufferCapacity; i += guidSize)
|
||||
{
|
||||
GUID* guid = reinterpret_cast<GUID*>(buffer.get() + i);
|
||||
temp[*guid] = true;
|
||||
}
|
||||
std::unique_lock writeLock(m_lock);
|
||||
for (auto it = begin(m_virtualDesktopIds); it != end(m_virtualDesktopIds);) {
|
||||
if (auto iter = temp.find(it->first); iter == temp.end()) {
|
||||
for (auto it = begin(m_virtualDesktopIds); it != end(m_virtualDesktopIds);)
|
||||
{
|
||||
auto iter = temp.find(it->first);
|
||||
if (iter == temp.end())
|
||||
{
|
||||
it = m_virtualDesktopIds.erase(it); // virtual desktop closed, remove it from map
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
temp.erase(it->first); // virtual desktop already in map, skip it
|
||||
++it;
|
||||
}
|
||||
@@ -894,7 +988,21 @@ void FancyZones::HandleVirtualDesktopUpdates(HANDLE fancyZonesDestroyedEvent) no
|
||||
}
|
||||
}
|
||||
|
||||
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, IFancyZonesSettings* settings) noexcept
|
||||
void FancyZones::OnEditorExitEvent() noexcept
|
||||
{
|
||||
// Colect information about changes in zone layout after editor exited.
|
||||
JSONHelpers::FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(ZoneWindowUtils::GetActiveZoneSetTmpPath());
|
||||
JSONHelpers::FancyZonesDataInstance().ParseDeletedCustomZoneSetsFromTmpFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath());
|
||||
JSONHelpers::FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(ZoneWindowUtils::GetAppliedZoneSetTmpPath());
|
||||
JSONHelpers::FancyZonesDataInstance().SaveFancyZonesData();
|
||||
}
|
||||
|
||||
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings) noexcept
|
||||
{
|
||||
if (!settings)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return winrt::make_self<FancyZones>(hinstance, settings);
|
||||
}
|
||||
@@ -2,15 +2,7 @@
|
||||
|
||||
interface IZoneWindow;
|
||||
interface IFancyZonesSettings;
|
||||
|
||||
enum class DisplayChangeType
|
||||
{
|
||||
WorkArea,
|
||||
DisplayChange,
|
||||
VirtualDesktop,
|
||||
Editor,
|
||||
Initialization
|
||||
};
|
||||
interface IZoneSet;
|
||||
|
||||
interface __declspec(uuid("{50D3F0F5-736E-4186-BDF4-3D6BEE150C3A}")) IFancyZones : public IUnknown
|
||||
{
|
||||
@@ -35,8 +27,8 @@ interface __declspec(uuid("{5C8D99D6-34B2-4F4A-A8E5-7483F6869775}")) IZoneWindow
|
||||
{
|
||||
IFACEMETHOD_(void, MoveWindowsOnActiveZoneSetChange)() = 0;
|
||||
IFACEMETHOD_(COLORREF, GetZoneHighlightColor)() = 0;
|
||||
IFACEMETHOD_(GUID, GetCurrentMonitorZoneSetId)(HMONITOR monitor) = 0;
|
||||
IFACEMETHOD_(IZoneWindow*, GetParentZoneWindow) (HMONITOR monitor) = 0;
|
||||
IFACEMETHOD_(int, GetZoneHighlightOpacity)() = 0;
|
||||
};
|
||||
|
||||
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, IFancyZonesSettings* settings) noexcept;
|
||||
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings) noexcept;
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="FancyZones.h" />
|
||||
<ClInclude Include="JsonHelpers.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="RegistryHelpers.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
@@ -104,12 +105,14 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="FancyZones.cpp" />
|
||||
<ClCompile Include="JsonHelpers.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Settings.cpp" />
|
||||
<ClCompile Include="trace.cpp" />
|
||||
<ClCompile Include="util.cpp" />
|
||||
<ClCompile Include="Zone.cpp" />
|
||||
<ClCompile Include="ZoneSet.cpp" />
|
||||
<ClCompile Include="ZoneWindow.cpp" />
|
||||
|
||||
@@ -45,6 +45,9 @@
|
||||
<ClInclude Include="trace.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="JsonHelpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
@@ -68,6 +71,12 @@
|
||||
<ClCompile Include="trace.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="JsonHelpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="util.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="fancyzones.rc">
|
||||
@@ -76,6 +85,5 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="fancyzones.def" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
950
src/modules/fancyzones/lib/JsonHelpers.cpp
Normal file
950
src/modules/fancyzones/lib/JsonHelpers.cpp
Normal file
@@ -0,0 +1,950 @@
|
||||
#include "pch.h"
|
||||
#include "JsonHelpers.h"
|
||||
#include "RegistryHelpers.h"
|
||||
#include "ZoneSet.h"
|
||||
|
||||
#include <common/common.h>
|
||||
|
||||
#include <shlwapi.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
namespace JSONHelpers
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
const std::wstring& FancyZonesData::GetPersistFancyZonesJSONPath() const
|
||||
{
|
||||
return jsonFilePath;
|
||||
}
|
||||
|
||||
json::JsonObject FancyZonesData::GetPersistFancyZonesJSON()
|
||||
{
|
||||
std::wstring save_file_path = GetPersistFancyZonesJSONPath();
|
||||
|
||||
auto result = json::from_file(save_file_path);
|
||||
if (result)
|
||||
{
|
||||
return *result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return json::JsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
void FancyZonesData::AddDevice(const std::wstring& deviceId)
|
||||
{
|
||||
if (!deviceInfoMap.contains(deviceId))
|
||||
{
|
||||
// Creates default entry in map when ZoneWindow is created
|
||||
deviceInfoMap[deviceId] = DeviceInfoData{ ZoneSetData{ L"null", ZoneSetLayoutType::Blank } };
|
||||
|
||||
MigrateDeviceInfoFromRegistry(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
void FancyZonesData::CloneDeviceInfo(const std::wstring& source, const std::wstring& destination)
|
||||
{
|
||||
// Clone information from source device if destination device is uninitialized (Blank).
|
||||
auto& destInfo = deviceInfoMap[destination];
|
||||
if (destInfo.activeZoneSet.type == ZoneSetLayoutType::Blank)
|
||||
{
|
||||
destInfo = deviceInfoMap[source];
|
||||
}
|
||||
}
|
||||
|
||||
int FancyZonesData::GetAppLastZoneIndex(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId) const
|
||||
{
|
||||
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)
|
||||
{
|
||||
return history->second.zoneIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool FancyZonesData::RemoveAppLastZone(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId)
|
||||
{
|
||||
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)
|
||||
{
|
||||
appZoneHistoryMap.erase(processPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FancyZonesData::SetAppLastZone(HWND window, const std::wstring& deviceId, const std::wstring& zoneSetId, int zoneIndex)
|
||||
{
|
||||
auto processPath = get_process_path(window);
|
||||
if (processPath.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
appZoneHistoryMap[processPath] = AppZoneHistoryData{ .zoneSetUuid = zoneSetId, .deviceId = deviceId, .zoneIndex = zoneIndex };
|
||||
return true;
|
||||
}
|
||||
|
||||
void FancyZonesData::SetActiveZoneSet(const std::wstring& deviceId, const ZoneSetData& data)
|
||||
{
|
||||
auto it = deviceInfoMap.find(deviceId);
|
||||
if (it != deviceInfoMap.end())
|
||||
{
|
||||
it->second.activeZoneSet = data;
|
||||
}
|
||||
}
|
||||
|
||||
void FancyZonesData::SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath) const
|
||||
{
|
||||
json::JsonObject deviceInfoJson = DeviceInfoJSON::ToJson(deviceInfo);
|
||||
json::to_file(tmpFilePath, deviceInfoJson);
|
||||
}
|
||||
|
||||
void FancyZonesData::ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
json::JsonArray DeviceInfosJSON{};
|
||||
|
||||
for (const auto& [deviceID, deviceData] : deviceInfoMap)
|
||||
{
|
||||
if (deviceData.activeZoneSet.type != ZoneSetLayoutType::Blank) {
|
||||
DeviceInfosJSON.Append(DeviceInfoJSON::DeviceInfoJSON::ToJson(DeviceInfoJSON{ deviceID, deviceData }));
|
||||
}
|
||||
}
|
||||
|
||||
return DeviceInfosJSON;
|
||||
}
|
||||
|
||||
bool FancyZonesData::ParseCustomZoneSets(const json::JsonObject& fancyZonesDataJSON)
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
const auto& customZoneSetsJson = SerializeCustomZoneSets();
|
||||
json::JsonObject root{};
|
||||
root.SetNamedValue(L"custom-zone-sets", customZoneSetsJson);
|
||||
json::to_file(filePath, root);
|
||||
}
|
||||
|
||||
void FancyZonesData::LoadFancyZonesData()
|
||||
{
|
||||
std::wstring jsonFilePath = GetPersistFancyZonesJSONPath();
|
||||
|
||||
if (!std::filesystem::exists(jsonFilePath))
|
||||
{
|
||||
TmpMigrateAppliedZoneSetsFromRegistry();
|
||||
|
||||
// Custom zone sets have to be migrated after applied zone sets!
|
||||
MigrateCustomZoneSetsFromRegistry();
|
||||
|
||||
SaveFancyZonesData();
|
||||
}
|
||||
else
|
||||
{
|
||||
json::JsonObject fancyZonesDataJSON = GetPersistFancyZonesJSON();
|
||||
|
||||
ParseAppZoneHistory(fancyZonesDataJSON);
|
||||
ParseDeviceInfos(fancyZonesDataJSON);
|
||||
ParseCustomZoneSets(fancyZonesDataJSON);
|
||||
}
|
||||
}
|
||||
|
||||
void FancyZonesData::SaveFancyZonesData() const
|
||||
{
|
||||
json::JsonObject root{};
|
||||
|
||||
root.SetNamedValue(L"app-zone-history", SerializeAppZoneHistory());
|
||||
root.SetNamedValue(L"devices", SerializeDeviceInfos());
|
||||
root.SetNamedValue(L"custom-zone-sets", SerializeCustomZoneSets());
|
||||
|
||||
json::to_file(jsonFilePath, root);
|
||||
}
|
||||
|
||||
void FancyZonesData::TmpMigrateAppliedZoneSetsFromRegistry()
|
||||
{
|
||||
std::wregex ex(L"^[0-9]{3,4}_[0-9]{3,4}$");
|
||||
|
||||
wchar_t key[256];
|
||||
StringCchPrintf(key, ARRAYSIZE(key), L"%s", RegistryHelpers::REG_SETTINGS);
|
||||
HKEY hkey;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS)
|
||||
{
|
||||
wchar_t resolutionKey[256]{};
|
||||
DWORD resolutionKeyLength = ARRAYSIZE(resolutionKey);
|
||||
DWORD i = 0;
|
||||
while (RegEnumKeyW(hkey, i++, resolutionKey, resolutionKeyLength) == ERROR_SUCCESS)
|
||||
{
|
||||
std::wstring resolution{ resolutionKey };
|
||||
wchar_t appliedZoneSetskey[256];
|
||||
StringCchPrintf(appliedZoneSetskey, ARRAYSIZE(appliedZoneSetskey), L"%s\\%s", RegistryHelpers::REG_SETTINGS, resolutionKey);
|
||||
HKEY appliedZoneSetsHkey;
|
||||
if (std::regex_match(resolution, ex) && RegOpenKeyExW(HKEY_CURRENT_USER, appliedZoneSetskey, 0, KEY_ALL_ACCESS, &appliedZoneSetsHkey) == ERROR_SUCCESS)
|
||||
{
|
||||
ZoneSetPersistedDataOLD data;
|
||||
DWORD dataSize = sizeof(data);
|
||||
wchar_t value[256]{};
|
||||
DWORD valueLength = ARRAYSIZE(value);
|
||||
DWORD i = 0;
|
||||
|
||||
while (RegEnumValueW(appliedZoneSetsHkey, i++, value, &valueLength, nullptr, nullptr, reinterpret_cast<BYTE*>(&data), &dataSize) == ERROR_SUCCESS)
|
||||
{
|
||||
ZoneSetData appliedZoneSetData;
|
||||
appliedZoneSetData.type = TypeFromLayoutId(data.LayoutId);
|
||||
if (appliedZoneSetData.type != ZoneSetLayoutType::Custom)
|
||||
{
|
||||
appliedZoneSetData.uuid = std::wstring{ value };
|
||||
}
|
||||
else
|
||||
{
|
||||
// uuid is changed later to actual uuid when migrating custom zone sets
|
||||
appliedZoneSetData.uuid = std::to_wstring(data.LayoutId);
|
||||
}
|
||||
appliedZoneSetsMap[value] = appliedZoneSetData;
|
||||
dataSize = sizeof(data);
|
||||
valueLength = ARRAYSIZE(value);
|
||||
}
|
||||
}
|
||||
resolutionKeyLength = ARRAYSIZE(resolutionKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FancyZonesData::MigrateDeviceInfoFromRegistry(const std::wstring& deviceId)
|
||||
{
|
||||
wchar_t key[256];
|
||||
StringCchPrintf(key, ARRAYSIZE(key), L"%s\\%s", RegistryHelpers::REG_SETTINGS, deviceId.c_str());
|
||||
|
||||
wchar_t activeZoneSetId[256];
|
||||
activeZoneSetId[0] = '\0';
|
||||
DWORD bufferSize = sizeof(activeZoneSetId);
|
||||
DWORD showSpacing = 1;
|
||||
DWORD spacing = 16;
|
||||
DWORD zoneCount = 3;
|
||||
DWORD size = sizeof(DWORD);
|
||||
|
||||
SHRegGetUSValueW(key, L"ActiveZoneSetId", nullptr, &activeZoneSetId, &bufferSize, FALSE, nullptr, 0);
|
||||
SHRegGetUSValueW(key, L"ShowSpacing", nullptr, &showSpacing, &size, FALSE, nullptr, 0);
|
||||
SHRegGetUSValueW(key, L"Spacing", nullptr, &spacing, &size, FALSE, nullptr, 0);
|
||||
SHRegGetUSValueW(key, L"ZoneCount", nullptr, &zoneCount, &size, FALSE, nullptr, 0);
|
||||
|
||||
if (appliedZoneSetsMap.contains(std::wstring{ activeZoneSetId }))
|
||||
{
|
||||
deviceInfoMap[deviceId] = DeviceInfoData{ appliedZoneSetsMap.at(std::wstring{ activeZoneSetId }), static_cast<bool>(showSpacing), static_cast<int>(spacing), static_cast<int>(zoneCount) };
|
||||
}
|
||||
}
|
||||
|
||||
void FancyZonesData::MigrateCustomZoneSetsFromRegistry()
|
||||
{
|
||||
wchar_t key[256];
|
||||
StringCchPrintf(key, ARRAYSIZE(key), L"%s\\%s", RegistryHelpers::REG_SETTINGS, L"Layouts");
|
||||
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
|
||||
|
||||
std::wstring uuid = std::to_wstring(data[3] * 256 + data[4]);
|
||||
auto it = std::find_if(appliedZoneSetsMap.begin(), appliedZoneSetsMap.end(), [&uuid](std::pair<std::wstring, ZoneSetData> zoneSetMap) {
|
||||
return zoneSetMap.second.uuid.compare(uuid) == 0;
|
||||
});
|
||||
|
||||
if (it != appliedZoneSetsMap.end())
|
||||
{
|
||||
it->second.uuid = uuid = it->first;
|
||||
}
|
||||
switch (zoneSetData.type)
|
||||
{
|
||||
case CustomLayoutType::Grid:
|
||||
{
|
||||
int j = 5;
|
||||
GridLayoutInfo zoneSetInfo(GridLayoutInfo::Minimal{ .rows = data[j++], .columns = data[j++] });
|
||||
|
||||
for (int row = 0; row < zoneSetInfo.rows(); row++)
|
||||
{
|
||||
zoneSetInfo.rowsPercents()[row] = data[j++] * 256 + data[j++];
|
||||
}
|
||||
|
||||
for (int col = 0; col < zoneSetInfo.columns(); col++)
|
||||
{
|
||||
zoneSetInfo.columnsPercents()[col] = data[j++] * 256 + data[j++];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
case CustomLayoutType::Canvas:
|
||||
{
|
||||
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:
|
||||
abort(); // TODO(stefan): Exception safety
|
||||
}
|
||||
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") });
|
||||
|
||||
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));
|
||||
result.SetNamedValue(L"zone-index", json::value(appZoneHistory.data.zoneIndex));
|
||||
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");
|
||||
result.data.zoneIndex = static_cast<int>(zoneSet.GetNamedNumber(L"zone-index"));
|
||||
result.data.deviceId = zoneSet.GetNamedString(L"device-id");
|
||||
result.data.zoneSetUuid = zoneSet.GetNamedString(L"zoneset-uuid");
|
||||
|
||||
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");
|
||||
|
||||
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)
|
||||
{
|
||||
case CustomLayoutType::Canvas:
|
||||
{
|
||||
result.SetNamedValue(L"type", json::value(L"canvas"));
|
||||
|
||||
CanvasLayoutInfo info = std::get<CanvasLayoutInfo>(customZoneSet.data.info);
|
||||
result.SetNamedValue(L"info", CanvasLayoutInfo::ToJson(info));
|
||||
|
||||
break;
|
||||
}
|
||||
case CustomLayoutType::Grid:
|
||||
{
|
||||
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");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
240
src/modules/fancyzones/lib/JsonHelpers.h
Normal file
240
src/modules/fancyzones/lib/JsonHelpers.h
Normal file
@@ -0,0 +1,240 @@
|
||||
#pragma once
|
||||
|
||||
#include <common/settings_helpers.h>
|
||||
#include <common/json.h>
|
||||
|
||||
#include <string>
|
||||
#include <strsafe.h>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <winnt.h>
|
||||
|
||||
namespace JSONHelpers
|
||||
{
|
||||
constexpr int MAX_ZONE_COUNT = 50;
|
||||
|
||||
enum class ZoneSetLayoutType : int
|
||||
{
|
||||
Blank = -1,
|
||||
Focus,
|
||||
Columns,
|
||||
Rows,
|
||||
Grid,
|
||||
PriorityGrid,
|
||||
Custom
|
||||
};
|
||||
|
||||
enum class CustomLayoutType : int
|
||||
{
|
||||
Grid = 0,
|
||||
Canvas
|
||||
};
|
||||
|
||||
std::wstring TypeToString(ZoneSetLayoutType type);
|
||||
ZoneSetLayoutType TypeFromString(const std::wstring& typeStr);
|
||||
|
||||
ZoneSetLayoutType TypeFromLayoutId(int layoutID);
|
||||
|
||||
struct CanvasLayoutInfo
|
||||
{
|
||||
int referenceWidth;
|
||||
int referenceHeight;
|
||||
struct Rect
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
std::vector<CanvasLayoutInfo::Rect> zones;
|
||||
|
||||
static json::JsonObject ToJson(const CanvasLayoutInfo& canvasInfo);
|
||||
static std::optional<CanvasLayoutInfo> FromJson(const json::JsonObject& infoJson);
|
||||
};
|
||||
|
||||
class GridLayoutInfo
|
||||
{
|
||||
public:
|
||||
struct Minimal
|
||||
{
|
||||
int rows;
|
||||
int columns;
|
||||
};
|
||||
|
||||
struct Full
|
||||
{
|
||||
int rows;
|
||||
int columns;
|
||||
const std::vector<int>& rowsPercents;
|
||||
const std::vector<int>& columnsPercents;
|
||||
const std::vector<std::vector<int>>& cellChildMap;
|
||||
};
|
||||
|
||||
GridLayoutInfo(const Minimal& info);
|
||||
GridLayoutInfo(const Full& info);
|
||||
~GridLayoutInfo() = default;
|
||||
|
||||
static json::JsonObject ToJson(const GridLayoutInfo& gridInfo);
|
||||
static std::optional<GridLayoutInfo> FromJson(const json::JsonObject& infoJson);
|
||||
|
||||
inline std::vector<int>& rowsPercents() { return m_rowsPercents; };
|
||||
inline std::vector<int>& columnsPercents() { return m_columnsPercents; };
|
||||
inline std::vector<std::vector<int>>& cellChildMap() { return m_cellChildMap; };
|
||||
|
||||
inline int rows() const { return m_rows; }
|
||||
inline int columns() const { return m_columns; }
|
||||
|
||||
inline const std::vector<int>& rowsPercents() const { return m_rowsPercents; };
|
||||
inline const std::vector<int>& columnsPercents() const { return m_columnsPercents; };
|
||||
inline const std::vector<std::vector<int>>& cellChildMap() const { return m_cellChildMap; };
|
||||
|
||||
private:
|
||||
int m_rows;
|
||||
int m_columns;
|
||||
std::vector<int> m_rowsPercents;
|
||||
std::vector<int> m_columnsPercents;
|
||||
std::vector<std::vector<int>> m_cellChildMap;
|
||||
};
|
||||
|
||||
struct CustomZoneSetData
|
||||
{
|
||||
std::wstring name;
|
||||
CustomLayoutType type;
|
||||
std::variant<CanvasLayoutInfo, GridLayoutInfo> info;
|
||||
};
|
||||
|
||||
struct CustomZoneSetJSON
|
||||
{
|
||||
std::wstring uuid;
|
||||
CustomZoneSetData data;
|
||||
|
||||
static json::JsonObject ToJson(const CustomZoneSetJSON& device);
|
||||
static std::optional<CustomZoneSetJSON> FromJson(const json::JsonObject& customZoneSet);
|
||||
};
|
||||
|
||||
// TODO(stefan): This needs to be moved to ZoneSet.h (probably)
|
||||
struct ZoneSetData
|
||||
{
|
||||
std::wstring uuid;
|
||||
ZoneSetLayoutType type;
|
||||
|
||||
static json::JsonObject ToJson(const ZoneSetData& zoneSet);
|
||||
static std::optional<ZoneSetData> FromJson(const json::JsonObject& zoneSet);
|
||||
};
|
||||
|
||||
struct AppZoneHistoryData
|
||||
{
|
||||
std::wstring zoneSetUuid;
|
||||
std::wstring deviceId;
|
||||
int zoneIndex;
|
||||
};
|
||||
|
||||
struct AppZoneHistoryJSON
|
||||
{
|
||||
std::wstring appPath;
|
||||
AppZoneHistoryData data;
|
||||
|
||||
static json::JsonObject ToJson(const AppZoneHistoryJSON& appZoneHistory);
|
||||
static std::optional<AppZoneHistoryJSON> FromJson(const json::JsonObject& zoneSet);
|
||||
};
|
||||
|
||||
struct DeviceInfoData
|
||||
{
|
||||
ZoneSetData activeZoneSet;
|
||||
bool showSpacing;
|
||||
int spacing;
|
||||
int zoneCount;
|
||||
};
|
||||
|
||||
struct DeviceInfoJSON
|
||||
{
|
||||
std::wstring deviceId;
|
||||
DeviceInfoData data;
|
||||
|
||||
static json::JsonObject ToJson(const DeviceInfoJSON& device);
|
||||
static std::optional<DeviceInfoJSON> FromJson(const json::JsonObject& device);
|
||||
};
|
||||
|
||||
class FancyZonesData
|
||||
{
|
||||
public:
|
||||
FancyZonesData();
|
||||
|
||||
const std::wstring& GetPersistFancyZonesJSONPath() const;
|
||||
json::JsonObject GetPersistFancyZonesJSON();
|
||||
|
||||
inline const std::unordered_map<std::wstring, DeviceInfoData>& GetDeviceInfoMap() const
|
||||
{
|
||||
return deviceInfoMap;
|
||||
}
|
||||
|
||||
inline const std::unordered_map<std::wstring, CustomZoneSetData>& GetCustomZoneSetsMap() const
|
||||
{
|
||||
return customZoneSetsMap;
|
||||
}
|
||||
|
||||
inline const std::unordered_map<std::wstring, AppZoneHistoryData>& GetAppZoneHistoryMap() const
|
||||
{
|
||||
return appZoneHistoryMap;
|
||||
}
|
||||
|
||||
inline const std::wstring GetActiveDeviceId() const
|
||||
{
|
||||
return activeDeviceId;
|
||||
}
|
||||
|
||||
void SetActiveDeviceId(const std::wstring& deviceId)
|
||||
{
|
||||
activeDeviceId = deviceId;
|
||||
}
|
||||
|
||||
inline bool DeleteTmpFile(std::wstring_view tmpFilePath) const
|
||||
{
|
||||
return DeleteFileW(tmpFilePath.data());
|
||||
}
|
||||
|
||||
void AddDevice(const std::wstring& deviceId);
|
||||
void CloneDeviceInfo(const std::wstring& source, const std::wstring& destination);
|
||||
|
||||
int GetAppLastZoneIndex(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId) const;
|
||||
bool RemoveAppLastZone(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId);
|
||||
bool SetAppLastZone(HWND window, const std::wstring& deviceId, const std::wstring& zoneSetId, int zoneIndex);
|
||||
|
||||
void SetActiveZoneSet(const std::wstring& deviceId, const ZoneSetData& zoneSet);
|
||||
|
||||
void SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath) const;
|
||||
|
||||
void ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath);
|
||||
bool ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath);
|
||||
bool ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath);
|
||||
|
||||
bool ParseAppZoneHistory(const json::JsonObject& fancyZonesDataJSON);
|
||||
json::JsonArray SerializeAppZoneHistory() const;
|
||||
bool ParseDeviceInfos(const json::JsonObject& fancyZonesDataJSON);
|
||||
json::JsonArray SerializeDeviceInfos() const;
|
||||
bool ParseCustomZoneSets(const json::JsonObject& fancyZonesDataJSON);
|
||||
json::JsonArray SerializeCustomZoneSets() const;
|
||||
void CustomZoneSetsToJsonFile(std::wstring_view filePath) const;
|
||||
|
||||
void LoadFancyZonesData();
|
||||
void SaveFancyZonesData() const;
|
||||
|
||||
void MigrateDeviceInfoFromRegistry(const std::wstring& deviceId);
|
||||
|
||||
private:
|
||||
void TmpMigrateAppliedZoneSetsFromRegistry();
|
||||
void MigrateCustomZoneSetsFromRegistry();
|
||||
|
||||
std::unordered_map<std::wstring, ZoneSetData> appliedZoneSetsMap{};
|
||||
std::unordered_map<std::wstring, AppZoneHistoryData> appZoneHistoryMap{};
|
||||
std::unordered_map<std::wstring, DeviceInfoData> deviceInfoMap{};
|
||||
std::unordered_map<std::wstring, CustomZoneSetData> customZoneSetsMap{};
|
||||
|
||||
std::wstring activeDeviceId;
|
||||
std::wstring jsonFilePath;
|
||||
};
|
||||
|
||||
FancyZonesData& FancyZonesDataInstance();
|
||||
}
|
||||
@@ -7,132 +7,6 @@ namespace RegistryHelpers
|
||||
static PCWSTR REG_SETTINGS = L"Software\\SuperFancyZones";
|
||||
static PCWSTR APP_ZONE_HISTORY_SUBKEY = L"AppZoneHistory";
|
||||
|
||||
inline PCWSTR GetKey(_In_opt_ PCWSTR monitorId, PWSTR key, size_t keyLength)
|
||||
{
|
||||
if (monitorId)
|
||||
{
|
||||
StringCchPrintf(key, keyLength, L"%s\\%s", REG_SETTINGS, monitorId);
|
||||
}
|
||||
else
|
||||
{
|
||||
StringCchPrintf(key, keyLength, L"%s", REG_SETTINGS);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
inline HKEY OpenKey(_In_opt_ PCWSTR monitorId)
|
||||
{
|
||||
HKEY hkey;
|
||||
wchar_t key[256];
|
||||
GetKey(monitorId, key, ARRAYSIZE(key));
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS)
|
||||
{
|
||||
return hkey;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline HKEY CreateKey(PCWSTR monitorId)
|
||||
{
|
||||
HKEY hkey;
|
||||
wchar_t key[256]{};
|
||||
GetKey(monitorId, key, ARRAYSIZE(key));
|
||||
if (RegCreateKeyExW(HKEY_CURRENT_USER, key, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &hkey, nullptr) == ERROR_SUCCESS)
|
||||
{
|
||||
return hkey;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline LSTATUS GetAppLastZone(HWND window, PCWSTR appPath, _Out_ PINT iZoneIndex)
|
||||
{
|
||||
*iZoneIndex = -1;
|
||||
|
||||
LSTATUS res{};
|
||||
if (auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL))
|
||||
{
|
||||
wchar_t keyPath[256]{};
|
||||
StringCchPrintf(keyPath, ARRAYSIZE(keyPath), L"%s\\%s\\%x", REG_SETTINGS, APP_ZONE_HISTORY_SUBKEY, monitor);
|
||||
|
||||
DWORD zoneIndex;
|
||||
DWORD dataType = REG_DWORD;
|
||||
DWORD dataSize = sizeof(DWORD);
|
||||
res = SHRegGetUSValueW(keyPath, appPath, &dataType, &zoneIndex, &dataSize, FALSE, nullptr, 0);
|
||||
if (res == ERROR_SUCCESS)
|
||||
{
|
||||
*iZoneIndex = static_cast<INT>(zoneIndex);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Pass -1 for the zoneIndex to delete the entry from the registry
|
||||
inline void SaveAppLastZone(HWND window, PCWSTR appPath, DWORD zoneIndex)
|
||||
{
|
||||
LSTATUS res{};
|
||||
if (auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL))
|
||||
{
|
||||
wchar_t keyPath[256]{};
|
||||
StringCchPrintf(keyPath, ARRAYSIZE(keyPath), L"%s\\%s\\%x", REG_SETTINGS, APP_ZONE_HISTORY_SUBKEY, monitor);
|
||||
if (zoneIndex == -1)
|
||||
{
|
||||
SHDeleteValueW(HKEY_CURRENT_USER, keyPath, appPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
SHRegSetUSValueW(keyPath, appPath, REG_DWORD, &zoneIndex, sizeof(zoneIndex), SHREGSET_FORCE_HKCU);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void GetString(PCWSTR uniqueId, PCWSTR setting, PWSTR value, DWORD cbValue)
|
||||
{
|
||||
wchar_t key[256]{};
|
||||
GetKey(uniqueId, key, ARRAYSIZE(key));
|
||||
SHRegGetUSValueW(key, setting, nullptr, value, &cbValue, FALSE, nullptr, 0);
|
||||
}
|
||||
|
||||
inline void SetString(PCWSTR uniqueId, PCWSTR setting, PCWSTR value)
|
||||
{
|
||||
wchar_t key[256]{};
|
||||
GetKey(uniqueId, key, ARRAYSIZE(key));
|
||||
SHRegSetUSValueW(key, setting, REG_SZ, value, sizeof(value) * static_cast<DWORD>(wcslen(value)), SHREGSET_FORCE_HKCU);
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
inline void GetValue(PCWSTR monitorId, PCWSTR setting, t* value, DWORD size)
|
||||
{
|
||||
wchar_t key[256]{};
|
||||
GetKey(monitorId, key, ARRAYSIZE(key));
|
||||
SHRegGetUSValueW(key, setting, nullptr, value, &size, FALSE, nullptr, 0);
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
inline void SetValue(PCWSTR monitorId, PCWSTR setting, t value, DWORD size)
|
||||
{
|
||||
wchar_t key[256]{};
|
||||
GetKey(monitorId, key, ARRAYSIZE(key));
|
||||
SHRegSetUSValueW(key, setting, REG_BINARY, &value, size, SHREGSET_FORCE_HKCU);
|
||||
}
|
||||
|
||||
inline void DeleteZoneSet(PCWSTR monitorId, GUID guid)
|
||||
{
|
||||
wil::unique_cotaskmem_string zoneSetId;
|
||||
if (SUCCEEDED_LOG(StringFromCLSID(guid, &zoneSetId)))
|
||||
{
|
||||
wchar_t key[256]{};
|
||||
GetKey(monitorId, key, ARRAYSIZE(key));
|
||||
SHDeleteValueW(HKEY_CURRENT_USER, key, zoneSetId.get());
|
||||
}
|
||||
}
|
||||
|
||||
inline void DeleteAllZoneSets(PCWSTR monitorId)
|
||||
{
|
||||
wchar_t key[256]{};
|
||||
GetKey(monitorId, key, ARRAYSIZE(key));
|
||||
SHDeleteKey(HKEY_CURRENT_USER, key);
|
||||
}
|
||||
|
||||
inline HRESULT GetCurrentVirtualDesktop(_Out_ GUID* id)
|
||||
{
|
||||
*id = GUID_NULL;
|
||||
|
||||
@@ -9,11 +9,11 @@ struct FancyZonesSettings : winrt::implements<FancyZonesSettings, IFancyZonesSet
|
||||
public:
|
||||
FancyZonesSettings(HINSTANCE hinstance, PCWSTR name)
|
||||
: m_hinstance(hinstance)
|
||||
, m_name(name)
|
||||
, m_moduleName(name)
|
||||
{
|
||||
LoadSettings(name, true /*fromFile*/);
|
||||
LoadSettings(name, true);
|
||||
}
|
||||
|
||||
|
||||
IFACEMETHODIMP_(void) SetCallback(IFancyZonesCallback* callback) { m_callback = callback; }
|
||||
IFACEMETHODIMP_(bool) GetConfig(_Out_ PWSTR buffer, _Out_ int *buffer_sizeg) noexcept;
|
||||
IFACEMETHODIMP_(void) SetConfig(PCWSTR config) noexcept;
|
||||
@@ -26,7 +26,7 @@ private:
|
||||
|
||||
IFancyZonesCallback* m_callback{};
|
||||
const HINSTANCE m_hinstance;
|
||||
PCWSTR m_name{};
|
||||
PCWSTR m_moduleName{};
|
||||
|
||||
Settings m_settings;
|
||||
|
||||
@@ -54,7 +54,7 @@ private:
|
||||
|
||||
IFACEMETHODIMP_(bool) FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ int *buffer_size) noexcept
|
||||
{
|
||||
PowerToysSettings::Settings settings(m_hinstance, m_name);
|
||||
PowerToysSettings::Settings settings(m_hinstance, m_moduleName);
|
||||
|
||||
// Pass a string literal or a resource id to Settings::set_description().
|
||||
settings.set_description(IDS_SETTING_DESCRIPTION);
|
||||
@@ -84,9 +84,9 @@ IFACEMETHODIMP_(bool) FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ in
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(void) FancyZonesSettings::SetConfig(PCWSTR config) noexcept try
|
||||
IFACEMETHODIMP_(void) FancyZonesSettings::SetConfig(PCWSTR serializedPowerToysSettingsJson) noexcept try
|
||||
{
|
||||
LoadSettings(config, false /*fromFile*/);
|
||||
LoadSettings(serializedPowerToysSettingsJson, false /*fromFile*/);
|
||||
SaveSettings();
|
||||
if (m_callback)
|
||||
{
|
||||
@@ -112,7 +112,7 @@ CATCH_LOG();
|
||||
void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept try
|
||||
{
|
||||
PowerToysSettings::PowerToyValues values = fromFile ?
|
||||
PowerToysSettings::PowerToyValues::load_from_settings_file(m_name) :
|
||||
PowerToysSettings::PowerToyValues::load_from_settings_file(m_moduleName) :
|
||||
PowerToysSettings::PowerToyValues::from_json_string(config);
|
||||
|
||||
for (auto const& setting : m_configBools)
|
||||
@@ -165,7 +165,7 @@ CATCH_LOG();
|
||||
|
||||
void FancyZonesSettings::SaveSettings() noexcept try
|
||||
{
|
||||
PowerToysSettings::PowerToyValues values(m_name);
|
||||
PowerToysSettings::PowerToyValues values(m_moduleName);
|
||||
|
||||
for (auto const& setting : m_configBools)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ interface __declspec(uuid("{BA4E77C4-6F44-4C5D-93D3-CBDE880495C2}")) IFancyZones
|
||||
{
|
||||
IFACEMETHOD_(void, SetCallback)(interface IFancyZonesCallback* callback) = 0;
|
||||
IFACEMETHOD_(bool, GetConfig)(_Out_ PWSTR buffer, _Out_ int *buffer_size) = 0;
|
||||
IFACEMETHOD_(void, SetConfig)(PCWSTR config) = 0;
|
||||
IFACEMETHOD_(void, SetConfig)(PCWSTR serializedPowerToysSettingsJson) = 0;
|
||||
IFACEMETHOD_(void, CallCustomAction)(PCWSTR action) = 0;
|
||||
IFACEMETHOD_(Settings, GetSettings)() = 0;
|
||||
};
|
||||
|
||||
@@ -129,7 +129,7 @@ void Zone::StampZone(HWND window, bool stamp) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
winrt::com_ptr<IZone> MakeZone(RECT zoneRect) noexcept
|
||||
winrt::com_ptr<IZone> MakeZone(const RECT& zoneRect) noexcept
|
||||
{
|
||||
return winrt::make_self<Zone>(zoneRect);
|
||||
}
|
||||
|
||||
@@ -11,4 +11,4 @@ interface __declspec(uuid("{8228E934-B6EF-402A-9892-15A1441BF8B0}")) IZone : pub
|
||||
IFACEMETHOD_(size_t, Id)() = 0;
|
||||
};
|
||||
|
||||
winrt::com_ptr<IZone> MakeZone(RECT zoneRect) noexcept;
|
||||
winrt::com_ptr<IZone> MakeZone(const RECT& zoneRect) noexcept;
|
||||
|
||||
@@ -1,12 +1,113 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "util.h"
|
||||
#include "lib/ZoneSet.h"
|
||||
#include "lib/RegistryHelpers.h"
|
||||
|
||||
#include <common/dpi_aware.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int C_MULTIPLIER = 10000;
|
||||
|
||||
/*
|
||||
struct GridLayoutInfo {
|
||||
int rows;
|
||||
int columns;
|
||||
int rowsPercents[MAX_ZONE_COUNT];
|
||||
int columnsPercents[MAX_ZONE_COUNT];
|
||||
int cellChildMap[MAX_ZONE_COUNT][MAX_ZONE_COUNT];
|
||||
};
|
||||
*/
|
||||
|
||||
auto l = JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Minimal{ .rows = 1, .columns = 1 });
|
||||
// PriorityGrid layout is unique for zoneCount <= 11. For zoneCount > 11 PriorityGrid is same as Grid
|
||||
JSONHelpers::GridLayoutInfo predefinedPriorityGridLayouts[11] = {
|
||||
/* 1 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 1,
|
||||
.columns = 1,
|
||||
.rowsPercents = { 10000 },
|
||||
.columnsPercents = { 10000 },
|
||||
.cellChildMap = { { 0 } } }),
|
||||
/* 2 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 1,
|
||||
.columns = 2,
|
||||
.rowsPercents = { 10000 },
|
||||
.columnsPercents = { 6667, 3333 },
|
||||
.cellChildMap = { { 0, 1 } } }),
|
||||
/* 3 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 1,
|
||||
.columns = 3,
|
||||
.rowsPercents = { 10000 },
|
||||
.columnsPercents = { 2500, 5000, 2500 },
|
||||
.cellChildMap = { { 0, 1, 2 } } }),
|
||||
/* 4 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 2,
|
||||
.columns = 3,
|
||||
.rowsPercents = { 5000, 5000 },
|
||||
.columnsPercents = { 2500, 5000, 2500 },
|
||||
.cellChildMap = { { 0, 1, 2 }, { 0, 1, 3 } } }),
|
||||
/* 5 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 2,
|
||||
.columns = 3,
|
||||
.rowsPercents = { 5000, 5000 },
|
||||
.columnsPercents = { 2500, 5000, 2500 },
|
||||
.cellChildMap = { { 0, 1, 2 }, { 3, 1, 4 } } }),
|
||||
/* 6 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 3,
|
||||
.columns = 3,
|
||||
.rowsPercents = { 3333, 3334, 3333 },
|
||||
.columnsPercents = { 2500, 5000, 2500 },
|
||||
.cellChildMap = { { 0, 1, 2 }, { 0, 1, 3 }, { 4, 1, 5 } } }),
|
||||
/* 7 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 3,
|
||||
.columns = 3,
|
||||
.rowsPercents = { 3333, 3334, 3333 },
|
||||
.columnsPercents = { 2500, 5000, 2500 },
|
||||
.cellChildMap = { { 0, 1, 2 }, { 3, 1, 4 }, { 5, 1, 6 } } }),
|
||||
/* 8 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 3,
|
||||
.columns = 4,
|
||||
.rowsPercents = { 3333, 3334, 3333 },
|
||||
.columnsPercents = { 2500, 2500, 2500, 2500 },
|
||||
.cellChildMap = { { 0, 1, 2, 3 }, { 4, 1, 2, 5 }, { 6, 1, 2, 7 } } }),
|
||||
/* 9 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 3,
|
||||
.columns = 4,
|
||||
.rowsPercents = { 3333, 3334, 3333 },
|
||||
.columnsPercents = { 2500, 2500, 2500, 2500 },
|
||||
.cellChildMap = { { 0, 1, 2, 3 }, { 4, 1, 2, 5 }, { 6, 1, 7, 8 } } }),
|
||||
/* 10 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 3,
|
||||
.columns = 4,
|
||||
.rowsPercents = { 3333, 3334, 3333 },
|
||||
.columnsPercents = { 2500, 2500, 2500, 2500 },
|
||||
.cellChildMap = { { 0, 1, 2, 3 }, { 4, 1, 5, 6 }, { 7, 1, 8, 9 } } }),
|
||||
/* 11 */
|
||||
JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{
|
||||
.rows = 3,
|
||||
.columns = 4,
|
||||
.rowsPercents = { 3333, 3334, 3333 },
|
||||
.columnsPercents = { 2500, 2500, 2500, 2500 },
|
||||
.cellChildMap = { { 0, 1, 2, 3 }, { 4, 1, 5, 6 }, { 7, 8, 9, 10 } } }),
|
||||
};
|
||||
}
|
||||
|
||||
struct ZoneSet : winrt::implements<ZoneSet, IZoneSet>
|
||||
{
|
||||
public:
|
||||
ZoneSet(ZoneSetConfig const& config) : m_config(config)
|
||||
ZoneSet(ZoneSetConfig const& config) :
|
||||
m_config(config)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -16,18 +117,35 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(GUID) Id() noexcept { return m_config.Id; }
|
||||
IFACEMETHODIMP_(WORD) LayoutId() noexcept { return m_config.LayoutId; }
|
||||
IFACEMETHODIMP_(GUID)
|
||||
Id() noexcept { return m_config.Id; }
|
||||
IFACEMETHODIMP_(JSONHelpers::ZoneSetLayoutType)
|
||||
LayoutType() noexcept { return m_config.LayoutType; }
|
||||
IFACEMETHODIMP AddZone(winrt::com_ptr<IZone> zone) noexcept;
|
||||
IFACEMETHODIMP_(winrt::com_ptr<IZone>) ZoneFromPoint(POINT pt) noexcept;
|
||||
IFACEMETHODIMP_(int) GetZoneIndexFromWindow(HWND window) noexcept;
|
||||
IFACEMETHODIMP_(std::vector<winrt::com_ptr<IZone>>) GetZones() noexcept { return m_zones; }
|
||||
IFACEMETHODIMP_(void) Save() noexcept;
|
||||
IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndex(HWND window, HWND zoneWindow, int index) noexcept;
|
||||
IFACEMETHODIMP_(void) MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode) noexcept;
|
||||
IFACEMETHODIMP_(void) MoveSizeEnd(HWND window, HWND zoneWindow, POINT ptClient) noexcept;
|
||||
IFACEMETHODIMP_(winrt::com_ptr<IZone>)
|
||||
ZoneFromPoint(POINT pt) noexcept;
|
||||
IFACEMETHODIMP_(int)
|
||||
GetZoneIndexFromWindow(HWND window) noexcept;
|
||||
IFACEMETHODIMP_(std::vector<winrt::com_ptr<IZone>>)
|
||||
GetZones() noexcept { return m_zones; }
|
||||
IFACEMETHODIMP_(void)
|
||||
MoveWindowIntoZoneByIndex(HWND window, HWND zoneWindow, int index) noexcept;
|
||||
IFACEMETHODIMP_(void)
|
||||
MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode) noexcept;
|
||||
IFACEMETHODIMP_(void)
|
||||
MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept;
|
||||
IFACEMETHODIMP_(bool)
|
||||
CalculateZones(MONITORINFO monitorInfo, int zoneCount, int spacing) noexcept;
|
||||
|
||||
private:
|
||||
bool CalculateFocusLayout(Rect workArea, int zoneCount) noexcept;
|
||||
bool CalculateColumnsAndRowsLayout(Rect workArea, JSONHelpers::ZoneSetLayoutType type, int zoneCount, int spacing) noexcept;
|
||||
bool CalculateGridLayout(Rect workArea, JSONHelpers::ZoneSetLayoutType type, int zoneCount, int spacing) noexcept;
|
||||
bool CalculateUniquePriorityGridLayout(Rect workArea, int zoneCount, int spacing) noexcept;
|
||||
bool CalculateCustomLayout(Rect workArea, int spacing) noexcept;
|
||||
|
||||
bool CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo gridLayoutInfo, int spacing);
|
||||
|
||||
winrt::com_ptr<IZone> ZoneFromWindow(HWND window) noexcept;
|
||||
|
||||
std::vector<winrt::com_ptr<IZone>> m_zones;
|
||||
@@ -44,7 +162,8 @@ IFACEMETHODIMP ZoneSet::AddZone(winrt::com_ptr<IZone> zone) noexcept
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(winrt::com_ptr<IZone>) ZoneSet::ZoneFromPoint(POINT pt) noexcept
|
||||
IFACEMETHODIMP_(winrt::com_ptr<IZone>)
|
||||
ZoneSet::ZoneFromPoint(POINT pt) noexcept
|
||||
{
|
||||
winrt::com_ptr<IZone> smallestKnownZone = nullptr;
|
||||
// To reduce redundant calculations, we will store the last known zones area.
|
||||
@@ -61,16 +180,16 @@ IFACEMETHODIMP_(winrt::com_ptr<IZone>) ZoneSet::ZoneFromPoint(POINT pt) noexcept
|
||||
smallestKnownZone = zone;
|
||||
|
||||
RECT* r = &smallestKnownZone->GetZoneRect();
|
||||
smallestKnownZoneArea = (r->right-r->left)*(r->bottom-r->top);
|
||||
smallestKnownZoneArea = (r->right - r->left) * (r->bottom - r->top);
|
||||
}
|
||||
else
|
||||
{
|
||||
int newZoneArea = (newZoneRect->right-newZoneRect->left)*(newZoneRect->bottom-newZoneRect->top);
|
||||
int newZoneArea = (newZoneRect->right - newZoneRect->left) * (newZoneRect->bottom - newZoneRect->top);
|
||||
|
||||
if (newZoneArea<smallestKnownZoneArea)
|
||||
if (newZoneArea < smallestKnownZoneArea)
|
||||
{
|
||||
smallestKnownZone = zone;
|
||||
newZoneArea = smallestKnownZoneArea;
|
||||
smallestKnownZoneArea = newZoneArea;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,38 +199,8 @@ IFACEMETHODIMP_(winrt::com_ptr<IZone>) ZoneSet::ZoneFromPoint(POINT pt) noexcept
|
||||
return smallestKnownZone;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(void) ZoneSet::Save() noexcept
|
||||
{
|
||||
size_t const zoneCount = m_zones.size();
|
||||
if (zoneCount == 0)
|
||||
{
|
||||
RegistryHelpers::DeleteZoneSet(m_config.ResolutionKey, m_config.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
ZoneSetPersistedData data{};
|
||||
data.LayoutId = m_config.LayoutId;
|
||||
data.ZoneCount = static_cast<DWORD>(zoneCount);
|
||||
|
||||
int i = 0;
|
||||
for (auto iter = m_zones.begin(); iter != m_zones.end(); iter++)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = iter->as<IZone>();
|
||||
CopyRect(&data.Zones[i++], &zone->GetZoneRect());
|
||||
}
|
||||
|
||||
wil::unique_cotaskmem_string guid;
|
||||
if (SUCCEEDED_LOG(StringFromCLSID(m_config.Id, &guid)))
|
||||
{
|
||||
if (wil::unique_hkey hkey{ RegistryHelpers::CreateKey(m_config.ResolutionKey) })
|
||||
{
|
||||
RegSetValueExW(hkey.get(), guid.get(), 0, REG_BINARY, reinterpret_cast<BYTE*>(&data), sizeof(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(int) ZoneSet::GetZoneIndexFromWindow(HWND window) noexcept
|
||||
IFACEMETHODIMP_(int)
|
||||
ZoneSet::GetZoneIndexFromWindow(HWND window) noexcept
|
||||
{
|
||||
int zoneIndex = 0;
|
||||
for (auto iter = m_zones.begin(); iter != m_zones.end(); iter++, zoneIndex++)
|
||||
@@ -127,26 +216,40 @@ IFACEMETHODIMP_(int) ZoneSet::GetZoneIndexFromWindow(HWND window) noexcept
|
||||
return -1;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(void) ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noexcept
|
||||
IFACEMETHODIMP_(void)
|
||||
ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noexcept
|
||||
{
|
||||
if (index >= static_cast<int>(m_zones.size()))
|
||||
if (m_zones.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (index >= int(m_zones.size()))
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if (index < m_zones.size())
|
||||
while (auto zoneDrop = ZoneFromWindow(window))
|
||||
{
|
||||
if (auto zone = m_zones.at(index))
|
||||
{
|
||||
zone->AddWindowToZone(window, windowZone, false);
|
||||
}
|
||||
zoneDrop->RemoveWindowFromZone(window, !IsZoomed(window));
|
||||
}
|
||||
|
||||
if (auto zone = m_zones.at(index))
|
||||
{
|
||||
zone->AddWindowToZone(window, windowZone, false);
|
||||
}
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(void) ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode) noexcept
|
||||
IFACEMETHODIMP_(void)
|
||||
ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode) noexcept
|
||||
{
|
||||
winrt::com_ptr<IZone> oldZone;
|
||||
winrt::com_ptr<IZone> newZone;
|
||||
if (m_zones.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IZone> oldZone = nullptr;
|
||||
winrt::com_ptr<IZone> newZone = nullptr;
|
||||
|
||||
auto iter = std::find(m_zones.begin(), m_zones.end(), ZoneFromWindow(window));
|
||||
if (iter == m_zones.end())
|
||||
@@ -183,9 +286,10 @@ IFACEMETHODIMP_(void) ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND w
|
||||
}
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(void) ZoneSet::MoveSizeEnd(HWND window, HWND zoneWindow, POINT ptClient) noexcept
|
||||
IFACEMETHODIMP_(void)
|
||||
ZoneSet::MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept
|
||||
{
|
||||
if (auto zoneDrop = ZoneFromWindow(window))
|
||||
while (auto zoneDrop = ZoneFromWindow(window))
|
||||
{
|
||||
zoneDrop->RemoveWindowFromZone(window, !IsZoomed(window));
|
||||
}
|
||||
@@ -196,6 +300,314 @@ IFACEMETHODIMP_(void) ZoneSet::MoveSizeEnd(HWND window, HWND zoneWindow, POINT p
|
||||
}
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(bool)
|
||||
ZoneSet::CalculateZones(MONITORINFO monitorInfo, int zoneCount, int spacing) noexcept
|
||||
{
|
||||
Rect const workArea(monitorInfo.rcWork);
|
||||
//invalid work area
|
||||
if (workArea.width() == 0 || workArea.height() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//invalid zoneCount, may cause division by zero
|
||||
if (zoneCount <= 0 && m_config.LayoutType != JSONHelpers::ZoneSetLayoutType::Custom)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
switch (m_config.LayoutType)
|
||||
{
|
||||
case JSONHelpers::ZoneSetLayoutType::Focus:
|
||||
success = CalculateFocusLayout(workArea, zoneCount);
|
||||
break;
|
||||
case JSONHelpers::ZoneSetLayoutType::Columns:
|
||||
case JSONHelpers::ZoneSetLayoutType::Rows:
|
||||
success = CalculateColumnsAndRowsLayout(workArea, m_config.LayoutType, zoneCount, spacing);
|
||||
break;
|
||||
case JSONHelpers::ZoneSetLayoutType::Grid:
|
||||
case JSONHelpers::ZoneSetLayoutType::PriorityGrid:
|
||||
success = CalculateGridLayout(workArea, m_config.LayoutType, zoneCount, spacing);
|
||||
break;
|
||||
case JSONHelpers::ZoneSetLayoutType::Custom:
|
||||
success = CalculateCustomLayout(workArea, spacing);
|
||||
break;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ZoneSet::CalculateFocusLayout(Rect workArea, int zoneCount) noexcept
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
long left{ long(workArea.width() * 0.1) };
|
||||
long top{ long(workArea.height() * 0.1) };
|
||||
long right{ long(workArea.width() * 0.6) };
|
||||
long bottom{ long(workArea.height() * 0.6) };
|
||||
|
||||
RECT focusZoneRect{ left, top, right, bottom };
|
||||
|
||||
long focusRectXIncrement = (zoneCount <= 1) ? 0 : (int)(workArea.width() * 0.2) / (zoneCount - 1);
|
||||
long focusRectYIncrement = (zoneCount <= 1) ? 0 : (int)(workArea.height() * 0.2) / (zoneCount - 1);
|
||||
|
||||
if (left >= right || top >= bottom || left < 0 || right < 0 || top < 0 || bottom < 0)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < zoneCount; i++)
|
||||
{
|
||||
AddZone(MakeZone(focusZoneRect));
|
||||
focusZoneRect.left += focusRectXIncrement;
|
||||
focusZoneRect.right += focusRectXIncrement;
|
||||
focusZoneRect.bottom += focusRectYIncrement;
|
||||
focusZoneRect.top += focusRectYIncrement;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ZoneSet::CalculateColumnsAndRowsLayout(Rect workArea, JSONHelpers::ZoneSetLayoutType type, int zoneCount, int spacing) noexcept
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
int zonePercent = C_MULTIPLIER / zoneCount;
|
||||
|
||||
long totalWidth;
|
||||
long totalHeight;
|
||||
|
||||
long cellWidth;
|
||||
long cellHeight;
|
||||
|
||||
if (type == JSONHelpers::ZoneSetLayoutType::Columns)
|
||||
{
|
||||
totalWidth = workArea.width() - (spacing * (zoneCount + 1));
|
||||
totalHeight = workArea.height() - (spacing * 2);
|
||||
cellWidth = totalWidth * zonePercent / C_MULTIPLIER;
|
||||
cellHeight = totalHeight;
|
||||
}
|
||||
else
|
||||
{ //Rows
|
||||
totalWidth = workArea.width() - (spacing * 2);
|
||||
totalHeight = workArea.height() - (spacing * (zoneCount + 1));
|
||||
cellWidth = totalWidth;
|
||||
cellHeight = totalHeight * zonePercent / C_MULTIPLIER;
|
||||
}
|
||||
|
||||
long top = spacing;
|
||||
long left = spacing;
|
||||
long bottom = top + cellHeight;
|
||||
long right = left + cellWidth;
|
||||
|
||||
for (int zone = 0; zone < zoneCount; zone++)
|
||||
{
|
||||
if (left >= right || top >= bottom || left < 0 || right < 0 || top < 0 || bottom < 0)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
RECT focusZoneRect{ left, top, right, bottom };
|
||||
AddZone(MakeZone(focusZoneRect));
|
||||
|
||||
if (type == JSONHelpers::ZoneSetLayoutType::Columns)
|
||||
{
|
||||
left += cellWidth + spacing;
|
||||
right = left + cellWidth;
|
||||
}
|
||||
else
|
||||
{ //Rows
|
||||
top += cellHeight + spacing;
|
||||
bottom = top + cellHeight;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ZoneSet::CalculateGridLayout(Rect workArea, JSONHelpers::ZoneSetLayoutType type, int zoneCount, int spacing) noexcept
|
||||
{
|
||||
const auto count = sizeof(predefinedPriorityGridLayouts) / sizeof(JSONHelpers::GridLayoutInfo);
|
||||
if (type == JSONHelpers::ZoneSetLayoutType::PriorityGrid && zoneCount < count)
|
||||
{
|
||||
return CalculateUniquePriorityGridLayout(workArea, zoneCount, spacing);
|
||||
}
|
||||
|
||||
int rows = 1, columns = 1;
|
||||
while (zoneCount / rows >= rows)
|
||||
{
|
||||
rows++;
|
||||
}
|
||||
rows--;
|
||||
columns = zoneCount / rows;
|
||||
if (zoneCount % rows == 0)
|
||||
{
|
||||
// even grid
|
||||
}
|
||||
else
|
||||
{
|
||||
columns++;
|
||||
}
|
||||
|
||||
JSONHelpers::GridLayoutInfo gridLayoutInfo(JSONHelpers::GridLayoutInfo::Minimal{ .rows = rows, .columns = columns });
|
||||
|
||||
for (int row = 0; row < rows; row++)
|
||||
{
|
||||
gridLayoutInfo.rowsPercents()[row] = C_MULTIPLIER / rows;
|
||||
}
|
||||
for (int col = 0; col < columns; col++)
|
||||
{
|
||||
gridLayoutInfo.columnsPercents()[col] = C_MULTIPLIER / columns;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rows; ++i)
|
||||
{
|
||||
gridLayoutInfo.cellChildMap()[i] = std::vector<int>(columns);
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (int col = columns - 1; col >= 0; col--)
|
||||
{
|
||||
for (int row = rows - 1; row >= 0; row--)
|
||||
{
|
||||
gridLayoutInfo.cellChildMap()[row][col] = index++;
|
||||
if (index == zoneCount)
|
||||
{
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return CalculateGridZones(workArea, gridLayoutInfo, spacing);
|
||||
}
|
||||
|
||||
bool ZoneSet::CalculateUniquePriorityGridLayout(Rect workArea, int zoneCount, int spacing) noexcept
|
||||
{
|
||||
if (zoneCount <= 0 || zoneCount >= sizeof(predefinedPriorityGridLayouts))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return CalculateGridZones(workArea, predefinedPriorityGridLayouts[zoneCount - 1], spacing);
|
||||
}
|
||||
|
||||
bool ZoneSet::CalculateCustomLayout(Rect workArea, int spacing) noexcept
|
||||
{
|
||||
wil::unique_cotaskmem_string guuidStr;
|
||||
if (SUCCEEDED_LOG(StringFromCLSID(m_config.Id, &guuidStr)))
|
||||
{
|
||||
const std::wstring guuid = guuidStr.get();
|
||||
const auto& customZoneSets = JSONHelpers::FancyZonesDataInstance().GetCustomZoneSetsMap();
|
||||
if (!customZoneSets.contains(guuid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& zoneSet = customZoneSets.at(guuid);
|
||||
if (zoneSet.type == JSONHelpers::CustomLayoutType::Canvas && std::holds_alternative<JSONHelpers::CanvasLayoutInfo>(zoneSet.info))
|
||||
{
|
||||
const auto& zoneSetInfo = std::get<JSONHelpers::CanvasLayoutInfo>(zoneSet.info);
|
||||
for (const auto& zone : zoneSetInfo.zones)
|
||||
{
|
||||
int x = zone.x;
|
||||
int y = zone.y;
|
||||
int width = zone.width;
|
||||
int height = zone.height;
|
||||
|
||||
if (x < 0 || y < 0 || width < 0 || height < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DPIAware::Convert(m_config.Monitor, x, y);
|
||||
DPIAware::Convert(m_config.Monitor, width, height);
|
||||
|
||||
AddZone(MakeZone(RECT{ x, y, x + width, y + height }));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (zoneSet.type == JSONHelpers::CustomLayoutType::Grid && std::holds_alternative<JSONHelpers::GridLayoutInfo>(zoneSet.info))
|
||||
{
|
||||
const auto& info = std::get<JSONHelpers::GridLayoutInfo>(zoneSet.info);
|
||||
return CalculateGridZones(workArea, info, spacing);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ZoneSet::CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo gridLayoutInfo, int spacing)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
long totalWidth = workArea.width() - (spacing * (gridLayoutInfo.columns() + 1));
|
||||
long totalHeight = workArea.height() - (spacing * (gridLayoutInfo.rows() + 1));
|
||||
struct Info
|
||||
{
|
||||
long Extent;
|
||||
long Start;
|
||||
long End;
|
||||
};
|
||||
Info rowInfo[JSONHelpers::MAX_ZONE_COUNT];
|
||||
Info columnInfo[JSONHelpers::MAX_ZONE_COUNT];
|
||||
|
||||
long top = spacing;
|
||||
for (int row = 0; row < gridLayoutInfo.rows(); row++)
|
||||
{
|
||||
rowInfo[row].Start = top;
|
||||
rowInfo[row].Extent = totalHeight * gridLayoutInfo.rowsPercents()[row] / C_MULTIPLIER;
|
||||
rowInfo[row].End = rowInfo[row].Start + rowInfo[row].Extent;
|
||||
top += rowInfo[row].Extent + spacing;
|
||||
}
|
||||
|
||||
long left = spacing;
|
||||
for (int col = 0; col < gridLayoutInfo.columns(); col++)
|
||||
{
|
||||
columnInfo[col].Start = left;
|
||||
columnInfo[col].Extent = totalWidth * gridLayoutInfo.columnsPercents()[col] / C_MULTIPLIER;
|
||||
columnInfo[col].End = columnInfo[col].Start + columnInfo[col].Extent;
|
||||
left += columnInfo[col].Extent + spacing;
|
||||
}
|
||||
|
||||
for (int row = 0; row < gridLayoutInfo.rows(); row++)
|
||||
{
|
||||
for (int col = 0; col < gridLayoutInfo.columns(); col++)
|
||||
{
|
||||
int i = gridLayoutInfo.cellChildMap()[row][col];
|
||||
if (((row == 0) || (gridLayoutInfo.cellChildMap()[row - 1][col] != i)) &&
|
||||
((col == 0) || (gridLayoutInfo.cellChildMap()[row][col - 1] != i)))
|
||||
{
|
||||
left = columnInfo[col].Start;
|
||||
top = rowInfo[row].Start;
|
||||
|
||||
int maxRow = row;
|
||||
while (((maxRow + 1) < gridLayoutInfo.rows()) && (gridLayoutInfo.cellChildMap()[maxRow + 1][col] == i))
|
||||
{
|
||||
maxRow++;
|
||||
}
|
||||
int maxCol = col;
|
||||
while (((maxCol + 1) < gridLayoutInfo.columns()) && (gridLayoutInfo.cellChildMap()[row][maxCol + 1] == i))
|
||||
{
|
||||
maxCol++;
|
||||
}
|
||||
|
||||
long right = columnInfo[maxCol].End;
|
||||
long bottom = rowInfo[maxRow].End;
|
||||
|
||||
if (left >= right || top >= bottom || left < 0 || right < 0 || top < 0 || bottom < 0)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
AddZone(MakeZone(RECT{ left, top, right, bottom }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IZone> ZoneSet::ZoneFromWindow(HWND window) noexcept
|
||||
{
|
||||
for (auto iter = m_zones.begin(); iter != m_zones.end(); iter++)
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "Zone.h"
|
||||
#include "JsonHelpers.h"
|
||||
|
||||
enum class ZoneSetLayout
|
||||
{
|
||||
Grid,
|
||||
Row,
|
||||
Focus,
|
||||
Custom
|
||||
};
|
||||
|
||||
interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : public IUnknown
|
||||
{
|
||||
IFACEMETHOD_(GUID, Id)() = 0;
|
||||
IFACEMETHOD_(WORD, LayoutId)() = 0;
|
||||
IFACEMETHOD_(JSONHelpers::ZoneSetLayoutType, LayoutType)() = 0;
|
||||
IFACEMETHOD(AddZone)(winrt::com_ptr<IZone> zone) = 0;
|
||||
IFACEMETHOD_(winrt::com_ptr<IZone>, ZoneFromPoint)(POINT pt) = 0;
|
||||
IFACEMETHOD_(int, GetZoneIndexFromWindow)(HWND window) = 0;
|
||||
IFACEMETHOD_(std::vector<winrt::com_ptr<IZone>>, GetZones)() = 0;
|
||||
IFACEMETHOD_(void, Save)() = 0;
|
||||
IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(HWND window, HWND zoneWindow, int index) = 0;
|
||||
IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(HWND window, HWND zoneWindow, DWORD vkCode) = 0;
|
||||
IFACEMETHOD_(void, MoveSizeEnd)(HWND window, HWND zoneWindow, POINT ptClient) = 0;
|
||||
IFACEMETHOD_(void, MoveWindowIntoZoneByPoint)(HWND window, HWND zoneWindow, POINT ptClient) = 0;
|
||||
IFACEMETHOD_(bool, CalculateZones)(MONITORINFO monitorInfo, int zoneCount, int spacing) = 0;
|
||||
};
|
||||
|
||||
#define VERSION_PERSISTEDDATA 0x0000F00D
|
||||
@@ -32,28 +26,39 @@ struct ZoneSetPersistedData
|
||||
DWORD Version{VERSION_PERSISTEDDATA};
|
||||
WORD LayoutId{};
|
||||
DWORD ZoneCount{};
|
||||
ZoneSetLayout Layout{};
|
||||
JSONHelpers::ZoneSetLayoutType Layout{};
|
||||
RECT Zones[MAX_ZONES]{};
|
||||
};
|
||||
|
||||
struct ZoneSetPersistedDataOLD
|
||||
{
|
||||
static constexpr inline size_t MAX_ZONES = 40;
|
||||
DWORD Version{ VERSION_PERSISTEDDATA };
|
||||
WORD LayoutId{};
|
||||
DWORD ZoneCount{};
|
||||
JSONHelpers::ZoneSetLayoutType Layout{};
|
||||
DWORD PaddingInner{};
|
||||
DWORD PaddingOuter{};
|
||||
RECT Zones[MAX_ZONES]{};
|
||||
};
|
||||
|
||||
|
||||
struct ZoneSetConfig
|
||||
{
|
||||
ZoneSetConfig(
|
||||
GUID id,
|
||||
WORD layoutId,
|
||||
JSONHelpers::ZoneSetLayoutType layoutType,
|
||||
HMONITOR monitor,
|
||||
PCWSTR resolutionKey) noexcept :
|
||||
Id(id),
|
||||
LayoutId(layoutId),
|
||||
LayoutType(layoutType),
|
||||
Monitor(monitor),
|
||||
ResolutionKey(resolutionKey)
|
||||
{
|
||||
}
|
||||
|
||||
GUID Id{};
|
||||
WORD LayoutId{};
|
||||
JSONHelpers::ZoneSetLayoutType LayoutType{};
|
||||
HMONITOR Monitor{};
|
||||
PCWSTR ResolutionKey{};
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,14 @@
|
||||
#include "FancyZones.h"
|
||||
#include "lib/ZoneSet.h"
|
||||
|
||||
namespace ZoneWindowUtils
|
||||
{
|
||||
const std::wstring& GetActiveZoneSetTmpPath();
|
||||
const std::wstring& GetAppliedZoneSetTmpPath();
|
||||
const std::wstring& GetCustomZoneSetsTmpPath();
|
||||
std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId);
|
||||
}
|
||||
|
||||
interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow : public IUnknown
|
||||
{
|
||||
IFACEMETHOD(MoveSizeEnter)(HWND window, bool dragEnabled) = 0;
|
||||
@@ -13,11 +21,10 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
|
||||
IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(HWND window, DWORD vkCode) = 0;
|
||||
IFACEMETHOD_(void, CycleActiveZoneSet)(DWORD vkCode) = 0;
|
||||
IFACEMETHOD_(void, SaveWindowProcessToZoneIndex)(HWND window) = 0;
|
||||
IFACEMETHOD_(std::wstring, DeviceId)() = 0;
|
||||
IFACEMETHOD_(std::wstring, UniqueId)() = 0;
|
||||
IFACEMETHOD_(std::wstring, WorkAreaKey)() = 0;
|
||||
IFACEMETHOD_(IZoneSet*, ActiveZoneSet)() = 0;
|
||||
};
|
||||
|
||||
winrt::com_ptr<IZoneWindow> MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor,
|
||||
PCWSTR deviceId, PCWSTR virtualDesktopId, bool flashZones) noexcept;
|
||||
const std::wstring& uniqueId, bool flashZones) noexcept;
|
||||
|
||||
27
src/modules/fancyzones/lib/util.cpp
Normal file
27
src/modules/fancyzones/lib/util.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "pch.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <common/dpi_aware.h>
|
||||
|
||||
typedef BOOL(WINAPI* GetDpiForMonitorInternalFunc)(HMONITOR, UINT, UINT*, UINT*);
|
||||
UINT GetDpiForMonitor(HMONITOR monitor) noexcept
|
||||
{
|
||||
UINT dpi{};
|
||||
if (wil::unique_hmodule user32{ LoadLibrary(L"user32.dll") })
|
||||
{
|
||||
if (auto func = reinterpret_cast<GetDpiForMonitorInternalFunc>(GetProcAddress(user32.get(), "GetDpiForMonitorInternal")))
|
||||
{
|
||||
func(monitor, 0, &dpi, &dpi);
|
||||
}
|
||||
}
|
||||
|
||||
if (dpi == 0)
|
||||
{
|
||||
if (wil::unique_hdc hdc{ GetDC(nullptr) })
|
||||
{
|
||||
dpi = GetDeviceCaps(hdc.get(), LOGPIXELSX);
|
||||
}
|
||||
}
|
||||
|
||||
return (dpi == 0) ? DPIAware::DEFAULT_DPI : dpi;
|
||||
}
|
||||
@@ -117,6 +117,12 @@ inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
|
||||
// We're interested in the unique part between the first and last #'s
|
||||
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
|
||||
// Example output: DELA026#5&10a58c63&0&UID16777488
|
||||
const std::wstring defaultDeviceId = L"FallbackDevice";
|
||||
if (!deviceId)
|
||||
{
|
||||
StringCchCopy(parsedId, size, defaultDeviceId.c_str());
|
||||
return;
|
||||
}
|
||||
wchar_t buffer[256];
|
||||
StringCchCopy(buffer, 256, deviceId);
|
||||
|
||||
@@ -130,12 +136,14 @@ inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
|
||||
}
|
||||
else
|
||||
{
|
||||
StringCchCopy(parsedId, size, L"FallbackDevice");
|
||||
StringCchCopy(parsedId, size, defaultDeviceId.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
inline int OpacitySettingToAlpha(int opacity)
|
||||
{
|
||||
// convert percentage to a 0-255 alpha value
|
||||
return opacity * 2.55;
|
||||
return static_cast<int>(opacity * 2.55);
|
||||
}
|
||||
|
||||
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
|
||||
Reference in New Issue
Block a user