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:
stefansjfw
2020-02-10 14:59:51 +01:00
committed by GitHub
parent a5e3207715
commit 53f830bb38
41 changed files with 8496 additions and 1905 deletions

View File

@@ -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(&currentVirtualDesktopId)))
{
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);
}

View File

@@ -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;

View File

@@ -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" />

View File

@@ -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>

View 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;
}
}
}

View 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();
}

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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++)

View File

@@ -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

View File

@@ -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;

View 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;
}

View File

@@ -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;