mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-01-18 06:29:44 +08: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:
parent
a5e3207715
commit
53f830bb38
@ -6,6 +6,8 @@
|
||||
|
||||
namespace PTSettingsHelper {
|
||||
|
||||
std::wstring get_module_save_folder_location(std::wstring_view powertoy_name);
|
||||
|
||||
void save_module_settings(std::wstring_view powertoy_name, json::JsonObject& settings);
|
||||
json::JsonObject load_module_settings(std::wstring_view powertoy_name);
|
||||
void save_general_settings(const json::JsonObject& settings);
|
||||
|
@ -71,7 +71,6 @@
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
<AdditionalDependencies>gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<ModuleDefinitionFile>fancyzones.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
@ -96,7 +95,6 @@
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
<AdditionalDependencies>gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<ModuleDefinitionFile>fancyzones.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
@ -121,7 +119,6 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="fancyzones.def" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -26,7 +26,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="fancyzones.def" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="..\lib\fancyzones.rc" />
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include <interface/lowlevel_keyboard_event_data.h>
|
||||
#include <interface/win_hook_event_data.h>
|
||||
#include <lib/ZoneSet.h>
|
||||
#include <lib/RegistryHelpers.h>
|
||||
|
||||
#include <lib/resource.h>
|
||||
#include <lib/trace.h>
|
||||
@ -33,76 +32,6 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// This function is exported and called from FancyZonesEditor.exe to save a layout from the editor.
|
||||
STDAPI PersistZoneSet(
|
||||
PCWSTR activeKey, // Registry key holding ActiveZoneSet
|
||||
PCWSTR resolutionKey, // Registry key to persist ZoneSet to
|
||||
HMONITOR monitor,
|
||||
WORD layoutId, // LayoutModel Id
|
||||
int zoneCount, // Number of zones in zones
|
||||
int zones[]) // Array of zones serialized in left/top/right/bottom chunks
|
||||
{
|
||||
// See if we have already persisted this layout we can update.
|
||||
UUID id{GUID_NULL};
|
||||
if (wil::unique_hkey key{ RegistryHelpers::OpenKey(resolutionKey) })
|
||||
{
|
||||
ZoneSetPersistedData data{};
|
||||
DWORD dataSize = sizeof(data);
|
||||
wchar_t value[256]{};
|
||||
DWORD valueLength = ARRAYSIZE(value);
|
||||
DWORD i = 0;
|
||||
while (RegEnumValueW(key.get(), i++, value, &valueLength, nullptr, nullptr, reinterpret_cast<BYTE*>(&data), &dataSize) == ERROR_SUCCESS)
|
||||
{
|
||||
if (data.LayoutId == layoutId)
|
||||
{
|
||||
if (data.ZoneCount == zoneCount)
|
||||
{
|
||||
CLSIDFromString(value, &id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
valueLength = ARRAYSIZE(value);
|
||||
dataSize = sizeof(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (id == GUID_NULL)
|
||||
{
|
||||
// No existing layout found so let's create a new one.
|
||||
UuidCreate(&id);
|
||||
}
|
||||
|
||||
if (id != GUID_NULL)
|
||||
{
|
||||
winrt::com_ptr<IZoneSet> zoneSet = MakeZoneSet(
|
||||
ZoneSetConfig(
|
||||
id,
|
||||
layoutId,
|
||||
MonitorFromPoint({}, MONITOR_DEFAULTTOPRIMARY),
|
||||
resolutionKey));
|
||||
|
||||
for (int i = 0; i < zoneCount; i++)
|
||||
{
|
||||
const int baseIndex = i * 4;
|
||||
const int left = zones[baseIndex];
|
||||
const int top = zones[baseIndex+1];
|
||||
const int right = zones[baseIndex+2];
|
||||
const int bottom = zones[baseIndex+3];
|
||||
zoneSet->AddZone(MakeZone({ left, top, right, bottom }));
|
||||
}
|
||||
zoneSet->Save();
|
||||
|
||||
wil::unique_cotaskmem_string zoneSetId;
|
||||
if (SUCCEEDED_LOG(StringFromCLSID(id, &zoneSetId)))
|
||||
{
|
||||
RegistryHelpers::SetString(activeKey, L"ActiveZoneSetId", zoneSetId.get());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
class FancyZonesModule : public PowertoyModuleIface
|
||||
{
|
||||
public:
|
||||
@ -147,7 +76,7 @@ public:
|
||||
if (!m_app)
|
||||
{
|
||||
Trace::FancyZones::EnableFancyZones(true);
|
||||
m_app = MakeFancyZones(reinterpret_cast<HINSTANCE>(&__ImageBase), m_settings.get());
|
||||
m_app = MakeFancyZones(reinterpret_cast<HINSTANCE>(&__ImageBase), m_settings);
|
||||
if (m_app)
|
||||
{
|
||||
m_app->Run();
|
||||
@ -200,12 +129,15 @@ public:
|
||||
{
|
||||
app_name = GET_RESOURCE_STRING(IDS_FANCYZONES);
|
||||
m_settings = MakeFancyZonesSettings(reinterpret_cast<HINSTANCE>(&__ImageBase), FancyZonesModule::get_name());
|
||||
JSONHelpers::FancyZonesDataInstance().LoadFancyZonesData();
|
||||
}
|
||||
|
||||
private:
|
||||
void Disable(bool const traceEvent)
|
||||
{
|
||||
if (m_app) {
|
||||
const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance();
|
||||
fancyZonesData.SaveFancyZonesData();
|
||||
if (traceEvent)
|
||||
{
|
||||
Trace::FancyZones::EnableFancyZones(false);
|
||||
|
@ -1,4 +0,0 @@
|
||||
LIBRARY fancyzones.dll
|
||||
|
||||
EXPORTS
|
||||
PersistZoneSet PRIVATE
|
@ -15,8 +15,6 @@ namespace FancyZonesEditor
|
||||
{
|
||||
public Settings ZoneSettings { get; }
|
||||
|
||||
private ushort _idInitial = 0;
|
||||
|
||||
public App()
|
||||
{
|
||||
ZoneSettings = new Settings();
|
||||
@ -24,37 +22,29 @@ namespace FancyZonesEditor
|
||||
|
||||
private void OnStartup(object sender, StartupEventArgs e)
|
||||
{
|
||||
if (e.Args.Length > 1)
|
||||
{
|
||||
ushort.TryParse(e.Args[1], out _idInitial);
|
||||
}
|
||||
|
||||
LayoutModel foundModel = null;
|
||||
|
||||
if (_idInitial != 0)
|
||||
foreach (LayoutModel model in ZoneSettings.DefaultModels)
|
||||
{
|
||||
foreach (LayoutModel model in ZoneSettings.DefaultModels)
|
||||
if (model.Type == Settings.ActiveZoneSetLayoutType)
|
||||
{
|
||||
if (model.Id == _idInitial)
|
||||
// found match
|
||||
foundModel = model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundModel == null)
|
||||
{
|
||||
foreach (LayoutModel model in Settings.CustomModels)
|
||||
{
|
||||
if ("{" + model.Guid.ToString().ToUpper() + "}" == Settings.ActiveZoneSetUUid.ToUpper())
|
||||
{
|
||||
// found match
|
||||
foundModel = model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundModel == null)
|
||||
{
|
||||
foreach (LayoutModel model in ZoneSettings.CustomModels)
|
||||
{
|
||||
if (model.Id == _idInitial)
|
||||
{
|
||||
// found match
|
||||
foundModel = model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundModel == null)
|
||||
|
@ -18,8 +18,11 @@ namespace FancyZonesEditor
|
||||
|
||||
private readonly Settings _settings = ((App)Application.Current).ZoneSettings;
|
||||
private LayoutPreview _layoutPreview;
|
||||
|
||||
private UserControl _editor;
|
||||
|
||||
private static MainWindow _mainWindow = new MainWindow();
|
||||
|
||||
public Int32Rect[] GetZoneRects()
|
||||
{
|
||||
if (_editor != null)
|
||||
@ -79,27 +82,23 @@ namespace FancyZonesEditor
|
||||
|
||||
public void ShowLayoutPicker()
|
||||
{
|
||||
DataContext = null;
|
||||
|
||||
_editor = null;
|
||||
_layoutPreview = new LayoutPreview
|
||||
{
|
||||
IsActualSize = true,
|
||||
Opacity = 0.5,
|
||||
};
|
||||
|
||||
Content = _layoutPreview;
|
||||
|
||||
MainWindow window = new MainWindow
|
||||
{
|
||||
Owner = this,
|
||||
ShowActivated = true,
|
||||
Topmost = true,
|
||||
};
|
||||
window.Show();
|
||||
_mainWindow.Owner = this;
|
||||
_mainWindow.ShowActivated = true;
|
||||
_mainWindow.Topmost = true;
|
||||
_mainWindow.Show();
|
||||
|
||||
// window is set to topmost to make sure it shows on top of PowerToys settings page
|
||||
// we can reset topmost flag now
|
||||
window.Topmost = false;
|
||||
_mainWindow.Topmost = false;
|
||||
}
|
||||
|
||||
// These event handlers are used to track the current state of the Shift and Ctrl keys on the keyboard
|
||||
|
@ -47,6 +47,7 @@
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
@ -194,6 +195,9 @@
|
||||
<PackageReference Include="MahApps.Metro">
|
||||
<Version>2.0.0-alpha0455</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Text.Json">
|
||||
<Version>4.6.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<Version>1.1.118</Version>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@ -208,4 +212,4 @@
|
||||
<Resource Include="images\Merge.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
@ -21,7 +21,6 @@ namespace FancyZonesEditor
|
||||
public const int MaxZones = 40;
|
||||
private readonly Settings _settings = ((App)Application.Current).ZoneSettings;
|
||||
private static readonly string _defaultNamePrefix = "Custom Layout ";
|
||||
private bool _editing = false;
|
||||
|
||||
public int WrapPanelItemSize { get; set; } = 262;
|
||||
|
||||
@ -67,7 +66,7 @@ namespace FancyZonesEditor
|
||||
{
|
||||
WindowLayout window = new WindowLayout();
|
||||
window.Show();
|
||||
Close();
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void LayoutItem_Click(object sender, MouseButtonEventArgs e)
|
||||
@ -95,12 +94,11 @@ namespace FancyZonesEditor
|
||||
}
|
||||
|
||||
model.IsSelected = false;
|
||||
_editing = true;
|
||||
Close();
|
||||
Hide();
|
||||
|
||||
bool isPredefinedLayout = Settings.IsPredefinedLayout(model);
|
||||
|
||||
if (!_settings.CustomModels.Contains(model) || isPredefinedLayout)
|
||||
if (!Settings.CustomModels.Contains(model) || isPredefinedLayout)
|
||||
{
|
||||
if (isPredefinedLayout)
|
||||
{
|
||||
@ -110,7 +108,7 @@ namespace FancyZonesEditor
|
||||
}
|
||||
|
||||
int maxCustomIndex = 0;
|
||||
foreach (LayoutModel customModel in _settings.CustomModels)
|
||||
foreach (LayoutModel customModel in Settings.CustomModels)
|
||||
{
|
||||
string name = customModel.Name;
|
||||
if (name.StartsWith(_defaultNamePrefix))
|
||||
@ -165,10 +163,8 @@ namespace FancyZonesEditor
|
||||
|
||||
private void OnClosing(object sender, EventArgs e)
|
||||
{
|
||||
if (!_editing)
|
||||
{
|
||||
EditorOverlay.Current.Close();
|
||||
}
|
||||
LayoutModel.SerializeDeletedCustomZoneSets();
|
||||
EditorOverlay.Current.Close();
|
||||
}
|
||||
|
||||
private void OnInitialized(object sender, EventArgs e)
|
||||
@ -178,7 +174,7 @@ namespace FancyZonesEditor
|
||||
|
||||
private void SetSelectedItem()
|
||||
{
|
||||
foreach (LayoutModel model in _settings.CustomModels)
|
||||
foreach (LayoutModel model in Settings.CustomModels)
|
||||
{
|
||||
if (model.IsSelected)
|
||||
{
|
||||
|
@ -2,7 +2,10 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
@ -11,40 +14,27 @@ namespace FancyZonesEditor.Models
|
||||
// Free form Layout Model, which specifies independent zone rects
|
||||
public class CanvasLayoutModel : LayoutModel
|
||||
{
|
||||
private static readonly ushort _latestVersion = 0;
|
||||
|
||||
public CanvasLayoutModel(ushort version, string name, ushort id, byte[] data)
|
||||
: base(name, id)
|
||||
public CanvasLayoutModel(string uuid, string name, LayoutType type, int referenceWidth, int referenceHeight, IList<Int32Rect> zones)
|
||||
: base(uuid, name, type)
|
||||
{
|
||||
if (version == _latestVersion)
|
||||
{
|
||||
Load(data);
|
||||
}
|
||||
_referenceWidth = referenceWidth;
|
||||
_referenceHeight = referenceHeight;
|
||||
Zones = zones;
|
||||
}
|
||||
|
||||
public CanvasLayoutModel(string name, ushort id, int referenceWidth, int referenceHeight)
|
||||
: base(name, id)
|
||||
public CanvasLayoutModel(string name, LayoutType type, int referenceWidth, int referenceHeight)
|
||||
: base(name, type)
|
||||
{
|
||||
// Initialize Reference Size
|
||||
_referenceWidth = referenceWidth;
|
||||
_referenceHeight = referenceHeight;
|
||||
}
|
||||
|
||||
public CanvasLayoutModel(string name, ushort id)
|
||||
: base(name, id)
|
||||
{
|
||||
}
|
||||
|
||||
public CanvasLayoutModel(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
public CanvasLayoutModel()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
// ReferenceWidth - the reference width for the layout rect that all Zones are relative to
|
||||
public int ReferenceWidth
|
||||
{
|
||||
@ -104,26 +94,6 @@ namespace FancyZonesEditor.Models
|
||||
FirePropertyChanged("Zones");
|
||||
}
|
||||
|
||||
private void Load(byte[] data)
|
||||
{
|
||||
// Initialize this CanvasLayoutModel based on the given persistence data
|
||||
// Skip version (2 bytes), id (2 bytes), and type (1 bytes)
|
||||
int i = 5;
|
||||
_referenceWidth = (data[i++] * 256) + data[i++];
|
||||
_referenceHeight = (data[i++] * 256) + data[i++];
|
||||
|
||||
int count = data[i++];
|
||||
|
||||
while (count-- > 0)
|
||||
{
|
||||
Zones.Add(new Int32Rect(
|
||||
(data[i++] * 256) + data[i++],
|
||||
(data[i++] * 256) + data[i++],
|
||||
(data[i++] * 256) + data[i++],
|
||||
(data[i++] * 256) + data[i++]));
|
||||
}
|
||||
}
|
||||
|
||||
// Clone
|
||||
// Implements the LayoutModel.Clone abstract method
|
||||
// Clones the data from this CanvasLayoutModel to a new CanvasLayoutModel
|
||||
@ -143,44 +113,50 @@ namespace FancyZonesEditor.Models
|
||||
return layout;
|
||||
}
|
||||
|
||||
// GetPersistData
|
||||
// Implements the LayoutModel.GetPersistData abstract method
|
||||
// Returns the state of this GridLayoutModel in persisted format
|
||||
protected override byte[] GetPersistData()
|
||||
// PersistData
|
||||
// Implements the LayoutModel.PersistData abstract method
|
||||
protected override void PersistData()
|
||||
{
|
||||
byte[] data = new byte[10 + (Zones.Count * 8)];
|
||||
int i = 0;
|
||||
|
||||
// Common persisted values between all layout types
|
||||
data[i++] = (byte)(_latestVersion / 256);
|
||||
data[i++] = (byte)(_latestVersion % 256);
|
||||
data[i++] = 1; // LayoutModelType: 1 == CanvasLayoutModel
|
||||
data[i++] = (byte)(Id / 256);
|
||||
data[i++] = (byte)(Id % 256);
|
||||
|
||||
// End common
|
||||
data[i++] = (byte)(_referenceWidth / 256);
|
||||
data[i++] = (byte)(_referenceWidth % 256);
|
||||
data[i++] = (byte)(_referenceHeight / 256);
|
||||
data[i++] = (byte)(_referenceHeight % 256);
|
||||
data[i++] = (byte)Zones.Count;
|
||||
|
||||
foreach (Int32Rect rect in Zones)
|
||||
FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create);
|
||||
JsonWriterOptions writerOptions = new JsonWriterOptions
|
||||
{
|
||||
data[i++] = (byte)(rect.X / 256);
|
||||
data[i++] = (byte)(rect.X % 256);
|
||||
SkipValidation = true,
|
||||
};
|
||||
using (var writer = new Utf8JsonWriter(outputStream, writerOptions))
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}");
|
||||
writer.WriteString("name", Name);
|
||||
|
||||
data[i++] = (byte)(rect.Y / 256);
|
||||
data[i++] = (byte)(rect.Y % 256);
|
||||
writer.WriteString("type", "canvas");
|
||||
|
||||
data[i++] = (byte)(rect.Width / 256);
|
||||
data[i++] = (byte)(rect.Width % 256);
|
||||
writer.WriteStartObject("info");
|
||||
|
||||
data[i++] = (byte)(rect.Height / 256);
|
||||
data[i++] = (byte)(rect.Height % 256);
|
||||
writer.WriteNumber("ref-width", _referenceWidth);
|
||||
writer.WriteNumber("ref-height", _referenceHeight);
|
||||
|
||||
writer.WriteStartArray("zones");
|
||||
foreach (Int32Rect rect in Zones)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteNumber("X", rect.X);
|
||||
writer.WriteNumber("Y", rect.Y);
|
||||
writer.WriteNumber("width", rect.Width);
|
||||
writer.WriteNumber("height", rect.Height);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
|
||||
// end info object
|
||||
writer.WriteEndObject();
|
||||
|
||||
// end root object
|
||||
writer.WriteEndObject();
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
return data;
|
||||
outputStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,19 +2,20 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
// GridLayoutModel
|
||||
// Grid-styled Layout Model, which specifies rows, columns, percentage sizes, and row/column spans
|
||||
public class GridLayoutModel : LayoutModel
|
||||
{
|
||||
private static readonly ushort _latestVersion = 0;
|
||||
|
||||
// Rows - number of rows in the Grid
|
||||
public int Rows
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
// GridLayoutModel
|
||||
// Grid-styled Layout Model, which specifies rows, columns, percentage sizes, and row/column spans
|
||||
public class GridLayoutModel : LayoutModel
|
||||
{
|
||||
// Rows - number of rows in the Grid
|
||||
public int Rows
|
||||
{
|
||||
get
|
||||
{
|
||||
return _rows;
|
||||
@ -22,19 +23,19 @@ namespace FancyZonesEditor.Models
|
||||
|
||||
set
|
||||
{
|
||||
if (_rows != value)
|
||||
{
|
||||
_rows = value;
|
||||
FirePropertyChanged("Rows");
|
||||
if (_rows != value)
|
||||
{
|
||||
_rows = value;
|
||||
FirePropertyChanged("Rows");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _rows = 1;
|
||||
|
||||
// Columns - number of columns in the Grid
|
||||
public int Columns
|
||||
{
|
||||
}
|
||||
|
||||
private int _rows = 1;
|
||||
|
||||
// Columns - number of columns in the Grid
|
||||
public int Columns
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cols;
|
||||
@ -42,19 +43,19 @@ namespace FancyZonesEditor.Models
|
||||
|
||||
set
|
||||
{
|
||||
if (_cols != value)
|
||||
{
|
||||
_cols = value;
|
||||
FirePropertyChanged("Columns");
|
||||
if (_cols != value)
|
||||
{
|
||||
_cols = value;
|
||||
FirePropertyChanged("Columns");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _cols = 1;
|
||||
|
||||
// CellChildMap - represents which "children" belong in which grid cells;
|
||||
// shows spanning children by the same index appearing in adjacent cells
|
||||
// TODO: ideally no setter here - this means moving logic like "split" over to model
|
||||
}
|
||||
|
||||
private int _cols = 1;
|
||||
|
||||
// CellChildMap - represents which "children" belong in which grid cells;
|
||||
// shows spanning children by the same index appearing in adjacent cells
|
||||
// TODO: ideally no setter here - this means moving logic like "split" over to model
|
||||
public int[,] CellChildMap { get; set; }
|
||||
|
||||
// RowPercents - represents the %age height of each row in the grid
|
||||
@ -69,179 +70,159 @@ namespace FancyZonesEditor.Models
|
||||
public IList<int> FreeZones { get; } = new List<int>();
|
||||
|
||||
public GridLayoutModel()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public GridLayoutModel(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
public GridLayoutModel(string name, ushort id)
|
||||
: base(name, id)
|
||||
{
|
||||
}
|
||||
|
||||
public GridLayoutModel(ushort version, string name, ushort id, byte[] data)
|
||||
: base(name, id)
|
||||
{
|
||||
if (version == _latestVersion)
|
||||
{
|
||||
Reload(data);
|
||||
}
|
||||
public GridLayoutModel(string name, LayoutType type)
|
||||
: base(name, type)
|
||||
{
|
||||
}
|
||||
|
||||
public void Reload(byte[] data)
|
||||
{
|
||||
// Skip version (2 bytes), id (2 bytes), and type (1 bytes)
|
||||
int i = 5;
|
||||
|
||||
Rows = data[i++];
|
||||
Columns = data[i++];
|
||||
|
||||
RowPercents = new int[Rows];
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
RowPercents[row] = (data[i++] * 256) + data[i++];
|
||||
}
|
||||
|
||||
ColumnPercents = new int[Columns];
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
ColumnPercents[col] = (data[i++] * 256) + data[i++];
|
||||
}
|
||||
|
||||
CellChildMap = new int[Rows, Columns];
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
CellChildMap[row, col] = data[i++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone
|
||||
// Implements the LayoutModel.Clone abstract method
|
||||
// Clones the data from this GridLayoutModel to a new GridLayoutModel
|
||||
public override LayoutModel Clone()
|
||||
{
|
||||
GridLayoutModel layout = new GridLayoutModel(Name);
|
||||
int rows = Rows;
|
||||
int cols = Columns;
|
||||
|
||||
layout.Rows = rows;
|
||||
layout.Columns = cols;
|
||||
|
||||
int[,] cellChildMap = new int[rows, cols];
|
||||
for (int row = 0; row < rows; row++)
|
||||
{
|
||||
for (int col = 0; col < cols; col++)
|
||||
{
|
||||
cellChildMap[row, col] = CellChildMap[row, col];
|
||||
}
|
||||
}
|
||||
|
||||
layout.CellChildMap = cellChildMap;
|
||||
|
||||
int[] rowPercents = new int[rows];
|
||||
for (int row = 0; row < rows; row++)
|
||||
{
|
||||
rowPercents[row] = RowPercents[row];
|
||||
}
|
||||
|
||||
layout.RowPercents = rowPercents;
|
||||
|
||||
int[] colPercents = new int[cols];
|
||||
for (int col = 0; col < cols; col++)
|
||||
{
|
||||
colPercents[col] = ColumnPercents[col];
|
||||
}
|
||||
|
||||
layout.ColumnPercents = colPercents;
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
// GetPersistData
|
||||
// Implements the LayoutModel.GetPersistData abstract method
|
||||
// Returns the state of this GridLayoutModel in persisted format
|
||||
protected override byte[] GetPersistData()
|
||||
{
|
||||
int rows = Rows;
|
||||
int cols = Columns;
|
||||
|
||||
int[,] cellChildMap;
|
||||
|
||||
if (FreeZones.Count == 0)
|
||||
{
|
||||
// no unused indices -- so we can just use the _cellChildMap as is
|
||||
cellChildMap = CellChildMap;
|
||||
}
|
||||
else
|
||||
{
|
||||
// compress cellChildMap to not have gaps for unused child indices;
|
||||
List<int> mapping = new List<int>();
|
||||
|
||||
cellChildMap = new int[rows, cols];
|
||||
|
||||
for (int row = 0; row < rows; row++)
|
||||
{
|
||||
for (int col = 0; col < cols; col++)
|
||||
{
|
||||
int source = CellChildMap[row, col];
|
||||
|
||||
int index = mapping.IndexOf(source);
|
||||
if (index == -1)
|
||||
{
|
||||
index = mapping.Count;
|
||||
mapping.Add(source);
|
||||
}
|
||||
|
||||
cellChildMap[row, col] = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] data = new byte[7 + (Rows * 2) + (Columns * 2) + (Rows * Columns)];
|
||||
|
||||
int i = 0;
|
||||
public GridLayoutModel(string uuid, string name, LayoutType type, int rows, int cols, int[] rowPercents, int[] colsPercents, int[,] cellChildMap)
|
||||
: base(uuid, name, type)
|
||||
{
|
||||
_rows = rows;
|
||||
_cols = cols;
|
||||
RowPercents = rowPercents;
|
||||
ColumnPercents = colsPercents;
|
||||
CellChildMap = cellChildMap;
|
||||
}
|
||||
|
||||
// Common persisted values between all layout types
|
||||
data[i++] = (byte)(_latestVersion / 256);
|
||||
data[i++] = (byte)(_latestVersion % 256);
|
||||
data[i++] = 0; // LayoutModelType: 0 == GridLayoutModel
|
||||
data[i++] = (byte)(Id / 256);
|
||||
data[i++] = (byte)(Id % 256);
|
||||
|
||||
// End common
|
||||
data[i++] = (byte)Rows;
|
||||
data[i++] = (byte)Columns;
|
||||
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
int rowPercent = RowPercents[row];
|
||||
data[i++] = (byte)(rowPercent / 256);
|
||||
data[i++] = (byte)(rowPercent % 256);
|
||||
}
|
||||
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
int colPercent = ColumnPercents[col];
|
||||
data[i++] = (byte)(colPercent / 256);
|
||||
data[i++] = (byte)(colPercent % 256);
|
||||
}
|
||||
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
data[i++] = (byte)cellChildMap[row, col];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
public void Reload(byte[] data)
|
||||
{
|
||||
// Skip version (2 bytes), id (2 bytes), and type (1 bytes)
|
||||
int i = 5;
|
||||
|
||||
Rows = data[i++];
|
||||
Columns = data[i++];
|
||||
|
||||
RowPercents = new int[Rows];
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
RowPercents[row] = (data[i++] * 256) + data[i++];
|
||||
}
|
||||
|
||||
ColumnPercents = new int[Columns];
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
ColumnPercents[col] = (data[i++] * 256) + data[i++];
|
||||
}
|
||||
|
||||
CellChildMap = new int[Rows, Columns];
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
CellChildMap[row, col] = data[i++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone
|
||||
// Implements the LayoutModel.Clone abstract method
|
||||
// Clones the data from this GridLayoutModel to a new GridLayoutModel
|
||||
public override LayoutModel Clone()
|
||||
{
|
||||
GridLayoutModel layout = new GridLayoutModel(Name);
|
||||
int rows = Rows;
|
||||
int cols = Columns;
|
||||
|
||||
layout.Rows = rows;
|
||||
layout.Columns = cols;
|
||||
|
||||
int[,] cellChildMap = new int[rows, cols];
|
||||
for (int row = 0; row < rows; row++)
|
||||
{
|
||||
for (int col = 0; col < cols; col++)
|
||||
{
|
||||
cellChildMap[row, col] = CellChildMap[row, col];
|
||||
}
|
||||
}
|
||||
|
||||
layout.CellChildMap = cellChildMap;
|
||||
|
||||
int[] rowPercents = new int[rows];
|
||||
for (int row = 0; row < rows; row++)
|
||||
{
|
||||
rowPercents[row] = RowPercents[row];
|
||||
}
|
||||
|
||||
layout.RowPercents = rowPercents;
|
||||
|
||||
int[] colPercents = new int[cols];
|
||||
for (int col = 0; col < cols; col++)
|
||||
{
|
||||
colPercents[col] = ColumnPercents[col];
|
||||
}
|
||||
|
||||
layout.ColumnPercents = colPercents;
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
// PersistData
|
||||
// Implements the LayoutModel.PersistData abstract method
|
||||
protected override void PersistData()
|
||||
{
|
||||
FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create);
|
||||
using (var writer = new Utf8JsonWriter(outputStream, options: default))
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}");
|
||||
writer.WriteString("name", Name);
|
||||
|
||||
writer.WriteString("type", "grid");
|
||||
|
||||
writer.WriteStartObject("info");
|
||||
|
||||
writer.WriteNumber("rows", Rows);
|
||||
writer.WriteNumber("columns", Columns);
|
||||
|
||||
writer.WriteStartArray("rows-percentage");
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
writer.WriteNumberValue(RowPercents[row]);
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
|
||||
writer.WriteStartArray("columns-percentage");
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
writer.WriteNumberValue(ColumnPercents[col]);
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
|
||||
writer.WriteStartArray("cell-child-map");
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
writer.WriteNumberValue(CellChildMap[row, col]);
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
|
||||
// end info object
|
||||
writer.WriteEndObject();
|
||||
|
||||
// end root object
|
||||
writer.WriteEndObject();
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
outputStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
public enum LayoutType
|
||||
{
|
||||
Blank = -1,
|
||||
Focus,
|
||||
Columns,
|
||||
Rows,
|
||||
Grid,
|
||||
PriorityGrid,
|
||||
Custom,
|
||||
}
|
||||
|
||||
// Base LayoutModel
|
||||
// Manages common properties and base persistence
|
||||
public abstract class LayoutModel : INotifyPropertyChanged
|
||||
@ -19,18 +32,30 @@ namespace FancyZonesEditor.Models
|
||||
|
||||
protected LayoutModel()
|
||||
{
|
||||
_guid = Guid.NewGuid();
|
||||
Type = LayoutType.Custom;
|
||||
}
|
||||
|
||||
protected LayoutModel(string name)
|
||||
: this()
|
||||
{
|
||||
_guid = Guid.NewGuid();
|
||||
Name = name;
|
||||
}
|
||||
|
||||
protected LayoutModel(string name, ushort id)
|
||||
protected LayoutModel(string uuid, string name, LayoutType type)
|
||||
: this()
|
||||
{
|
||||
_guid = Guid.Parse(uuid);
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
protected LayoutModel(string name, LayoutType type)
|
||||
: this(name)
|
||||
{
|
||||
_id = id;
|
||||
_guid = Guid.NewGuid();
|
||||
Type = type;
|
||||
}
|
||||
|
||||
// Name - the display name for this layout model - is also used as the key in the registry
|
||||
@ -53,22 +78,17 @@ namespace FancyZonesEditor.Models
|
||||
|
||||
private string _name;
|
||||
|
||||
// Id - the unique ID for this layout model - is used to connect fancy zones' ZonesSets with the editor's Layouts
|
||||
// - note: 0 means this is a new layout, which means it will have its ID auto-assigned on persist
|
||||
public ushort Id
|
||||
public LayoutType Type { get; set; }
|
||||
|
||||
public Guid Guid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_id == 0)
|
||||
{
|
||||
_id = ++_maxId;
|
||||
}
|
||||
|
||||
return _id;
|
||||
return _guid;
|
||||
}
|
||||
}
|
||||
|
||||
private ushort _id = 0;
|
||||
private Guid _guid;
|
||||
|
||||
// IsSelected (not-persisted) - tracks whether or not this LayoutModel is selected in the picker
|
||||
// TODO: once we switch to a picker per monitor, we need to move this state to the view
|
||||
@ -103,51 +123,99 @@ namespace FancyZonesEditor.Models
|
||||
// Removes this Layout from the registry and the loaded CustomModels list
|
||||
public void Delete()
|
||||
{
|
||||
RegistryKey key = Registry.CurrentUser.OpenSubKey(_registryPath, true);
|
||||
if (key != null)
|
||||
{
|
||||
key.DeleteValue(Name);
|
||||
}
|
||||
|
||||
int i = _customModels.IndexOf(this);
|
||||
if (i != -1)
|
||||
{
|
||||
_customModels.RemoveAt(i);
|
||||
_deletedCustomModels.Add(Guid.ToString().ToUpper());
|
||||
}
|
||||
}
|
||||
|
||||
// Loads all the Layouts persisted under the Layouts key in the registry
|
||||
public static void SerializeDeletedCustomZoneSets()
|
||||
{
|
||||
FileStream outputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Create);
|
||||
var writer = new Utf8JsonWriter(outputStream, options: default);
|
||||
writer.WriteStartObject();
|
||||
writer.WriteStartArray("deleted-custom-zone-sets");
|
||||
foreach (string zoneSet in _deletedCustomModels)
|
||||
{
|
||||
writer.WriteStringValue(zoneSet);
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
writer.WriteEndObject();
|
||||
writer.Flush();
|
||||
outputStream.Close();
|
||||
}
|
||||
|
||||
// Loads all the custom Layouts from tmp file passed by FancuZonesLib
|
||||
public static ObservableCollection<LayoutModel> LoadCustomModels()
|
||||
{
|
||||
_customModels = new ObservableCollection<LayoutModel>();
|
||||
|
||||
RegistryKey key = Registry.CurrentUser.OpenSubKey(_registryPath);
|
||||
if (key != null)
|
||||
FileStream inputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Open);
|
||||
var jsonObject = JsonDocument.Parse(inputStream, options: default);
|
||||
JsonElement.ArrayEnumerator customZoneSetsEnumerator = jsonObject.RootElement.GetProperty("custom-zone-sets").EnumerateArray();
|
||||
while (customZoneSetsEnumerator.MoveNext())
|
||||
{
|
||||
foreach (string name in key.GetValueNames())
|
||||
var current = customZoneSetsEnumerator.Current;
|
||||
string name = current.GetProperty("name").GetString();
|
||||
string type = current.GetProperty("type").GetString();
|
||||
string uuid = current.GetProperty("uuid").GetString();
|
||||
var info = current.GetProperty("info");
|
||||
if (type.Equals("grid"))
|
||||
{
|
||||
LayoutModel model = null;
|
||||
byte[] data = (byte[])Registry.GetValue(_fullRegistryPath, name, null);
|
||||
|
||||
ushort version = (ushort)((data[0] * 256) + data[1]);
|
||||
byte type = data[2];
|
||||
ushort id = (ushort)((data[3] * 256) + data[4]);
|
||||
|
||||
switch (type)
|
||||
int rows = info.GetProperty("rows").GetInt32();
|
||||
int columns = info.GetProperty("columns").GetInt32();
|
||||
int[] rowsPercentage = new int[rows];
|
||||
JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty("rows-percentage").EnumerateArray();
|
||||
int i = 0;
|
||||
while (rowsPercentageEnumerator.MoveNext())
|
||||
{
|
||||
case 0: model = new GridLayoutModel(version, name, id, data); break;
|
||||
case 1: model = new CanvasLayoutModel(version, name, id, data); break;
|
||||
rowsPercentage[i++] = rowsPercentageEnumerator.Current.GetInt32();
|
||||
}
|
||||
|
||||
if (model != null)
|
||||
i = 0;
|
||||
int[] columnsPercentage = new int[columns];
|
||||
JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty("columns-percentage").EnumerateArray();
|
||||
while (columnsPercentageEnumerator.MoveNext())
|
||||
{
|
||||
if (_maxId < id)
|
||||
columnsPercentage[i++] = columnsPercentageEnumerator.Current.GetInt32();
|
||||
}
|
||||
|
||||
i = 0;
|
||||
JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty("cell-child-map").EnumerateArray();
|
||||
int[,] cellChildMap = new int[rows, columns];
|
||||
while (cellChildMapRows.MoveNext())
|
||||
{
|
||||
int j = 0;
|
||||
JsonElement.ArrayEnumerator cellChildMapRowElems = cellChildMapRows.Current.EnumerateArray();
|
||||
while (cellChildMapRowElems.MoveNext())
|
||||
{
|
||||
_maxId = id;
|
||||
cellChildMap[i, j++] = cellChildMapRowElems.Current.GetInt32();
|
||||
}
|
||||
|
||||
_customModels.Add(model);
|
||||
i++;
|
||||
}
|
||||
|
||||
_customModels.Add(new GridLayoutModel(uuid, name, LayoutType.Custom, rows, columns, rowsPercentage, columnsPercentage, cellChildMap));
|
||||
}
|
||||
else if (type.Equals("canvas"))
|
||||
{
|
||||
int referenceWidth = info.GetProperty("ref-width").GetInt32();
|
||||
int referenceHeight = info.GetProperty("ref-height").GetInt32();
|
||||
JsonElement.ArrayEnumerator zonesEnumerator = info.GetProperty("zones").EnumerateArray();
|
||||
IList<Int32Rect> zones = new List<Int32Rect>();
|
||||
while (zonesEnumerator.MoveNext())
|
||||
{
|
||||
int x = zonesEnumerator.Current.GetProperty("X").GetInt32();
|
||||
int y = zonesEnumerator.Current.GetProperty("Y").GetInt32();
|
||||
int width = zonesEnumerator.Current.GetProperty("width").GetInt32();
|
||||
int height = zonesEnumerator.Current.GetProperty("height").GetInt32();
|
||||
zones.Add(new Int32Rect(x, y, width, height));
|
||||
}
|
||||
|
||||
_customModels.Add(new CanvasLayoutModel(uuid, name, LayoutType.Custom, referenceWidth, referenceHeight, zones));
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,55 +223,63 @@ namespace FancyZonesEditor.Models
|
||||
}
|
||||
|
||||
private static ObservableCollection<LayoutModel> _customModels = null;
|
||||
|
||||
private static ushort _maxId = 0;
|
||||
private static List<string> _deletedCustomModels = new List<string>();
|
||||
|
||||
// Callbacks that the base LayoutModel makes to derived types
|
||||
protected abstract byte[] GetPersistData();
|
||||
protected abstract void PersistData();
|
||||
|
||||
public abstract LayoutModel Clone();
|
||||
|
||||
public void Persist(System.Windows.Int32Rect[] zones)
|
||||
{
|
||||
// Persist the editor data
|
||||
Registry.SetValue(_fullRegistryPath, Name, GetPersistData(), Microsoft.Win32.RegistryValueKind.Binary);
|
||||
PersistData();
|
||||
Apply(zones);
|
||||
}
|
||||
|
||||
public void Apply(System.Windows.Int32Rect[] zones)
|
||||
{
|
||||
// Persist the zone data back into FZ
|
||||
var module = Native.LoadLibrary("fancyzones.dll");
|
||||
if (module == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pfn = Native.GetProcAddress(module, "PersistZoneSet");
|
||||
if (pfn == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Scale all the zones to the DPI and then pack them up to be marshalled.
|
||||
int zoneCount = zones.Length;
|
||||
var zoneArray = new int[zoneCount * 4];
|
||||
for (int i = 0; i < zones.Length; i++)
|
||||
{
|
||||
var left = (int)(zones[i].X * Settings.Dpi);
|
||||
var top = (int)(zones[i].Y * Settings.Dpi);
|
||||
var right = left + (int)(zones[i].Width * Settings.Dpi);
|
||||
var bottom = top + (int)(zones[i].Height * Settings.Dpi);
|
||||
FileStream outputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Create);
|
||||
var writer = new Utf8JsonWriter(outputStream, options: default);
|
||||
|
||||
var index = i * 4;
|
||||
zoneArray[index] = left;
|
||||
zoneArray[index + 1] = top;
|
||||
zoneArray[index + 2] = right;
|
||||
zoneArray[index + 3] = bottom;
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("device-id", Settings.UniqueKey);
|
||||
|
||||
writer.WriteStartObject("active-zoneset");
|
||||
writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}");
|
||||
switch (Type)
|
||||
{
|
||||
case LayoutType.Focus:
|
||||
writer.WriteString("type", "focus");
|
||||
break;
|
||||
case LayoutType.Rows:
|
||||
writer.WriteString("type", "rows");
|
||||
break;
|
||||
case LayoutType.Columns:
|
||||
writer.WriteString("type", "columns");
|
||||
break;
|
||||
case LayoutType.Grid:
|
||||
writer.WriteString("type", "grid");
|
||||
break;
|
||||
case LayoutType.PriorityGrid:
|
||||
writer.WriteString("type", "priority-grid");
|
||||
break;
|
||||
case LayoutType.Custom:
|
||||
writer.WriteString("type", "custom");
|
||||
break;
|
||||
}
|
||||
|
||||
var persistZoneSet = Marshal.GetDelegateForFunctionPointer<Native.PersistZoneSet>(pfn);
|
||||
persistZoneSet(Settings.UniqueKey, Settings.WorkAreaKey, Settings.Monitor, _id, zoneCount, zoneArray);
|
||||
writer.WriteEndObject();
|
||||
|
||||
Settings settings = ((App)Application.Current).ZoneSettings;
|
||||
|
||||
writer.WriteBoolean("editor-show-spacing", settings.ShowSpacing);
|
||||
writer.WriteNumber("editor-spacing", settings.Spacing);
|
||||
writer.WriteNumber("editor-zone-count", settings.ZoneCount);
|
||||
|
||||
writer.WriteEndObject();
|
||||
writer.Flush();
|
||||
outputStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,10 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
using FancyZonesEditor.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace FancyZonesEditor
|
||||
{
|
||||
@ -18,20 +19,30 @@ namespace FancyZonesEditor
|
||||
// Other UIs in the editor will subscribe to change events on the properties to stay up to date as these properties change
|
||||
public class Settings : INotifyPropertyChanged
|
||||
{
|
||||
private readonly CanvasLayoutModel _blankCustomModel;
|
||||
private enum CmdArgs
|
||||
{
|
||||
MonitorHandle = 1,
|
||||
X_Y_Width_Height,
|
||||
ResolutionKey,
|
||||
ActiveZoneSetTmpFile,
|
||||
AppliedZoneSetTmpFile,
|
||||
CustomZoneSetsTmpFile,
|
||||
}
|
||||
|
||||
private static CanvasLayoutModel _blankCustomModel;
|
||||
private readonly CanvasLayoutModel _focusModel;
|
||||
private readonly GridLayoutModel _rowsModel;
|
||||
private readonly GridLayoutModel _columnsModel;
|
||||
private readonly GridLayoutModel _gridModel;
|
||||
private readonly GridLayoutModel _priorityGridModel;
|
||||
|
||||
private static readonly ushort _focusModelId = 0xFFFF;
|
||||
private static readonly ushort _rowsModelId = 0xFFFE;
|
||||
private static readonly ushort _columnsModelId = 0xFFFD;
|
||||
private static readonly ushort _gridModelId = 0xFFFC;
|
||||
private static readonly ushort _priorityGridModelId = 0xFFFB;
|
||||
private static readonly ushort _blankCustomModelId = 0xFFFA;
|
||||
private static readonly ushort _lastPrefinedId = _blankCustomModelId;
|
||||
public const ushort _focusModelId = 0xFFFF;
|
||||
public const ushort _rowsModelId = 0xFFFE;
|
||||
public const ushort _columnsModelId = 0xFFFD;
|
||||
public const ushort _gridModelId = 0xFFFC;
|
||||
public const ushort _priorityGridModelId = 0xFFFB;
|
||||
public const ushort _blankCustomModelId = 0xFFFA;
|
||||
public const ushort _lastPrefinedId = _blankCustomModelId;
|
||||
|
||||
// hard coded data for all the "Priority Grid" configurations that are unique to "Grid"
|
||||
private static readonly byte[][] _priorityData = new byte[][]
|
||||
@ -73,34 +84,30 @@ namespace FancyZonesEditor
|
||||
|
||||
// Initialize the five default layout models: Focus, Columns, Rows, Grid, and PriorityGrid
|
||||
DefaultModels = new List<LayoutModel>(5);
|
||||
_focusModel = new CanvasLayoutModel("Focus", _focusModelId, (int)_workArea.Width, (int)_workArea.Height);
|
||||
_focusModel = new CanvasLayoutModel("Focus", LayoutType.Focus, (int)_workArea.Width, (int)_workArea.Height);
|
||||
DefaultModels.Add(_focusModel);
|
||||
|
||||
_columnsModel = new GridLayoutModel("Columns", _columnsModelId)
|
||||
_columnsModel = new GridLayoutModel("Columns", LayoutType.Columns)
|
||||
{
|
||||
Rows = 1,
|
||||
RowPercents = new int[1] { _multiplier },
|
||||
};
|
||||
DefaultModels.Add(_columnsModel);
|
||||
|
||||
_rowsModel = new GridLayoutModel("Rows", _rowsModelId)
|
||||
_rowsModel = new GridLayoutModel("Rows", LayoutType.Rows)
|
||||
{
|
||||
Columns = 1,
|
||||
ColumnPercents = new int[1] { _multiplier },
|
||||
};
|
||||
DefaultModels.Add(_rowsModel);
|
||||
|
||||
_gridModel = new GridLayoutModel("Grid", _gridModelId);
|
||||
_gridModel = new GridLayoutModel("Grid", LayoutType.Grid);
|
||||
DefaultModels.Add(_gridModel);
|
||||
|
||||
_priorityGridModel = new GridLayoutModel("Priority Grid", _priorityGridModelId);
|
||||
_priorityGridModel = new GridLayoutModel("Priority Grid", LayoutType.PriorityGrid);
|
||||
DefaultModels.Add(_priorityGridModel);
|
||||
|
||||
_blankCustomModel = new CanvasLayoutModel("Create new custom", _blankCustomModelId, (int)_workArea.Width, (int)_workArea.Height);
|
||||
|
||||
_zoneCount = ReadRegistryInt("ZoneCount", 3);
|
||||
_spacing = ReadRegistryInt("Spacing", 16);
|
||||
_showSpacing = ReadRegistryInt("ShowSpacing", 1) == 1;
|
||||
_blankCustomModel = new CanvasLayoutModel("Create new custom", LayoutType.Blank, (int)_workArea.Width, (int)_workArea.Height);
|
||||
|
||||
UpdateLayoutModels();
|
||||
}
|
||||
@ -118,7 +125,6 @@ namespace FancyZonesEditor
|
||||
if (_zoneCount != value)
|
||||
{
|
||||
_zoneCount = value;
|
||||
Registry.SetValue(_uniqueRegistryPath, "ZoneCount", _zoneCount, RegistryValueKind.DWord);
|
||||
UpdateLayoutModels();
|
||||
FirePropertyChanged("ZoneCount");
|
||||
}
|
||||
@ -140,7 +146,6 @@ namespace FancyZonesEditor
|
||||
if (_spacing != value)
|
||||
{
|
||||
_spacing = value;
|
||||
Registry.SetValue(_uniqueRegistryPath, "Spacing", _spacing, RegistryValueKind.DWord);
|
||||
FirePropertyChanged("Spacing");
|
||||
}
|
||||
}
|
||||
@ -161,7 +166,6 @@ namespace FancyZonesEditor
|
||||
if (_showSpacing != value)
|
||||
{
|
||||
_showSpacing = value;
|
||||
Registry.SetValue(_uniqueRegistryPath, "ShowSpacing", _showSpacing, RegistryValueKind.DWord);
|
||||
FirePropertyChanged("ShowSpacing");
|
||||
}
|
||||
}
|
||||
@ -220,18 +224,33 @@ namespace FancyZonesEditor
|
||||
|
||||
public static string UniqueKey { get; private set; }
|
||||
|
||||
private string _uniqueRegistryPath;
|
||||
public static string ActiveZoneSetUUid { get; private set; }
|
||||
|
||||
public static LayoutType ActiveZoneSetLayoutType { get; private set; }
|
||||
|
||||
public static string ActiveZoneSetTmpFile
|
||||
{
|
||||
get { return _activeZoneSetTmpFile; }
|
||||
}
|
||||
|
||||
private static string _activeZoneSetTmpFile;
|
||||
|
||||
public static string AppliedZoneSetTmpFile
|
||||
{
|
||||
get { return _appliedZoneSetTmpFile; }
|
||||
}
|
||||
|
||||
private static string _appliedZoneSetTmpFile;
|
||||
|
||||
public static string CustomZoneSetsTmpFile
|
||||
{
|
||||
get { return _customZoneSetsTmpFile; }
|
||||
}
|
||||
|
||||
private static string _customZoneSetsTmpFile;
|
||||
|
||||
public static string WorkAreaKey { get; private set; }
|
||||
|
||||
public static float Dpi { get; private set; }
|
||||
|
||||
private int ReadRegistryInt(string valueName, int defaultValue)
|
||||
{
|
||||
object obj = Registry.GetValue(_uniqueRegistryPath, valueName, defaultValue);
|
||||
return (obj != null) ? (int)obj : defaultValue;
|
||||
}
|
||||
|
||||
// UpdateLayoutModels
|
||||
// Update the five default layouts based on the new ZoneCount
|
||||
private void UpdateLayoutModels()
|
||||
@ -327,59 +346,87 @@ namespace FancyZonesEditor
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseDeviceInfoData()
|
||||
{
|
||||
FileStream inputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Open);
|
||||
var jsonObject = JsonDocument.Parse(inputStream, options: default).RootElement;
|
||||
|
||||
UniqueKey = jsonObject.GetProperty("device-id").GetString();
|
||||
ActiveZoneSetUUid = jsonObject.GetProperty("active-zoneset").GetProperty("uuid").GetString();
|
||||
string layoutType = jsonObject.GetProperty("active-zoneset").GetProperty("type").GetString();
|
||||
|
||||
if (ActiveZoneSetUUid == "null" || layoutType == "blank")
|
||||
{
|
||||
// Default selection is Focus
|
||||
ActiveZoneSetLayoutType = LayoutType.Focus;
|
||||
_showSpacing = true;
|
||||
_spacing = 16;
|
||||
_zoneCount = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (layoutType)
|
||||
{
|
||||
case "focus":
|
||||
ActiveZoneSetLayoutType = LayoutType.Focus;
|
||||
break;
|
||||
case "columns":
|
||||
ActiveZoneSetLayoutType = LayoutType.Columns;
|
||||
break;
|
||||
case "rows":
|
||||
ActiveZoneSetLayoutType = LayoutType.Rows;
|
||||
break;
|
||||
case "grid":
|
||||
ActiveZoneSetLayoutType = LayoutType.Grid;
|
||||
break;
|
||||
case "priority-grid":
|
||||
ActiveZoneSetLayoutType = LayoutType.PriorityGrid;
|
||||
break;
|
||||
case "custom":
|
||||
ActiveZoneSetLayoutType = LayoutType.Custom;
|
||||
break;
|
||||
}
|
||||
|
||||
_showSpacing = jsonObject.GetProperty("editor-show-spacing").GetBoolean();
|
||||
_spacing = jsonObject.GetProperty("editor-spacing").GetInt32();
|
||||
_zoneCount = jsonObject.GetProperty("editor-zone-count").GetInt32();
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseCommandLineArgs()
|
||||
{
|
||||
_workArea = SystemParameters.WorkArea;
|
||||
Monitor = 0;
|
||||
_uniqueRegistryPath = FullRegistryPath;
|
||||
UniqueKey = string.Empty;
|
||||
Dpi = 1;
|
||||
|
||||
string[] args = Environment.GetCommandLineArgs();
|
||||
if (args.Length == 7)
|
||||
{
|
||||
// 1 = unique key for per-monitor settings
|
||||
// 2 = layoutid used to generate current layout (used to pick the default layout to show)
|
||||
// 3 = handle to monitor (passed back to engine to persist data)
|
||||
// 4 = X_Y_Width_Height in a dpi-scaled-but-unaware coords (where EditorOverlay shows up)
|
||||
// 5 = resolution key (passed back to engine to persist data)
|
||||
// 6 = monitor DPI (float)
|
||||
UniqueKey = args[1];
|
||||
_uniqueRegistryPath += "\\" + UniqueKey;
|
||||
if (uint.TryParse(args[(int)CmdArgs.MonitorHandle], out uint monitor))
|
||||
{
|
||||
Monitor = monitor;
|
||||
}
|
||||
|
||||
var parsedLocation = args[4].Split('_');
|
||||
var parsedLocation = args[(int)CmdArgs.X_Y_Width_Height].Split('_');
|
||||
var x = int.Parse(parsedLocation[0]);
|
||||
var y = int.Parse(parsedLocation[1]);
|
||||
var width = int.Parse(parsedLocation[2]);
|
||||
var height = int.Parse(parsedLocation[3]);
|
||||
|
||||
WorkAreaKey = args[5];
|
||||
|
||||
// Try invariant culture first, caller likely uses invariant i.e. "C" locale to construct parameters
|
||||
foreach (var cultureInfo in new[] { CultureInfo.InvariantCulture, CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture })
|
||||
{
|
||||
try
|
||||
{
|
||||
Dpi = float.Parse(args[6], cultureInfo);
|
||||
break;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
_workArea = new Rect(x, y, width, height);
|
||||
|
||||
if (uint.TryParse(args[4], out uint monitor))
|
||||
{
|
||||
Monitor = monitor;
|
||||
}
|
||||
WorkAreaKey = args[(int)CmdArgs.ResolutionKey];
|
||||
|
||||
_activeZoneSetTmpFile = args[(int)CmdArgs.ActiveZoneSetTmpFile];
|
||||
_appliedZoneSetTmpFile = args[(int)CmdArgs.AppliedZoneSetTmpFile];
|
||||
_customZoneSetsTmpFile = args[(int)CmdArgs.CustomZoneSetsTmpFile];
|
||||
|
||||
ParseDeviceInfoData();
|
||||
}
|
||||
}
|
||||
|
||||
public IList<LayoutModel> DefaultModels { get; }
|
||||
|
||||
public ObservableCollection<LayoutModel> CustomModels
|
||||
public static ObservableCollection<LayoutModel> CustomModels
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -393,14 +440,14 @@ namespace FancyZonesEditor
|
||||
}
|
||||
}
|
||||
|
||||
private ObservableCollection<LayoutModel> _customModels;
|
||||
private static ObservableCollection<LayoutModel> _customModels;
|
||||
|
||||
public static readonly string RegistryPath = "SOFTWARE\\SuperFancyZones";
|
||||
public static readonly string FullRegistryPath = "HKEY_CURRENT_USER\\" + RegistryPath;
|
||||
|
||||
public static bool IsPredefinedLayout(LayoutModel model)
|
||||
{
|
||||
return model.Id >= _lastPrefinedId;
|
||||
return model.Type != LayoutType.Custom;
|
||||
}
|
||||
|
||||
// implementation of INotifyProeprtyChanged
|
||||
|
@ -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;
|
475
src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp
Normal file
475
src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp
Normal file
@ -0,0 +1,475 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <lib/FancyZones.h>
|
||||
#include <lib/Settings.h>
|
||||
#include <common/common.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
|
||||
namespace FancyZonesUnitTests
|
||||
{
|
||||
TEST_CLASS(FancyZonesUnitTests)
|
||||
{
|
||||
HINSTANCE m_hInst;
|
||||
winrt::com_ptr<IFancyZonesSettings> m_settings;
|
||||
|
||||
TEST_METHOD_INITIALIZE(Init)
|
||||
{
|
||||
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
|
||||
m_settings = MakeFancyZonesSettings(m_hInst, L"FancyZonesUnitTests");
|
||||
Assert::IsTrue(m_settings != nullptr);
|
||||
}
|
||||
|
||||
TEST_METHOD(Create)
|
||||
{
|
||||
auto actual = MakeFancyZones(m_hInst, m_settings);
|
||||
Assert::IsNotNull(actual.get());
|
||||
}
|
||||
TEST_METHOD(CreateWithEmptyHinstance)
|
||||
{
|
||||
auto actual = MakeFancyZones({}, m_settings);
|
||||
Assert::IsNotNull(actual.get());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateWithNullHinstance)
|
||||
{
|
||||
auto actual = MakeFancyZones(nullptr, m_settings);
|
||||
Assert::IsNotNull(actual.get());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateWithNullSettings)
|
||||
{
|
||||
auto actual = MakeFancyZones(m_hInst, nullptr);
|
||||
Assert::IsNull(actual.get());
|
||||
}
|
||||
|
||||
TEST_METHOD(Run)
|
||||
{
|
||||
auto actual = MakeFancyZones(m_hInst, m_settings);
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
std::atomic<int> counter = 0;
|
||||
const int expectedCount = 10;
|
||||
|
||||
auto runFunc = [&]() {
|
||||
actual->Run();
|
||||
counter++;
|
||||
};
|
||||
|
||||
for (int i = 0; i < expectedCount; i++)
|
||||
{
|
||||
threads.push_back(std::thread(runFunc));
|
||||
}
|
||||
|
||||
for (auto& thread : threads)
|
||||
{
|
||||
thread.join();
|
||||
}
|
||||
|
||||
Assert::AreEqual(expectedCount, counter.load());
|
||||
}
|
||||
|
||||
TEST_METHOD(Destroy)
|
||||
{
|
||||
auto actual = MakeFancyZones(m_hInst, m_settings);
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
std::atomic<int> counter = 0;
|
||||
const int expectedCount = 10;
|
||||
|
||||
auto destroyFunc = [&]() {
|
||||
actual->Destroy();
|
||||
counter++;
|
||||
};
|
||||
|
||||
for (int i = 0; i < expectedCount; i++)
|
||||
{
|
||||
threads.push_back(std::thread(destroyFunc));
|
||||
}
|
||||
|
||||
for (auto& thread : threads)
|
||||
{
|
||||
thread.join();
|
||||
}
|
||||
|
||||
Assert::AreEqual(expectedCount, counter.load());
|
||||
}
|
||||
|
||||
TEST_METHOD(RunDestroy)
|
||||
{
|
||||
auto actual = MakeFancyZones(m_hInst, m_settings);
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
std::atomic<int> counter = 0;
|
||||
const int expectedCount = 20;
|
||||
|
||||
auto func = [&]() {
|
||||
auto idHash = std::hash<std::thread::id>()(std::this_thread::get_id());
|
||||
bool run = (idHash % 2 == 0);
|
||||
run ? actual->Run() : actual->Destroy();
|
||||
counter++;
|
||||
};
|
||||
|
||||
for (int i = 0; i < expectedCount; i++)
|
||||
{
|
||||
threads.push_back(std::thread(func));
|
||||
}
|
||||
|
||||
for (auto& thread : threads)
|
||||
{
|
||||
thread.join();
|
||||
}
|
||||
|
||||
Assert::AreEqual(expectedCount, counter.load());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CLASS(FancyZonesIZoneWindowHostUnitTests)
|
||||
{
|
||||
HINSTANCE m_hInst{};
|
||||
winrt::com_ptr<IFancyZonesSettings> m_settings = nullptr;
|
||||
winrt::com_ptr<IZoneWindowHost> m_zoneWindowHost = nullptr;
|
||||
|
||||
std::wstring serializedPowerToySettings(const Settings& settings)
|
||||
{
|
||||
PowerToysSettings::Settings ptSettings(HINSTANCE{}, L"FancyZonesUnitTests");
|
||||
|
||||
ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_flashZones", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES, settings.zoneSetChange_flashZones);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_displayChange_moveWindows", IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS, settings.displayChange_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, settings.zoneSetChange_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, settings.virtualDesktopChange_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen);
|
||||
ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1);
|
||||
ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHightlightColor);
|
||||
ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, settings.excludedApps);
|
||||
|
||||
return ptSettings.serialize();
|
||||
}
|
||||
|
||||
TEST_METHOD_INITIALIZE(Init)
|
||||
{
|
||||
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
|
||||
m_settings = MakeFancyZonesSettings(m_hInst, L"FancyZonesUnitTests");
|
||||
Assert::IsTrue(m_settings != nullptr);
|
||||
|
||||
auto fancyZones = MakeFancyZones(m_hInst, m_settings);
|
||||
Assert::IsTrue(fancyZones != nullptr);
|
||||
|
||||
m_zoneWindowHost = fancyZones.as<IZoneWindowHost>();
|
||||
Assert::IsTrue(m_zoneWindowHost != nullptr);
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(Cleanup)
|
||||
{
|
||||
const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(L"FancyZonesUnitTests") + L"\\settings.json";
|
||||
std::filesystem::remove(settingsFile);
|
||||
}
|
||||
|
||||
TEST_METHOD(GetZoneHighlightColor)
|
||||
{
|
||||
const auto expected = RGB(171, 175, 238);
|
||||
const Settings settings{
|
||||
.shiftDrag = true,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = false,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = true,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = L"#abafee",
|
||||
.zoneHighlightOpacity = 45,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
|
||||
.excludedApps = L"app\r\napp2",
|
||||
.excludedAppsArray = { L"APP", L"APP2" },
|
||||
};
|
||||
|
||||
auto config = serializedPowerToySettings(settings);
|
||||
m_settings->SetConfig(config.c_str());
|
||||
|
||||
const auto actual = m_zoneWindowHost->GetZoneHighlightColor();
|
||||
Assert::AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(GetZoneHighlightOpacity)
|
||||
{
|
||||
const auto expected = 88;
|
||||
const Settings settings{
|
||||
.shiftDrag = true,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = false,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = true,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = L"#abafee",
|
||||
.zoneHighlightOpacity = expected,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
|
||||
.excludedApps = L"app\r\napp2",
|
||||
.excludedAppsArray = { L"APP", L"APP2" },
|
||||
};
|
||||
|
||||
auto config = serializedPowerToySettings(settings);
|
||||
m_settings->SetConfig(config.c_str());
|
||||
|
||||
const auto actual = m_zoneWindowHost->GetZoneHighlightOpacity();
|
||||
Assert::AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(GetCurrentMonitorZoneSetEmpty)
|
||||
{
|
||||
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(Mocks::Monitor());
|
||||
Assert::IsNull(actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(GetCurrentMonitorZoneSetNullMonitor)
|
||||
{
|
||||
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(nullptr);
|
||||
Assert::IsNull(actual);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CLASS(FancyZonesIFancyZonesCallbackUnitTests)
|
||||
{
|
||||
HINSTANCE m_hInst{};
|
||||
winrt::com_ptr<IFancyZonesSettings> m_settings = nullptr;
|
||||
winrt::com_ptr<IFancyZonesCallback> m_fzCallback = nullptr;
|
||||
|
||||
JSONHelpers::FancyZonesData& m_fancyZonesData = JSONHelpers::FancyZonesDataInstance();
|
||||
|
||||
std::wstring serializedPowerToySettings(const Settings& settings)
|
||||
{
|
||||
PowerToysSettings::Settings ptSettings(HINSTANCE{}, L"FancyZonesUnitTests");
|
||||
|
||||
ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_flashZones", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES, settings.zoneSetChange_flashZones);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_displayChange_moveWindows", IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS, settings.displayChange_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, settings.zoneSetChange_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, settings.virtualDesktopChange_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen);
|
||||
ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1);
|
||||
ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHightlightColor);
|
||||
ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, settings.excludedApps);
|
||||
|
||||
return ptSettings.serialize();
|
||||
}
|
||||
|
||||
void sendKeyboardInput(WORD code, bool release = false)
|
||||
{
|
||||
INPUT ip;
|
||||
ip.type = INPUT_KEYBOARD;
|
||||
ip.ki.wScan = 0; // hardware scan code for key
|
||||
ip.ki.time = 0;
|
||||
ip.ki.dwExtraInfo = 0;
|
||||
ip.ki.wVk = code;
|
||||
ip.ki.dwFlags = release ? KEYEVENTF_KEYUP : 0;
|
||||
SendInput(1, &ip, sizeof(INPUT));
|
||||
}
|
||||
|
||||
TEST_METHOD_INITIALIZE(Init)
|
||||
{
|
||||
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
|
||||
m_settings = MakeFancyZonesSettings(m_hInst, L"FancyZonesUnitTests");
|
||||
Assert::IsTrue(m_settings != nullptr);
|
||||
|
||||
auto fancyZones = MakeFancyZones(m_hInst, m_settings);
|
||||
Assert::IsTrue(fancyZones != nullptr);
|
||||
|
||||
m_fzCallback = fancyZones.as<IFancyZonesCallback>();
|
||||
Assert::IsTrue(m_fzCallback != nullptr);
|
||||
|
||||
m_fancyZonesData = JSONHelpers::FancyZonesData();
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(Cleanup)
|
||||
{
|
||||
sendKeyboardInput(VK_SHIFT, true);
|
||||
sendKeyboardInput(VK_LWIN, true);
|
||||
sendKeyboardInput(VK_CONTROL, true);
|
||||
|
||||
const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(L"FancyZonesUnitTests") + L"\\settings.json";
|
||||
std::filesystem::remove(settingsFile);
|
||||
}
|
||||
|
||||
TEST_METHOD(InMoveSizeTest)
|
||||
{
|
||||
Assert::IsFalse(m_fzCallback->InMoveSize());
|
||||
|
||||
m_fzCallback->MoveSizeStart(Mocks::Window(), Mocks::Monitor(), POINT{ 0, 0 });
|
||||
Assert::IsFalse(m_fzCallback->InMoveSize()); //point outside of window rect
|
||||
|
||||
const auto window = Mocks::WindowCreate(m_hInst);
|
||||
const int paddingX = 8, paddingY = 6;
|
||||
RECT windowRect{};
|
||||
::GetWindowRect(window, &windowRect);
|
||||
m_fzCallback->MoveSizeStart(window, Mocks::Monitor(), POINT{ windowRect.left + paddingX, windowRect.top + paddingY });
|
||||
Assert::IsTrue(m_fzCallback->InMoveSize());
|
||||
|
||||
m_fzCallback->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 });
|
||||
Assert::IsFalse(m_fzCallback->InMoveSize());
|
||||
}
|
||||
|
||||
TEST_METHOD(OnKeyDownNothingPressed)
|
||||
{
|
||||
for (DWORD code = '0'; code <= '9'; code++)
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = code;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_LEFT;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_RIGHT;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(OnKeyDownShiftPressed)
|
||||
{
|
||||
sendKeyboardInput(VK_SHIFT);
|
||||
|
||||
for (DWORD code = '0'; code <= '9'; code++)
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = code;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_LEFT;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_RIGHT;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(OnKeyDownWinPressed)
|
||||
{
|
||||
sendKeyboardInput(VK_LWIN);
|
||||
|
||||
for (DWORD code = '0'; code <= '9'; code++)
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = code;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_LEFT;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_RIGHT;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(OnKeyDownWinShiftPressed)
|
||||
{
|
||||
sendKeyboardInput(VK_LWIN);
|
||||
sendKeyboardInput(VK_SHIFT);
|
||||
|
||||
for (DWORD code = '0'; code <= '9'; code++)
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = code;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_LEFT;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_RIGHT;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(OnKeyDownWinCtrlPressed)
|
||||
{
|
||||
sendKeyboardInput(VK_LWIN);
|
||||
sendKeyboardInput(VK_CONTROL);
|
||||
|
||||
const Settings settings{
|
||||
.overrideSnapHotkeys = false,
|
||||
};
|
||||
|
||||
auto config = serializedPowerToySettings(settings);
|
||||
m_settings->SetConfig(config.c_str());
|
||||
|
||||
for (DWORD code = '0'; code <= '9'; code++)
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = code;
|
||||
Assert::IsTrue(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_LEFT;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_RIGHT;
|
||||
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(OnKeyDownWinPressedOverride)
|
||||
{
|
||||
sendKeyboardInput(VK_LWIN);
|
||||
|
||||
const Settings settings{
|
||||
.overrideSnapHotkeys = true,
|
||||
};
|
||||
|
||||
auto config = serializedPowerToySettings(settings);
|
||||
m_settings->SetConfig(config.c_str());
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_LEFT;
|
||||
Assert::IsTrue(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
|
||||
{
|
||||
tagKBDLLHOOKSTRUCT input{};
|
||||
input.vkCode = VK_RIGHT;
|
||||
Assert::IsTrue(m_fzCallback->OnKeyDown(&input));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,714 @@
|
||||
#include "pch.h"
|
||||
#include <filesystem>
|
||||
|
||||
#include <lib/Settings.h>
|
||||
#include <lib/FancyZones.h>
|
||||
#include <common/settings_helpers.h>
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
|
||||
namespace FancyZonesUnitTests
|
||||
{
|
||||
void compareHotkeyObjects(const PowerToysSettings::HotkeyObject& expected, const PowerToysSettings::HotkeyObject& actual)
|
||||
{
|
||||
Assert::AreEqual(expected.alt_pressed(), actual.alt_pressed());
|
||||
Assert::AreEqual(expected.ctrl_pressed(), actual.ctrl_pressed());
|
||||
Assert::AreEqual(expected.shift_pressed(), actual.shift_pressed());
|
||||
Assert::AreEqual(expected.win_pressed(), actual.win_pressed());
|
||||
|
||||
//NOTE: key_from_code may create different values
|
||||
//Assert::AreEqual(expected.get_key(), actual.get_key());
|
||||
Assert::AreEqual(expected.get_code(), actual.get_code());
|
||||
Assert::AreEqual(expected.get_modifiers(), actual.get_modifiers());
|
||||
Assert::AreEqual(expected.get_modifiers_repeat(), actual.get_modifiers_repeat());
|
||||
}
|
||||
|
||||
void compareSettings(const Settings& expected, const Settings& actual)
|
||||
{
|
||||
Assert::AreEqual(expected.shiftDrag, actual.shiftDrag);
|
||||
Assert::AreEqual(expected.displayChange_moveWindows, actual.displayChange_moveWindows);
|
||||
Assert::AreEqual(expected.virtualDesktopChange_moveWindows, actual.virtualDesktopChange_moveWindows);
|
||||
Assert::AreEqual(expected.zoneSetChange_flashZones, actual.zoneSetChange_flashZones);
|
||||
Assert::AreEqual(expected.zoneSetChange_moveWindows, actual.zoneSetChange_moveWindows);
|
||||
Assert::AreEqual(expected.overrideSnapHotkeys, actual.overrideSnapHotkeys);
|
||||
Assert::AreEqual(expected.appLastZone_moveWindows, actual.appLastZone_moveWindows);
|
||||
Assert::AreEqual(expected.use_cursorpos_editor_startupscreen, actual.use_cursorpos_editor_startupscreen);
|
||||
Assert::AreEqual(expected.zoneHightlightColor.c_str(), actual.zoneHightlightColor.c_str());
|
||||
Assert::AreEqual(expected.zoneHighlightOpacity, actual.zoneHighlightOpacity);
|
||||
Assert::AreEqual(expected.excludedApps.c_str(), actual.excludedApps.c_str());
|
||||
Assert::AreEqual(expected.excludedAppsArray.size(), actual.excludedAppsArray.size());
|
||||
for (int i = 0; i < expected.excludedAppsArray.size(); i++)
|
||||
{
|
||||
Assert::AreEqual(expected.excludedAppsArray[i].c_str(), actual.excludedAppsArray[i].c_str());
|
||||
}
|
||||
|
||||
compareHotkeyObjects(expected.editorHotkey, actual.editorHotkey);
|
||||
}
|
||||
|
||||
TEST_CLASS(FancyZonesSettingsCreationUnitTest)
|
||||
{
|
||||
HINSTANCE m_hInst;
|
||||
PCWSTR m_moduleName = L"FancyZonesTest";
|
||||
std::wstring m_tmpName;
|
||||
|
||||
const PowerToysSettings::HotkeyObject m_defaultHotkeyObject = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_OEM_3);
|
||||
const Settings m_defaultSettings;
|
||||
|
||||
TEST_METHOD_INITIALIZE(Init)
|
||||
{
|
||||
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
|
||||
m_tmpName = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json";
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(Cleanup)
|
||||
{
|
||||
std::filesystem::remove(m_tmpName);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateWithHinstanceDefault)
|
||||
{
|
||||
auto actual = MakeFancyZonesSettings({}, m_moduleName);
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(m_defaultSettings, actualSettings);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateWithHinstanceNullptr)
|
||||
{
|
||||
auto actual = MakeFancyZonesSettings(nullptr, m_moduleName);
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(m_defaultSettings, actualSettings);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateWithNameEmpty)
|
||||
{
|
||||
auto actual = MakeFancyZonesSettings(m_hInst, L"");
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(m_defaultSettings, actualSettings);
|
||||
}
|
||||
|
||||
TEST_METHOD(Create)
|
||||
{
|
||||
//prepare data
|
||||
const Settings expected {
|
||||
.shiftDrag = false,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = true,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = false,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = L"#00FFD7",
|
||||
.zoneHighlightOpacity = 45,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3),
|
||||
.excludedApps = L"app",
|
||||
.excludedAppsArray = { L"APP" },
|
||||
};
|
||||
|
||||
PowerToysSettings::PowerToyValues values(m_moduleName);
|
||||
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
|
||||
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
|
||||
values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows);
|
||||
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
|
||||
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
|
||||
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
|
||||
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
|
||||
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
|
||||
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor);
|
||||
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
|
||||
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
|
||||
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
|
||||
|
||||
values.save_to_settings_file();
|
||||
|
||||
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName);
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(expected, actualSettings);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateWithMultipleApps)
|
||||
{
|
||||
//prepare data
|
||||
const Settings expected {
|
||||
.shiftDrag = false,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = true,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = false,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = L"#00FFD7",
|
||||
.zoneHighlightOpacity = 45,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3),
|
||||
.excludedApps = L"app\r\napp1\r\napp2\r\nanother app",
|
||||
.excludedAppsArray = { L"APP", L"APP1", L"APP2", L"ANOTHER APP" },
|
||||
};
|
||||
|
||||
PowerToysSettings::PowerToyValues values(m_moduleName);
|
||||
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
|
||||
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
|
||||
values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows);
|
||||
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
|
||||
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
|
||||
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
|
||||
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
|
||||
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
|
||||
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor);
|
||||
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
|
||||
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
|
||||
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
|
||||
|
||||
values.save_to_settings_file();
|
||||
|
||||
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName);
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(expected, actualSettings);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateWithBoolValuesMissed)
|
||||
{
|
||||
const Settings expected {
|
||||
.shiftDrag = m_defaultSettings.shiftDrag,
|
||||
.displayChange_moveWindows = m_defaultSettings.displayChange_moveWindows,
|
||||
.virtualDesktopChange_moveWindows = m_defaultSettings.virtualDesktopChange_moveWindows,
|
||||
.zoneSetChange_flashZones = m_defaultSettings.zoneSetChange_flashZones,
|
||||
.zoneSetChange_moveWindows = m_defaultSettings.zoneSetChange_moveWindows,
|
||||
.overrideSnapHotkeys = m_defaultSettings.overrideSnapHotkeys,
|
||||
.appLastZone_moveWindows = m_defaultSettings.appLastZone_moveWindows,
|
||||
.use_cursorpos_editor_startupscreen = m_defaultSettings.use_cursorpos_editor_startupscreen,
|
||||
.zoneHightlightColor = L"#00FFD7",
|
||||
.zoneHighlightOpacity = 45,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3),
|
||||
.excludedApps = L"app",
|
||||
.excludedAppsArray = { L"APP" },
|
||||
};
|
||||
|
||||
PowerToysSettings::PowerToyValues values(m_moduleName);
|
||||
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor);
|
||||
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
|
||||
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
|
||||
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
|
||||
|
||||
values.save_to_settings_file();
|
||||
|
||||
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName);
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(expected, actualSettings);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateColorMissed)
|
||||
{
|
||||
//prepare data
|
||||
const Settings expected {
|
||||
.shiftDrag = false,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = true,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = false,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = m_defaultSettings.zoneHightlightColor,
|
||||
.zoneHighlightOpacity = 45,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3),
|
||||
.excludedApps = L"app",
|
||||
.excludedAppsArray = { L"APP" },
|
||||
};
|
||||
|
||||
PowerToysSettings::PowerToyValues values(m_moduleName);
|
||||
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
|
||||
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
|
||||
values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows);
|
||||
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
|
||||
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
|
||||
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
|
||||
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
|
||||
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
|
||||
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
|
||||
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
|
||||
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
|
||||
|
||||
values.save_to_settings_file();
|
||||
|
||||
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName);
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(expected, actualSettings);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateOpacityMissed)
|
||||
{
|
||||
//prepare data
|
||||
const Settings expected {
|
||||
.shiftDrag = false,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = true,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = false,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = L"#00FFD7",
|
||||
.zoneHighlightOpacity = m_defaultSettings.zoneHighlightOpacity,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3),
|
||||
.excludedApps = L"app",
|
||||
.excludedAppsArray = { L"APP" },
|
||||
};
|
||||
|
||||
PowerToysSettings::PowerToyValues values(m_moduleName);
|
||||
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
|
||||
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
|
||||
values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows);
|
||||
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
|
||||
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
|
||||
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
|
||||
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
|
||||
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
|
||||
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor);
|
||||
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
|
||||
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
|
||||
|
||||
values.save_to_settings_file();
|
||||
|
||||
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName);
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(expected, actualSettings);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateHotkeyMissed)
|
||||
{
|
||||
//prepare data
|
||||
const Settings expected = Settings{
|
||||
.shiftDrag = false,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = true,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = false,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = L"#00FFD7",
|
||||
.zoneHighlightOpacity = 45,
|
||||
.editorHotkey = m_defaultSettings.editorHotkey,
|
||||
.excludedApps = L"app",
|
||||
.excludedAppsArray = { L"APP" },
|
||||
};
|
||||
|
||||
PowerToysSettings::PowerToyValues values(m_moduleName);
|
||||
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
|
||||
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
|
||||
values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows);
|
||||
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
|
||||
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
|
||||
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
|
||||
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
|
||||
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
|
||||
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor);
|
||||
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
|
||||
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
|
||||
|
||||
values.save_to_settings_file();
|
||||
|
||||
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName);
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(expected, actualSettings);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateAppsMissed)
|
||||
{
|
||||
//prepare data
|
||||
const Settings expected = Settings{
|
||||
.shiftDrag = false,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = true,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = false,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = L"#00FFD7",
|
||||
.zoneHighlightOpacity = 45,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3),
|
||||
.excludedApps = m_defaultSettings.excludedApps,
|
||||
.excludedAppsArray = m_defaultSettings.excludedAppsArray,
|
||||
};
|
||||
|
||||
PowerToysSettings::PowerToyValues values(m_moduleName);
|
||||
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
|
||||
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
|
||||
values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows);
|
||||
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
|
||||
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
|
||||
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
|
||||
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
|
||||
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
|
||||
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor);
|
||||
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
|
||||
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
|
||||
|
||||
values.save_to_settings_file();
|
||||
|
||||
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName);
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(expected, actualSettings);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateWithEmptyJson)
|
||||
{
|
||||
json::to_file(m_tmpName, json::JsonObject());
|
||||
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName);
|
||||
Assert::IsTrue(actual != nullptr);
|
||||
|
||||
auto actualSettings = actual->GetSettings();
|
||||
compareSettings(m_defaultSettings, actualSettings);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CLASS(FancyZonesSettingsCallbackUnitTests)
|
||||
{
|
||||
winrt::com_ptr<IFancyZonesSettings> m_settings = nullptr;
|
||||
PCWSTR m_moduleName = L"FancyZonesTest";
|
||||
|
||||
struct FZCallback : public winrt::implements<FZCallback, IFancyZonesCallback>
|
||||
{
|
||||
public:
|
||||
FZCallback(bool* callFlag) :
|
||||
m_callFlag(callFlag)
|
||||
{
|
||||
*m_callFlag = false;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(bool) InMoveSize() noexcept { return false; }
|
||||
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 {}
|
||||
IFACEMETHODIMP_(void) VirtualDesktopChanged() noexcept {}
|
||||
IFACEMETHODIMP_(void) VirtualDesktopInitialize() noexcept {}
|
||||
IFACEMETHODIMP_(void) WindowCreated(HWND window) noexcept {}
|
||||
IFACEMETHODIMP_(bool) OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept { return false; }
|
||||
|
||||
IFACEMETHODIMP_(void) ToggleEditor() noexcept
|
||||
{
|
||||
Assert::IsNotNull(m_callFlag);
|
||||
*m_callFlag = true;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(void) SettingsChanged() noexcept
|
||||
{
|
||||
Assert::IsNotNull(m_callFlag);
|
||||
*m_callFlag = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool* m_callFlag = nullptr;
|
||||
};
|
||||
|
||||
TEST_METHOD_INITIALIZE(Init)
|
||||
{
|
||||
HINSTANCE hInst = (HINSTANCE)GetModuleHandleW(nullptr);
|
||||
const Settings expected{
|
||||
.shiftDrag = false,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = true,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = false,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = L"#00FFD7",
|
||||
.zoneHighlightOpacity = 45,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3),
|
||||
.excludedApps = L"app",
|
||||
.excludedAppsArray = { L"APP" },
|
||||
};
|
||||
|
||||
PowerToysSettings::PowerToyValues values(m_moduleName);
|
||||
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
|
||||
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
|
||||
values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows);
|
||||
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
|
||||
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
|
||||
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
|
||||
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
|
||||
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
|
||||
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor);
|
||||
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
|
||||
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
|
||||
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
|
||||
|
||||
values.save_to_settings_file();
|
||||
|
||||
m_settings = MakeFancyZonesSettings(hInst, m_moduleName);
|
||||
Assert::IsTrue(m_settings != nullptr);
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(Cleanup)
|
||||
{
|
||||
const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json";
|
||||
std::filesystem::remove(settingsFile);
|
||||
}
|
||||
|
||||
TEST_METHOD(CallbackSetConfig)
|
||||
{
|
||||
bool flag = false;
|
||||
FZCallback callback(&flag);
|
||||
|
||||
json::JsonObject json{};
|
||||
json.SetNamedValue(L"name", json::JsonValue::CreateStringValue(L"name"));
|
||||
|
||||
m_settings->SetCallback(&callback);
|
||||
m_settings->SetConfig(json.Stringify().c_str());
|
||||
|
||||
Assert::IsTrue(flag);
|
||||
}
|
||||
|
||||
TEST_METHOD(CallbackCallCustomAction)
|
||||
{
|
||||
bool flag = false;
|
||||
FZCallback callback(&flag);
|
||||
|
||||
json::JsonObject action{};
|
||||
action.SetNamedValue(L"action_name", json::JsonValue::CreateStringValue(L"ToggledFZEditor"));
|
||||
|
||||
m_settings->SetCallback(&callback);
|
||||
m_settings->CallCustomAction(action.Stringify().c_str());
|
||||
|
||||
Assert::IsTrue(flag);
|
||||
}
|
||||
|
||||
TEST_METHOD(CallbackCallCustomActionNotToggle)
|
||||
{
|
||||
bool flag = false;
|
||||
FZCallback callback(&flag);
|
||||
|
||||
json::JsonObject action{};
|
||||
action.SetNamedValue(L"action_name", json::JsonValue::CreateStringValue(L"NOT_ToggledFZEditor"));
|
||||
|
||||
m_settings->SetCallback(&callback);
|
||||
m_settings->CallCustomAction(action.Stringify().c_str());
|
||||
|
||||
Assert::IsFalse(flag);
|
||||
}
|
||||
|
||||
TEST_METHOD(CallbackGetConfig)
|
||||
{
|
||||
bool flag = false;
|
||||
FZCallback callback(&flag);
|
||||
|
||||
m_settings->SetCallback(&callback);
|
||||
|
||||
int bufSize = 0;
|
||||
m_settings->GetConfig(L"", &bufSize);
|
||||
|
||||
Assert::IsFalse(flag);
|
||||
}
|
||||
|
||||
TEST_METHOD(CallbackGetSettings)
|
||||
{
|
||||
bool flag = false;
|
||||
FZCallback callback(&flag);
|
||||
|
||||
m_settings->SetCallback(&callback);
|
||||
m_settings->GetSettings();
|
||||
|
||||
Assert::IsFalse(flag);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CLASS(FancyZonesSettingsUnitTests)
|
||||
{
|
||||
winrt::com_ptr<IFancyZonesSettings> m_settings = nullptr;
|
||||
PowerToysSettings::Settings* m_ptSettings = nullptr;
|
||||
PCWSTR m_moduleName = L"FancyZonesTest";
|
||||
|
||||
std::wstring serializedPowerToySettings(const Settings& settings)
|
||||
{
|
||||
PowerToysSettings::Settings ptSettings(HINSTANCE{}, m_moduleName);
|
||||
ptSettings.set_description(IDS_SETTING_DESCRIPTION);
|
||||
ptSettings.set_icon_key(L"pt-fancy-zones");
|
||||
ptSettings.set_overview_link(L"https://github.com/microsoft/PowerToys/blob/master/src/modules/fancyzones/README.md");
|
||||
ptSettings.set_video_link(L"https://youtu.be/rTtGzZYAXgY");
|
||||
|
||||
ptSettings.add_custom_action(
|
||||
L"ToggledFZEditor", // action name.
|
||||
IDS_SETTING_LAUNCH_EDITOR_LABEL,
|
||||
IDS_SETTING_LAUNCH_EDITOR_BUTTON,
|
||||
IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION);
|
||||
ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_flashZones", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES, settings.zoneSetChange_flashZones);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_displayChange_moveWindows", IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS, settings.displayChange_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, settings.zoneSetChange_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, settings.virtualDesktopChange_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows);
|
||||
ptSettings.add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen);
|
||||
ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1);
|
||||
ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHightlightColor);
|
||||
ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, settings.excludedApps);
|
||||
|
||||
return ptSettings.serialize();
|
||||
}
|
||||
|
||||
TEST_METHOD_INITIALIZE(Init)
|
||||
{
|
||||
HINSTANCE hInst = (HINSTANCE)GetModuleHandleW(nullptr);
|
||||
|
||||
//init m_settings
|
||||
const Settings expected{
|
||||
.shiftDrag = false,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = true,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = false,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = L"#00FFD7",
|
||||
.zoneHighlightOpacity = 45,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3),
|
||||
.excludedApps = L"app",
|
||||
.excludedAppsArray = { L"APP" },
|
||||
};
|
||||
|
||||
PowerToysSettings::PowerToyValues values(m_moduleName);
|
||||
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
|
||||
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
|
||||
values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows);
|
||||
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
|
||||
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
|
||||
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
|
||||
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
|
||||
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
|
||||
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor);
|
||||
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
|
||||
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
|
||||
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
|
||||
|
||||
values.save_to_settings_file();
|
||||
|
||||
m_settings = MakeFancyZonesSettings(hInst, m_moduleName);
|
||||
Assert::IsTrue(m_settings != nullptr);
|
||||
|
||||
//init m_ptSettings
|
||||
m_ptSettings = new PowerToysSettings::Settings(hInst, m_moduleName);
|
||||
m_ptSettings->set_description(IDS_SETTING_DESCRIPTION);
|
||||
m_ptSettings->set_icon_key(L"pt-fancy-zones");
|
||||
m_ptSettings->set_overview_link(L"https://github.com/microsoft/PowerToys/blob/master/src/modules/fancyzones/README.md");
|
||||
m_ptSettings->set_video_link(L"https://youtu.be/rTtGzZYAXgY");
|
||||
|
||||
m_ptSettings->add_custom_action(
|
||||
L"ToggledFZEditor", // action name.
|
||||
IDS_SETTING_LAUNCH_EDITOR_LABEL,
|
||||
IDS_SETTING_LAUNCH_EDITOR_BUTTON,
|
||||
IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION);
|
||||
m_ptSettings->add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, expected.editorHotkey);
|
||||
m_ptSettings->add_bool_toogle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, expected.shiftDrag);
|
||||
m_ptSettings->add_bool_toogle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, expected.overrideSnapHotkeys);
|
||||
m_ptSettings->add_bool_toogle(L"fancyzones_zoneSetChange_flashZones", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES, expected.zoneSetChange_flashZones);
|
||||
m_ptSettings->add_bool_toogle(L"fancyzones_displayChange_moveWindows", IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS, expected.displayChange_moveWindows);
|
||||
m_ptSettings->add_bool_toogle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, expected.zoneSetChange_moveWindows);
|
||||
m_ptSettings->add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, expected.virtualDesktopChange_moveWindows);
|
||||
m_ptSettings->add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, expected.appLastZone_moveWindows);
|
||||
m_ptSettings->add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, expected.use_cursorpos_editor_startupscreen);
|
||||
m_ptSettings->add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, expected.zoneHighlightOpacity, 0, 100, 1);
|
||||
m_ptSettings->add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, expected.zoneHightlightColor);
|
||||
m_ptSettings->add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, expected.excludedApps);
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(Cleanup)
|
||||
{
|
||||
const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json";
|
||||
std::filesystem::remove(settingsFile);
|
||||
}
|
||||
|
||||
TEST_METHOD(GetConfig)
|
||||
{
|
||||
const int expectedSize = m_ptSettings->serialize().size() + 1;
|
||||
|
||||
int actualBufferSize = expectedSize;
|
||||
PWSTR actualBuffer = new wchar_t[actualBufferSize];
|
||||
|
||||
Assert::IsTrue(m_settings->GetConfig(actualBuffer, &actualBufferSize));
|
||||
Assert::AreEqual(expectedSize, actualBufferSize);
|
||||
|
||||
Assert::AreEqual(m_ptSettings->serialize().c_str(), actualBuffer);
|
||||
}
|
||||
|
||||
TEST_METHOD(GetConfigSmallBuffer)
|
||||
{
|
||||
const auto serialized = m_ptSettings->serialize();
|
||||
const int expectedSize = serialized.size() + 1;
|
||||
|
||||
int actualBufferSize = m_ptSettings->serialize().size() - 1;
|
||||
PWSTR actualBuffer = new wchar_t[actualBufferSize];
|
||||
|
||||
Assert::IsFalse(m_settings->GetConfig(actualBuffer, &actualBufferSize));
|
||||
Assert::AreEqual(expectedSize, actualBufferSize);
|
||||
Assert::AreNotEqual(serialized.c_str(), actualBuffer);
|
||||
}
|
||||
|
||||
TEST_METHOD(GetConfigNullBuffer)
|
||||
{
|
||||
const auto serialized = m_ptSettings->serialize();
|
||||
const int expectedSize = serialized.size() + 1;
|
||||
|
||||
int actualBufferSize = 0;
|
||||
PWSTR actualBuffer = nullptr;
|
||||
|
||||
Assert::IsFalse(m_settings->GetConfig(actualBuffer, &actualBufferSize));
|
||||
Assert::AreEqual(expectedSize, actualBufferSize);
|
||||
}
|
||||
|
||||
TEST_METHOD(SetConfig)
|
||||
{
|
||||
//cleanup file before call set config
|
||||
const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json";
|
||||
std::filesystem::remove(settingsFile);
|
||||
|
||||
const Settings expected {
|
||||
.shiftDrag = true,
|
||||
.displayChange_moveWindows = true,
|
||||
.virtualDesktopChange_moveWindows = true,
|
||||
.zoneSetChange_flashZones = false,
|
||||
.zoneSetChange_moveWindows = true,
|
||||
.overrideSnapHotkeys = false,
|
||||
.appLastZone_moveWindows = true,
|
||||
.use_cursorpos_editor_startupscreen = true,
|
||||
.zoneHightlightColor = L"#00AABB",
|
||||
.zoneHighlightOpacity = 45,
|
||||
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
|
||||
.excludedApps = L"app\r\napp2",
|
||||
.excludedAppsArray = { L"APP", L"APP2" },
|
||||
};
|
||||
|
||||
auto config = serializedPowerToySettings(expected);
|
||||
m_settings->SetConfig(config.c_str());
|
||||
|
||||
auto actual = m_settings->GetSettings();
|
||||
compareSettings(expected, actual);
|
||||
|
||||
Assert::IsTrue(std::filesystem::exists(settingsFile));
|
||||
}
|
||||
};
|
||||
}
|
1659
src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp
Normal file
1659
src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,35 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include "lib\RegistryHelpers.h"
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
|
||||
namespace FancyZonesUnitTests
|
||||
{
|
||||
TEST_CLASS(RegistryHelpersUnitTests){
|
||||
public:
|
||||
TEST_METHOD(GetDefaultKey){
|
||||
// Test the path to the key is the same string.
|
||||
wchar_t key[256];
|
||||
Assert::AreEqual(0, wcscmp(RegistryHelpers::GetKey(nullptr, key, ARRAYSIZE(key)), L"Software\\SuperFancyZones"));
|
||||
}
|
||||
|
||||
TEST_METHOD(GetKeyWithMonitor)
|
||||
{
|
||||
// Test the path to the key is the same string.
|
||||
wchar_t key[256];
|
||||
Assert::AreEqual(0, wcscmp(RegistryHelpers::GetKey(L"Monitor1", key, ARRAYSIZE(key)), L"Software\\SuperFancyZones\\Monitor1"));
|
||||
}
|
||||
|
||||
TEST_METHOD(OpenKey)
|
||||
{
|
||||
// The default key should exist.
|
||||
wil::unique_hkey key{ RegistryHelpers::OpenKey({}) };
|
||||
Assert::IsNotNull(key.get());
|
||||
|
||||
// The Monitor1 key shouldn't exist.
|
||||
wil::unique_hkey key2{ RegistryHelpers::OpenKey(L"Monitor1") };
|
||||
Assert::IsNull(key2.get());
|
||||
}
|
||||
}
|
||||
;
|
||||
}
|
@ -96,12 +96,15 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="FancyZones.Spec.cpp" />
|
||||
<ClCompile Include="FancyZonesSettings.Spec.cpp" />
|
||||
<ClCompile Include="JsonHelpers.Tests.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RegistryHelpers.Spec.cpp" />
|
||||
<ClCompile Include="Util.Spec.cpp" />
|
||||
<ClCompile Include="Util.cpp" />
|
||||
<ClCompile Include="Zone.Spec.cpp" />
|
||||
<ClCompile Include="ZoneSet.Spec.cpp" />
|
||||
<ClCompile Include="ZoneWindow.Spec.cpp" />
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
@ -24,15 +24,24 @@
|
||||
<ClCompile Include="Zone.Spec.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RegistryHelpers.Spec.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Util.Spec.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ZoneWindow.Spec.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="JsonHelpers.Tests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Util.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FancyZonesSettings.Spec.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FancyZones.Spec.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
|
149
src/modules/fancyzones/tests/UnitTests/Util.cpp
Normal file
149
src/modules/fancyzones/tests/UnitTests/Util.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
#include "pch.h"
|
||||
#include "Util.h"
|
||||
|
||||
static int s_classId = 0;
|
||||
|
||||
namespace Mocks
|
||||
{
|
||||
class HwndCreator
|
||||
{
|
||||
public:
|
||||
HwndCreator(const std::wstring& title = L"");
|
||||
|
||||
~HwndCreator();
|
||||
|
||||
HWND operator()(HINSTANCE hInst);
|
||||
|
||||
void setHwnd(HWND val);
|
||||
void setCondition(bool cond);
|
||||
|
||||
inline HINSTANCE getHInstance() const { return m_hInst; }
|
||||
inline const std::wstring& getTitle() const { return m_windowTitle; }
|
||||
inline const std::wstring& getWindowClassName() const { return m_windowClassName; }
|
||||
|
||||
private:
|
||||
std::wstring m_windowTitle;
|
||||
std::wstring m_windowClassName;
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_conditionVar;
|
||||
bool m_conditionFlag;
|
||||
HANDLE m_thread;
|
||||
|
||||
HINSTANCE m_hInst;
|
||||
HWND m_hWnd;
|
||||
};
|
||||
|
||||
HWND WindowCreate(HINSTANCE hInst)
|
||||
{
|
||||
return HwndCreator()(hInst);
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CALLBACK DLLWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (message == WM_DESTROY)
|
||||
{
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DefWindowProc(hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
BOOL RegisterDLLWindowClass(LPCWSTR szClassName, Mocks::HwndCreator* creator)
|
||||
{
|
||||
if (!creator)
|
||||
return false;
|
||||
|
||||
WNDCLASSEX wc;
|
||||
|
||||
wc.hInstance = creator->getHInstance();
|
||||
wc.lpszClassName = szClassName;
|
||||
wc.lpfnWndProc = DLLWindowProc;
|
||||
wc.cbSize = sizeof(WNDCLASSEX);
|
||||
|
||||
wc.style = CS_DBLCLKS;
|
||||
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
||||
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
|
||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wc.lpszMenuName = NULL;
|
||||
wc.cbClsExtra = 0;
|
||||
wc.cbWndExtra = 0;
|
||||
wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
|
||||
|
||||
auto regRes = RegisterClassEx(&wc);
|
||||
return regRes;
|
||||
}
|
||||
|
||||
DWORD WINAPI ThreadProc(LPVOID lpParam)
|
||||
{
|
||||
MSG messages;
|
||||
Mocks::HwndCreator* creator = reinterpret_cast<Mocks::HwndCreator*>(lpParam);
|
||||
if (!creator)
|
||||
return -1;
|
||||
|
||||
if (RegisterDLLWindowClass((LPCWSTR)creator->getWindowClassName().c_str(), creator) != 0)
|
||||
{
|
||||
auto hWnd = CreateWindowEx(0, (LPCWSTR)creator->getWindowClassName().c_str(), (LPCWSTR)creator->getTitle().c_str(), WS_EX_APPWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 10, 10, nullptr, nullptr, creator->getHInstance(), NULL);
|
||||
creator->setHwnd(hWnd);
|
||||
creator->setCondition(true);
|
||||
|
||||
while (GetMessage(&messages, NULL, 0, 0))
|
||||
{
|
||||
TranslateMessage(&messages);
|
||||
DispatchMessage(&messages);
|
||||
}
|
||||
|
||||
creator->setHwnd(hWnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
creator->setCondition(true);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
namespace Mocks
|
||||
{
|
||||
HwndCreator::HwndCreator(const std::wstring& title) :
|
||||
m_windowTitle(title), m_windowClassName(std::to_wstring(++s_classId)), m_conditionFlag(false), m_thread(nullptr), m_hInst(HINSTANCE{}), m_hWnd(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
HwndCreator::~HwndCreator()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_conditionVar.wait(lock, [this] { return m_conditionFlag; });
|
||||
|
||||
if (m_thread)
|
||||
{
|
||||
CloseHandle(m_thread);
|
||||
}
|
||||
}
|
||||
|
||||
HWND HwndCreator::operator()(HINSTANCE hInst)
|
||||
{
|
||||
m_hInst = hInst;
|
||||
m_conditionFlag = false;
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
m_thread = CreateThread(0, NULL, ThreadProc, (LPVOID)this, NULL, NULL);
|
||||
m_conditionVar.wait(lock, [this] { return m_conditionFlag; });
|
||||
|
||||
return m_hWnd;
|
||||
}
|
||||
|
||||
void HwndCreator::setHwnd(HWND val)
|
||||
{
|
||||
m_hWnd = val;
|
||||
}
|
||||
|
||||
void HwndCreator::setCondition(bool cond)
|
||||
{
|
||||
m_conditionFlag = cond;
|
||||
m_conditionVar.notify_one();
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/JsonHelpers.h"
|
||||
|
||||
namespace CustomAssert
|
||||
{
|
||||
static void AreEqual(const RECT& r1, const RECT& r2)
|
||||
@ -13,9 +15,9 @@ namespace CustomAssert
|
||||
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(g1 == g2);
|
||||
}
|
||||
|
||||
static void AreEqual(WORD w1, WORD w2)
|
||||
static void AreEqual(JSONHelpers::ZoneSetLayoutType t1, JSONHelpers::ZoneSetLayoutType t2)
|
||||
{
|
||||
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(w1 == w2);
|
||||
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(t1 == t2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,4 +41,5 @@ namespace Mocks
|
||||
return reinterpret_cast<HINSTANCE>(++s_nextInstance);
|
||||
}
|
||||
|
||||
}
|
||||
HWND WindowCreate(HINSTANCE hInst);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "pch.h"
|
||||
#include "lib\Zone.h"
|
||||
#include "lib\Settings.h"
|
||||
|
||||
#include "Util.h"
|
||||
|
||||
@ -7,48 +8,419 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
|
||||
namespace FancyZonesUnitTests
|
||||
{
|
||||
TEST_CLASS(ZoneUnitTests){
|
||||
public:
|
||||
TEST_METHOD(TestCreateZone){
|
||||
RECT zoneRect{ 10, 10, 200, 200 };
|
||||
winrt::com_ptr<IZone> zone = MakeZone(zoneRect);
|
||||
Assert::IsNotNull(&zone);
|
||||
CustomAssert::AreEqual(zoneRect, zone->GetZoneRect());
|
||||
TEST_CLASS(ZoneUnitTests)
|
||||
{
|
||||
private:
|
||||
RECT m_zoneRect{ 10, 10, 200, 200 };
|
||||
HINSTANCE m_hInst{};
|
||||
|
||||
constexpr size_t id = 10;
|
||||
zone->SetId(id);
|
||||
Assert::AreEqual(zone->Id(), id);
|
||||
}
|
||||
HWND addWindow(const winrt::com_ptr<IZone>& zone, bool stamp)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(m_hInst);
|
||||
HWND zoneWindow = Mocks::WindowCreate(m_hInst);
|
||||
zone->AddWindowToZone(window, zoneWindow, stamp);
|
||||
|
||||
TEST_METHOD(ContainsWindow)
|
||||
{
|
||||
RECT zoneRect{ 10, 10, 200, 200 };
|
||||
winrt::com_ptr<IZone> zone = MakeZone(zoneRect);
|
||||
HWND newWindow = Mocks::Window();
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
}
|
||||
return window;
|
||||
}
|
||||
|
||||
TEST_METHOD(TestAddRemoveWindow)
|
||||
{
|
||||
RECT zoneRect{ 10, 10, 200, 200 };
|
||||
winrt::com_ptr<IZone> zone = MakeZone(zoneRect);
|
||||
HWND newWindow = Mocks::Window();
|
||||
void addMany(const winrt::com_ptr<IZone>& zone)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
addWindow(zone, i % 2 == 0);
|
||||
}
|
||||
}
|
||||
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
zone->AddWindowToZone(newWindow, Mocks::Window(), true);
|
||||
Assert::IsTrue(zone->ContainsWindow(newWindow));
|
||||
TEST_METHOD_INITIALIZE(Init)
|
||||
{
|
||||
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
|
||||
}
|
||||
|
||||
zone->RemoveWindowFromZone(newWindow, false);
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
}
|
||||
public:
|
||||
TEST_METHOD(TestCreateZone)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
Assert::IsNotNull(&zone);
|
||||
CustomAssert::AreEqual(m_zoneRect, zone->GetZoneRect());
|
||||
}
|
||||
|
||||
TEST_METHOD(TestRemoveInvalidWindow)
|
||||
{
|
||||
RECT zoneRect{ 10, 10, 200, 200 };
|
||||
winrt::com_ptr<IZone> zone = MakeZone(zoneRect);
|
||||
HWND newWindow = Mocks::Window();
|
||||
zone->RemoveWindowFromZone(newWindow, false);
|
||||
}
|
||||
}
|
||||
;
|
||||
TEST_METHOD(TestCreateZoneZeroRect)
|
||||
{
|
||||
RECT zoneRect{ 0, 0, 0, 0 };
|
||||
winrt::com_ptr<IZone> zone = MakeZone(zoneRect);
|
||||
Assert::IsNotNull(&zone);
|
||||
CustomAssert::AreEqual(zoneRect, zone->GetZoneRect());
|
||||
}
|
||||
|
||||
TEST_METHOD(GetSetId)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
constexpr size_t id = 10;
|
||||
zone->SetId(id);
|
||||
Assert::AreEqual(zone->Id(), id);
|
||||
}
|
||||
|
||||
TEST_METHOD(IsEmpty)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
Assert::IsTrue(zone->IsEmpty());
|
||||
}
|
||||
|
||||
TEST_METHOD(IsNonEmptyStampTrue)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
addWindow(zone, true);
|
||||
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
}
|
||||
|
||||
TEST_METHOD(IsNonEmptyStampFalse)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
addWindow(zone, false);
|
||||
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
}
|
||||
|
||||
TEST_METHOD(IsNonEmptyManyWindows)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND zoneWindow = Mocks::WindowCreate(m_hInst);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(m_hInst);
|
||||
zone->AddWindowToZone(window, zoneWindow, i % 2 == 0);
|
||||
}
|
||||
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
}
|
||||
|
||||
TEST_METHOD(IsNonEmptyManyZoneWindows)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND window = Mocks::WindowCreate(m_hInst);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
HWND zoneWindow = Mocks::WindowCreate(m_hInst);
|
||||
zone->AddWindowToZone(window, zoneWindow, i % 2 == 0);
|
||||
}
|
||||
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
}
|
||||
|
||||
TEST_METHOD(IsNonEmptyMany)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
addMany(zone);
|
||||
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
}
|
||||
|
||||
TEST_METHOD(ContainsWindowEmpty)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
HWND newWindow = Mocks::WindowCreate(m_hInst);
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
}
|
||||
|
||||
TEST_METHOD(ContainsWindowNot)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
addMany(zone);
|
||||
|
||||
HWND newWindow = Mocks::WindowCreate(m_hInst);
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
}
|
||||
|
||||
TEST_METHOD(ContainsWindowStampTrue)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
HWND window = addWindow(zone, true);
|
||||
|
||||
Assert::IsTrue(zone->ContainsWindow(window));
|
||||
}
|
||||
|
||||
TEST_METHOD(ContainsWindowStampFalse)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
HWND window = addWindow(zone, false);
|
||||
|
||||
Assert::IsTrue(zone->ContainsWindow(window));
|
||||
}
|
||||
|
||||
TEST_METHOD(ContainsWindowManyWindows)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND zoneWindow = Mocks::WindowCreate(m_hInst);
|
||||
std::vector<HWND> windowVec{};
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(m_hInst);
|
||||
windowVec.push_back(window);
|
||||
zone->AddWindowToZone(window, zoneWindow, i % 2 == 0);
|
||||
}
|
||||
|
||||
for (auto wnd : windowVec)
|
||||
{
|
||||
Assert::IsTrue(zone->ContainsWindow(wnd));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(ContainsWindowManyZoneWindows)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND window = Mocks::WindowCreate(m_hInst);
|
||||
std::vector<HWND> windowVec{};
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
HWND zoneWindow = Mocks::WindowCreate(m_hInst);
|
||||
windowVec.push_back(window);
|
||||
zone->AddWindowToZone(window, zoneWindow, i % 2 == 0);
|
||||
}
|
||||
|
||||
for (auto wnd : windowVec)
|
||||
{
|
||||
Assert::IsTrue(zone->ContainsWindow(wnd));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(ContainsWindowMany)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
std::vector<HWND> windowVec{};
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
HWND window = addWindow(zone, i % 2 == 0);
|
||||
windowVec.push_back(window);
|
||||
}
|
||||
|
||||
for (auto wnd : windowVec)
|
||||
{
|
||||
Assert::IsTrue(zone->ContainsWindow(wnd));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(AddWindowNullptr)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND window = nullptr;
|
||||
HWND zoneWindow = Mocks::WindowCreate(m_hInst);
|
||||
zone->AddWindowToZone(window, zoneWindow, true);
|
||||
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
Assert::IsTrue(zone->ContainsWindow(window));
|
||||
}
|
||||
|
||||
TEST_METHOD(AddWindowZoneNullptr)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND window = Mocks::WindowCreate(m_hInst);
|
||||
HWND zoneWindow = nullptr;
|
||||
zone->AddWindowToZone(window, zoneWindow, true);
|
||||
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
Assert::IsTrue(zone->ContainsWindow(window));
|
||||
}
|
||||
|
||||
TEST_METHOD(AddManySame)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND zoneWindow = Mocks::WindowCreate(m_hInst);
|
||||
HWND window = Mocks::WindowCreate(m_hInst);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
zone->AddWindowToZone(window, zoneWindow, i % 2 == 0);
|
||||
}
|
||||
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
Assert::IsTrue(zone->ContainsWindow(window));
|
||||
}
|
||||
|
||||
TEST_METHOD(AddManySameNullptr)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND zoneWindow = nullptr;
|
||||
HWND window = nullptr;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
zone->AddWindowToZone(window, zoneWindow, i % 2 == 0);
|
||||
}
|
||||
|
||||
Assert::IsTrue(zone->ContainsWindow(window));
|
||||
}
|
||||
|
||||
TEST_METHOD(RemoveWindowRestoreSizeTrue)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
HWND newWindow = Mocks::WindowCreate(m_hInst);
|
||||
|
||||
zone->AddWindowToZone(newWindow, Mocks::WindowCreate(m_hInst), true);
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
Assert::IsTrue(zone->ContainsWindow(newWindow));
|
||||
|
||||
zone->RemoveWindowFromZone(newWindow, true);
|
||||
Assert::IsTrue(zone->IsEmpty());
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
}
|
||||
|
||||
TEST_METHOD(RemoveWindowRestoreSizeFalse)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
HWND newWindow = Mocks::WindowCreate(m_hInst);
|
||||
|
||||
zone->AddWindowToZone(newWindow, Mocks::WindowCreate(m_hInst), true);
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
Assert::IsTrue(zone->ContainsWindow(newWindow));
|
||||
|
||||
zone->RemoveWindowFromZone(newWindow, false);
|
||||
Assert::IsTrue(zone->IsEmpty());
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
}
|
||||
|
||||
TEST_METHOD(RemoveInvalidWindowRestoreSizeTrue)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
HWND newWindow = Mocks::WindowCreate(m_hInst);
|
||||
zone->RemoveWindowFromZone(newWindow, true);
|
||||
|
||||
Assert::IsTrue(zone->IsEmpty());
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
}
|
||||
|
||||
TEST_METHOD(RemoveInvalidWindowRestoreSizeFalse)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
HWND newWindow = Mocks::WindowCreate(m_hInst);
|
||||
zone->RemoveWindowFromZone(newWindow, false);
|
||||
|
||||
Assert::IsTrue(zone->IsEmpty());
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
}
|
||||
|
||||
TEST_METHOD(RemoveNullptrWindowRestoreSizeTrue)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
HWND newWindow = nullptr;
|
||||
|
||||
zone->AddWindowToZone(newWindow, Mocks::WindowCreate(m_hInst), true);
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
Assert::IsTrue(zone->ContainsWindow(newWindow));
|
||||
|
||||
zone->RemoveWindowFromZone(newWindow, true);
|
||||
Assert::IsTrue(zone->IsEmpty());
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
}
|
||||
|
||||
TEST_METHOD(RemoveNullptrWindowRestoreSizeFalse)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
HWND newWindow = nullptr;
|
||||
|
||||
zone->AddWindowToZone(newWindow, Mocks::WindowCreate(m_hInst), true);
|
||||
Assert::IsFalse(zone->IsEmpty());
|
||||
Assert::IsTrue(zone->ContainsWindow(newWindow));
|
||||
|
||||
zone->RemoveWindowFromZone(newWindow, false);
|
||||
Assert::IsTrue(zone->IsEmpty());
|
||||
Assert::IsFalse(zone->ContainsWindow(newWindow));
|
||||
}
|
||||
|
||||
TEST_METHOD(RemoveMany)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
std::vector<HWND> windowVec{};
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
HWND window = addWindow(zone, i % 2 == 0);
|
||||
windowVec.push_back(window);
|
||||
}
|
||||
|
||||
for (auto wnd : windowVec)
|
||||
{
|
||||
zone->RemoveWindowFromZone(wnd, true);
|
||||
}
|
||||
|
||||
Assert::IsTrue(zone->IsEmpty());
|
||||
}
|
||||
|
||||
TEST_METHOD(RemoveManySame)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND zoneWindow = Mocks::WindowCreate(m_hInst);
|
||||
HWND window = Mocks::WindowCreate(m_hInst);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
zone->AddWindowToZone(window, zoneWindow, i % 2 == 0);
|
||||
}
|
||||
|
||||
zone->RemoveWindowFromZone(window, true);
|
||||
|
||||
Assert::IsTrue(zone->IsEmpty());
|
||||
Assert::IsFalse(zone->ContainsWindow(window));
|
||||
}
|
||||
|
||||
TEST_METHOD(RemoveDouble)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND zoneWindow = Mocks::WindowCreate(m_hInst);
|
||||
HWND window = Mocks::WindowCreate(m_hInst);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
zone->AddWindowToZone(window, zoneWindow, i % 2 == 0);
|
||||
}
|
||||
|
||||
zone->RemoveWindowFromZone(window, true);
|
||||
zone->RemoveWindowFromZone(window, true);
|
||||
|
||||
Assert::IsTrue(zone->IsEmpty());
|
||||
}
|
||||
|
||||
TEST_METHOD(StampTrue)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
size_t expected = 123456;
|
||||
zone->SetId(expected);
|
||||
|
||||
HWND window = addWindow(zone, true);
|
||||
|
||||
HANDLE actual = GetProp(window, ZONE_STAMP);
|
||||
Assert::IsNotNull(actual);
|
||||
|
||||
size_t actualVal = HandleToLong(actual);
|
||||
Assert::AreEqual(expected, actualVal);
|
||||
}
|
||||
|
||||
TEST_METHOD(StampTrueNoId)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND window = addWindow(zone, true);
|
||||
|
||||
HANDLE actual = GetProp(window, ZONE_STAMP);
|
||||
Assert::IsNull(actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(StampFalse)
|
||||
{
|
||||
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect);
|
||||
|
||||
HWND window = addWindow(zone, false);
|
||||
|
||||
HANDLE actual = GetProp(window, ZONE_STAMP);
|
||||
Assert::IsNull(actual);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,14 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <common/common.h>
|
||||
#include <lib/util.h>
|
||||
#include <lib/ZoneSet.h>
|
||||
#include <lib/ZoneWindow.h>
|
||||
#include <lib/FancyZones.h>
|
||||
#include "Util.h"
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
|
||||
namespace FancyZonesUnitTests
|
||||
@ -17,10 +22,10 @@ namespace FancyZonesUnitTests
|
||||
{
|
||||
return RGB(0xFF, 0xFF, 0xFF);
|
||||
}
|
||||
IFACEMETHODIMP_(GUID)
|
||||
GetCurrentMonitorZoneSetId(HMONITOR monitor) noexcept
|
||||
IFACEMETHODIMP_(IZoneWindow*)
|
||||
GetParentZoneWindow(HMONITOR monitor) noexcept
|
||||
{
|
||||
return m_guid;
|
||||
return m_zoneWindow;
|
||||
}
|
||||
IFACEMETHODIMP_(int)
|
||||
GetZoneHighlightOpacity() noexcept
|
||||
@ -28,47 +33,636 @@ namespace FancyZonesUnitTests
|
||||
return 100;
|
||||
}
|
||||
|
||||
GUID m_guid;
|
||||
IZoneWindow* m_zoneWindow;
|
||||
};
|
||||
|
||||
TEST_CLASS(ZoneWindowUnitTests){
|
||||
public:
|
||||
TEST_CLASS(ZoneWindowUnitTests)
|
||||
{
|
||||
const std::wstring m_deviceId = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}";
|
||||
const std::wstring m_virtualDesktopId = L"MyVirtualDesktopId";
|
||||
std::wstringstream m_uniqueId;
|
||||
|
||||
TEST_METHOD(TestCreateZoneWindow){
|
||||
winrt::com_ptr<IZoneWindow> zoneWindow = MakeZoneWindow(nullptr, Mocks::Instance(), Mocks::Monitor(), L"DeviceId", L"MyVirtualDesktopId", false);
|
||||
Assert::IsNotNull(zoneWindow.get());
|
||||
}
|
||||
|
||||
TEST_METHOD(TestDeviceId)
|
||||
{
|
||||
// Window initialization requires a valid HMONITOR - just use the primary for now.
|
||||
HMONITOR pimaryMonitor = MonitorFromWindow(HWND(), MONITOR_DEFAULTTOPRIMARY);
|
||||
MockZoneWindowHost host;
|
||||
std::wstring expectedDeviceId = L"SomeRandomValue";
|
||||
winrt::com_ptr<IZoneWindow> zoneWindow = MakeZoneWindow(dynamic_cast<IZoneWindowHost*>(&host), Mocks::Instance(), pimaryMonitor, expectedDeviceId.c_str(), L"MyVirtualDesktopId", false);
|
||||
|
||||
Assert::AreEqual(expectedDeviceId, zoneWindow->DeviceId());
|
||||
}
|
||||
|
||||
TEST_METHOD(TestUniqueId)
|
||||
{
|
||||
// Unique id of the format "ParsedMonitorDeviceId_MonitorWidth_MonitorHeight_VirtualDesktopId
|
||||
// Example: "DELA026#5&10a58c63&0&UID16777488_1024_768_MyVirtualDesktopId"
|
||||
std::wstring deviceId(L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}");
|
||||
// Window initialization requires a valid HMONITOR - just use the primary for now.
|
||||
HMONITOR pimaryMonitor = MonitorFromWindow(HWND(), MONITOR_DEFAULTTOPRIMARY);
|
||||
MONITORINFO info;
|
||||
info.cbSize = sizeof(info);
|
||||
Assert::IsTrue(GetMonitorInfo(pimaryMonitor, &info));
|
||||
|
||||
Rect monitorRect = Rect(info.rcMonitor);
|
||||
std::wstringstream ss;
|
||||
ss << L"DELA026#5&10a58c63&0&UID16777488_" << monitorRect.width() << "_" << monitorRect.height() << "_MyVirtualDesktopId";
|
||||
|
||||
MockZoneWindowHost host;
|
||||
winrt::com_ptr<IZoneWindow> zoneWindow = MakeZoneWindow(dynamic_cast<IZoneWindowHost*>(&host), Mocks::Instance(), pimaryMonitor, deviceId.c_str(), L"MyVirtualDesktopId", false);
|
||||
Assert::AreEqual(zoneWindow->UniqueId().compare(ss.str()), 0);
|
||||
}
|
||||
}
|
||||
;
|
||||
HINSTANCE m_hInst{};
|
||||
HMONITOR m_monitor{};
|
||||
MONITORINFO m_monitorInfo{};
|
||||
MockZoneWindowHost m_zoneWindowHost{};
|
||||
IZoneWindowHost* m_hostPtr = m_zoneWindowHost.get_strong().get();
|
||||
|
||||
winrt::com_ptr<IZoneWindow> m_zoneWindow;
|
||||
|
||||
JSONHelpers::FancyZonesData& m_fancyZonesData = JSONHelpers::FancyZonesDataInstance();
|
||||
|
||||
std::wstring GuidString(const GUID& guid)
|
||||
{
|
||||
OLECHAR* guidString;
|
||||
Assert::AreEqual(S_OK, StringFromCLSID(guid, &guidString));
|
||||
|
||||
std::wstring guidStr{ guidString };
|
||||
CoTaskMemFree(guidString);
|
||||
|
||||
return guidStr;
|
||||
}
|
||||
|
||||
std::wstring CreateGuidString()
|
||||
{
|
||||
GUID guid;
|
||||
Assert::AreEqual(S_OK, CoCreateGuid(&guid));
|
||||
|
||||
return GuidString(guid);
|
||||
}
|
||||
|
||||
TEST_METHOD_INITIALIZE(Init)
|
||||
{
|
||||
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
|
||||
|
||||
m_monitor = MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY);
|
||||
m_monitorInfo.cbSize = sizeof(m_monitorInfo);
|
||||
Assert::AreNotEqual(0, GetMonitorInfoW(m_monitor, &m_monitorInfo));
|
||||
|
||||
m_uniqueId << L"DELA026#5&10a58c63&0&UID16777488_" << m_monitorInfo.rcMonitor.right << "_" << m_monitorInfo.rcMonitor.bottom << "_MyVirtualDesktopId";
|
||||
|
||||
Assert::IsFalse(ZoneWindowUtils::GetActiveZoneSetTmpPath().empty());
|
||||
Assert::IsFalse(ZoneWindowUtils::GetAppliedZoneSetTmpPath().empty());
|
||||
Assert::IsFalse(ZoneWindowUtils::GetCustomZoneSetsTmpPath().empty());
|
||||
|
||||
Assert::IsFalse(std::filesystem::exists(ZoneWindowUtils::GetActiveZoneSetTmpPath()));
|
||||
Assert::IsFalse(std::filesystem::exists(ZoneWindowUtils::GetAppliedZoneSetTmpPath()));
|
||||
Assert::IsFalse(std::filesystem::exists(ZoneWindowUtils::GetCustomZoneSetsTmpPath()));
|
||||
|
||||
m_fancyZonesData = JSONHelpers::FancyZonesData();
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(Cleanup)
|
||||
{
|
||||
//cleanup temp files if were created
|
||||
std::filesystem::remove(ZoneWindowUtils::GetActiveZoneSetTmpPath());
|
||||
std::filesystem::remove(ZoneWindowUtils::GetAppliedZoneSetTmpPath());
|
||||
std::filesystem::remove(ZoneWindowUtils::GetCustomZoneSetsTmpPath());
|
||||
|
||||
m_zoneWindow = nullptr;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IZoneWindow> InitZoneWindowWithActiveZoneSet()
|
||||
{
|
||||
const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath();
|
||||
Assert::IsFalse(std::filesystem::exists(activeZoneSetTempPath));
|
||||
|
||||
const auto type = JSONHelpers::ZoneSetLayoutType::Columns;
|
||||
const auto expectedZoneSet = JSONHelpers::ZoneSetData{ CreateGuidString(), type };
|
||||
const auto data = JSONHelpers::DeviceInfoData{ expectedZoneSet, true, 16, 3 };
|
||||
const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data };
|
||||
const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo);
|
||||
json::to_file(activeZoneSetTempPath, json);
|
||||
Assert::IsTrue(std::filesystem::exists(activeZoneSetTempPath));
|
||||
|
||||
m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath);
|
||||
|
||||
return MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
}
|
||||
|
||||
void testZoneWindow(winrt::com_ptr<IZoneWindow> zoneWindow)
|
||||
{
|
||||
const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom);
|
||||
|
||||
Assert::IsNotNull(zoneWindow.get());
|
||||
Assert::IsFalse(zoneWindow->IsDragEnabled());
|
||||
Assert::AreEqual(m_uniqueId.str().c_str(), zoneWindow->UniqueId().c_str());
|
||||
Assert::AreEqual(expectedWorkArea, zoneWindow->WorkAreaKey());
|
||||
}
|
||||
|
||||
public:
|
||||
TEST_METHOD(CreateZoneWindow)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
testZoneWindow(m_zoneWindow);
|
||||
Assert::IsNull(m_zoneWindow->ActiveZoneSet());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowNoHinst)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, {}, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
testZoneWindow(m_zoneWindow);
|
||||
Assert::IsNull(m_zoneWindow->ActiveZoneSet());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowNoHinstFlashZones)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, {}, m_monitor, m_uniqueId.str(), true);
|
||||
|
||||
testZoneWindow(m_zoneWindow);
|
||||
Assert::IsNull(m_zoneWindow->ActiveZoneSet());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowNoMonitor)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, {}, m_uniqueId.str(), false);
|
||||
|
||||
Assert::IsNull(m_zoneWindow.get());
|
||||
Assert::IsNotNull(m_hostPtr);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowNoMonitorFlashZones)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, {}, m_uniqueId.str(), true);
|
||||
|
||||
Assert::IsNull(m_zoneWindow.get());
|
||||
Assert::IsNotNull(m_hostPtr);
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowNoDeviceId)
|
||||
{
|
||||
// Generate unique id without device id
|
||||
std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(m_monitor, nullptr, m_virtualDesktopId.c_str());
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, uniqueId, false);
|
||||
|
||||
const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom);
|
||||
const std::wstring expectedUniqueId = L"FallbackDevice_" + std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom) + L"_" + m_virtualDesktopId;
|
||||
|
||||
Assert::IsNotNull(m_zoneWindow.get());
|
||||
Assert::IsFalse(m_zoneWindow->IsDragEnabled());
|
||||
Assert::AreEqual(expectedUniqueId.c_str(), m_zoneWindow->UniqueId().c_str());
|
||||
Assert::AreEqual(expectedWorkArea, m_zoneWindow->WorkAreaKey());
|
||||
Assert::IsNull(m_zoneWindow->ActiveZoneSet());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowNoDesktopId)
|
||||
{
|
||||
// Generate unique id without virtual desktop id
|
||||
std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(m_monitor, m_deviceId.c_str(), nullptr);
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, uniqueId, false);
|
||||
|
||||
const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom);
|
||||
Assert::IsNotNull(m_zoneWindow.get());
|
||||
Assert::IsFalse(m_zoneWindow->IsDragEnabled());
|
||||
Assert::IsTrue(m_zoneWindow->UniqueId().empty());
|
||||
Assert::IsNull(m_zoneWindow->ActiveZoneSet());
|
||||
Assert::IsNull(m_zoneWindow->ActiveZoneSet());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowWithActiveZoneTmpFile)
|
||||
{
|
||||
using namespace JSONHelpers;
|
||||
|
||||
const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath();
|
||||
|
||||
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
|
||||
{
|
||||
const auto expectedZoneSet = ZoneSetData{ CreateGuidString(), static_cast<ZoneSetLayoutType>(type) };
|
||||
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
|
||||
const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data };
|
||||
const auto json = DeviceInfoJSON::ToJson(deviceInfo);
|
||||
json::to_file(activeZoneSetTempPath, json);
|
||||
|
||||
m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath);
|
||||
|
||||
//temp file read on initialization
|
||||
auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
testZoneWindow(actual);
|
||||
|
||||
Assert::IsNotNull(actual->ActiveZoneSet());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowWithActiveCustomZoneTmpFile)
|
||||
{
|
||||
using namespace JSONHelpers;
|
||||
|
||||
const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath();
|
||||
|
||||
const ZoneSetLayoutType type = ZoneSetLayoutType::Custom;
|
||||
const auto expectedZoneSet = ZoneSetData{ CreateGuidString(), type };
|
||||
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
|
||||
const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data };
|
||||
const auto json = DeviceInfoJSON::ToJson(deviceInfo);
|
||||
json::to_file(activeZoneSetTempPath, json);
|
||||
|
||||
m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath);
|
||||
|
||||
//temp file read on initialization
|
||||
auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
testZoneWindow(actual);
|
||||
|
||||
//custom zone needs temp file for applied zone
|
||||
Assert::IsNotNull(actual->ActiveZoneSet());
|
||||
const auto actualZoneSet = actual->ActiveZoneSet()->GetZones();
|
||||
Assert::AreEqual((size_t)0, actualZoneSet.size());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowWithActiveCustomZoneAppliedTmpFile)
|
||||
{
|
||||
using namespace JSONHelpers;
|
||||
|
||||
//save required data
|
||||
const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath();
|
||||
const auto appliedZoneSetTempPath = ZoneWindowUtils::GetAppliedZoneSetTmpPath();
|
||||
|
||||
const ZoneSetLayoutType type = ZoneSetLayoutType::Custom;
|
||||
const auto customSetGuid = CreateGuidString();
|
||||
const auto expectedZoneSet = ZoneSetData{ customSetGuid, type };
|
||||
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
|
||||
const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data };
|
||||
const auto json = DeviceInfoJSON::ToJson(deviceInfo);
|
||||
json::to_file(activeZoneSetTempPath, json);
|
||||
|
||||
const auto info = CanvasLayoutInfo{
|
||||
100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } }
|
||||
};
|
||||
const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info };
|
||||
auto customZoneJson = CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ customSetGuid, customZoneData });
|
||||
json::to_file(appliedZoneSetTempPath, customZoneJson);
|
||||
m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath);
|
||||
m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath);
|
||||
|
||||
//temp file read on initialization
|
||||
auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
testZoneWindow(actual);
|
||||
|
||||
//custom zone needs temp file for applied zone
|
||||
Assert::IsNotNull(actual->ActiveZoneSet());
|
||||
const auto actualZoneSet = actual->ActiveZoneSet()->GetZones();
|
||||
Assert::AreEqual((size_t)1, actualZoneSet.size());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowWithActiveCustomZoneAppliedTmpFileWithDeletedCustomZones)
|
||||
{
|
||||
using namespace JSONHelpers;
|
||||
|
||||
//save required data
|
||||
const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath();
|
||||
const auto appliedZoneSetTempPath = ZoneWindowUtils::GetAppliedZoneSetTmpPath();
|
||||
const auto deletedZonesTempPath = ZoneWindowUtils::GetCustomZoneSetsTmpPath();
|
||||
|
||||
const ZoneSetLayoutType type = ZoneSetLayoutType::Custom;
|
||||
const auto customSetGuid = CreateGuidString();
|
||||
const auto expectedZoneSet = ZoneSetData{ customSetGuid, type };
|
||||
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
|
||||
const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data };
|
||||
const auto json = DeviceInfoJSON::ToJson(deviceInfo);
|
||||
json::to_file(activeZoneSetTempPath, json);
|
||||
|
||||
const auto info = CanvasLayoutInfo{
|
||||
100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } }
|
||||
};
|
||||
const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info };
|
||||
const auto customZoneSet = CustomZoneSetJSON{ customSetGuid, customZoneData };
|
||||
auto customZoneJson = CustomZoneSetJSON::ToJson(customZoneSet);
|
||||
json::to_file(appliedZoneSetTempPath, customZoneJson);
|
||||
|
||||
//save same zone as deleted
|
||||
json::JsonObject deletedCustomZoneSets = {};
|
||||
json::JsonArray zonesArray{};
|
||||
zonesArray.Append(json::JsonValue::CreateStringValue(customZoneSet.uuid.substr(1, customZoneSet.uuid.size() - 2).c_str()));
|
||||
deletedCustomZoneSets.SetNamedValue(L"deleted-custom-zone-sets", zonesArray);
|
||||
json::to_file(deletedZonesTempPath, deletedCustomZoneSets);
|
||||
|
||||
m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath);
|
||||
m_fancyZonesData.ParseDeletedCustomZoneSetsFromTmpFile(deletedZonesTempPath);
|
||||
m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath);
|
||||
|
||||
//temp file read on initialization
|
||||
auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
testZoneWindow(actual);
|
||||
|
||||
Assert::IsNotNull(actual->ActiveZoneSet());
|
||||
const auto actualZoneSet = actual->ActiveZoneSet()->GetZones();
|
||||
Assert::AreEqual((size_t)1, actualZoneSet.size());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateZoneWindowWithActiveCustomZoneAppliedTmpFileWithUnusedDeletedCustomZones)
|
||||
{
|
||||
using namespace JSONHelpers;
|
||||
|
||||
//save required data
|
||||
const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath();
|
||||
const auto appliedZoneSetTempPath = ZoneWindowUtils::GetAppliedZoneSetTmpPath();
|
||||
const auto deletedZonesTempPath = ZoneWindowUtils::GetCustomZoneSetsTmpPath();
|
||||
|
||||
const ZoneSetLayoutType type = ZoneSetLayoutType::Custom;
|
||||
const auto customSetGuid = CreateGuidString();
|
||||
const auto expectedZoneSet = ZoneSetData{ customSetGuid, type };
|
||||
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
|
||||
const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data };
|
||||
const auto json = DeviceInfoJSON::ToJson(deviceInfo);
|
||||
json::to_file(activeZoneSetTempPath, json);
|
||||
|
||||
const auto info = CanvasLayoutInfo{
|
||||
100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } }
|
||||
};
|
||||
const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info };
|
||||
const auto customZoneSet = CustomZoneSetJSON{ customSetGuid, customZoneData };
|
||||
auto customZoneJson = CustomZoneSetJSON::ToJson(customZoneSet);
|
||||
json::to_file(appliedZoneSetTempPath, customZoneJson);
|
||||
|
||||
//save different zone as deleted
|
||||
json::JsonObject deletedCustomZoneSets = {};
|
||||
json::JsonArray zonesArray{};
|
||||
const auto uuid = CreateGuidString();
|
||||
zonesArray.Append(json::JsonValue::CreateStringValue(uuid.substr(1, uuid.size() - 2).c_str()));
|
||||
deletedCustomZoneSets.SetNamedValue(L"deleted-custom-zone-sets", zonesArray);
|
||||
json::to_file(deletedZonesTempPath, deletedCustomZoneSets);
|
||||
|
||||
m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath);
|
||||
m_fancyZonesData.ParseDeletedCustomZoneSetsFromTmpFile(deletedZonesTempPath);
|
||||
m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath);
|
||||
|
||||
//temp file read on initialization
|
||||
auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
testZoneWindow(actual);
|
||||
|
||||
Assert::IsNotNull(actual->ActiveZoneSet());
|
||||
const auto actualZoneSet = actual->ActiveZoneSet()->GetZones();
|
||||
Assert::AreEqual((size_t)1, actualZoneSet.size());
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeEnter)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
const auto expected = S_OK;
|
||||
const auto actual = m_zoneWindow->MoveSizeEnter(Mocks::Window(), true);
|
||||
|
||||
Assert::AreEqual(expected, actual);
|
||||
Assert::IsTrue(m_zoneWindow->IsDragEnabled());
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeEnterTwice)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
const auto expected = E_INVALIDARG;
|
||||
|
||||
m_zoneWindow->MoveSizeEnter(Mocks::Window(), true);
|
||||
const auto actual = m_zoneWindow->MoveSizeEnter(Mocks::Window(), false);
|
||||
|
||||
Assert::AreEqual(expected, actual);
|
||||
Assert::IsTrue(m_zoneWindow->IsDragEnabled());
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeUpdate)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
const auto expected = S_OK;
|
||||
const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ 0, 0 }, true);
|
||||
|
||||
Assert::AreEqual(expected, actual);
|
||||
Assert::IsTrue(m_zoneWindow->IsDragEnabled());
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeUpdatePointNegativeCoordinates)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
const auto expected = S_OK;
|
||||
const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ -10, -10 }, true);
|
||||
|
||||
Assert::AreEqual(expected, actual);
|
||||
Assert::IsTrue(m_zoneWindow->IsDragEnabled());
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeUpdatePointBigCoordinates)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
const auto expected = S_OK;
|
||||
const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ m_monitorInfo.rcMonitor.right + 1, m_monitorInfo.rcMonitor.bottom + 1 }, true);
|
||||
|
||||
Assert::AreEqual(expected, actual);
|
||||
Assert::IsTrue(m_zoneWindow->IsDragEnabled());
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeEnd)
|
||||
{
|
||||
auto zoneWindow = InitZoneWindowWithActiveZoneSet();
|
||||
|
||||
const auto window = Mocks::Window();
|
||||
zoneWindow->MoveSizeEnter(window, true);
|
||||
|
||||
const auto expected = S_OK;
|
||||
const auto actual = zoneWindow->MoveSizeEnd(window, POINT{ 0, 0 });
|
||||
Assert::AreEqual(expected, actual);
|
||||
|
||||
const auto zoneSet = zoneWindow->ActiveZoneSet();
|
||||
zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), false);
|
||||
const auto actualZoneIndex = zoneSet->GetZoneIndexFromWindow(window);
|
||||
Assert::AreNotEqual(-1, actualZoneIndex);
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeEndWindowNotAdded)
|
||||
{
|
||||
auto zoneWindow = InitZoneWindowWithActiveZoneSet();
|
||||
|
||||
const auto window = Mocks::Window();
|
||||
zoneWindow->MoveSizeEnter(window, true);
|
||||
|
||||
const auto expected = S_OK;
|
||||
const auto actual = zoneWindow->MoveSizeEnd(window, POINT{ 0, 0 });
|
||||
Assert::AreEqual(expected, actual);
|
||||
|
||||
const auto zoneSet = zoneWindow->ActiveZoneSet();
|
||||
const auto actualZoneIndex = zoneSet->GetZoneIndexFromWindow(window);
|
||||
Assert::AreEqual(-1, actualZoneIndex);
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeEndDifferentWindows)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
const auto window = Mocks::Window();
|
||||
m_zoneWindow->MoveSizeEnter(window, true);
|
||||
|
||||
const auto expected = E_INVALIDARG;
|
||||
const auto actual = m_zoneWindow->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 });
|
||||
|
||||
Assert::AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeEndWindowNotSet)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
const auto expected = E_INVALIDARG;
|
||||
const auto actual = m_zoneWindow->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 });
|
||||
|
||||
Assert::AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeEndInvalidPoint)
|
||||
{
|
||||
auto zoneWindow = InitZoneWindowWithActiveZoneSet();
|
||||
|
||||
const auto window = Mocks::Window();
|
||||
zoneWindow->MoveSizeEnter(window, true);
|
||||
|
||||
const auto expected = S_OK;
|
||||
const auto actual = zoneWindow->MoveSizeEnd(window, POINT{ -1, -1 });
|
||||
Assert::AreEqual(expected, actual);
|
||||
|
||||
const auto zoneSet = zoneWindow->ActiveZoneSet();
|
||||
zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), false);
|
||||
const auto actualZoneIndex = zoneSet->GetZoneIndexFromWindow(window);
|
||||
Assert::AreNotEqual(-1, actualZoneIndex); //with invalid point zone remains the same
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveSizeCancel)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
|
||||
const auto expected = S_OK;
|
||||
const auto actual = m_zoneWindow->MoveSizeCancel();
|
||||
|
||||
Assert::AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveWindowIntoZoneByIndexNoActiveZoneSet)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
Assert::IsNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
m_zoneWindow->MoveWindowIntoZoneByIndex(Mocks::Window(), 0);
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveWindowIntoZoneByIndex)
|
||||
{
|
||||
m_zoneWindow = InitZoneWindowWithActiveZoneSet();
|
||||
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
m_zoneWindow->MoveWindowIntoZoneByIndex(Mocks::Window(), 0);
|
||||
|
||||
const auto actual = m_zoneWindow->ActiveZoneSet();
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveWindowIntoZoneByDirectionNoActiveZoneSet)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
Assert::IsNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
m_zoneWindow->MoveWindowIntoZoneByIndex(Mocks::Window(), 0);
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveWindowIntoZoneByDirection)
|
||||
{
|
||||
m_zoneWindow = InitZoneWindowWithActiveZoneSet();
|
||||
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
const auto window = Mocks::WindowCreate(m_hInst);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
|
||||
|
||||
const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap();
|
||||
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
|
||||
const auto actual = actualAppZoneHistory.begin()->second;
|
||||
Assert::AreEqual(0, actual.zoneIndex);
|
||||
}
|
||||
|
||||
TEST_METHOD(MoveWindowIntoZoneByDirectionManyTimes)
|
||||
{
|
||||
m_zoneWindow = InitZoneWindowWithActiveZoneSet();
|
||||
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
const auto window = Mocks::WindowCreate(m_hInst);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
|
||||
|
||||
const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap();
|
||||
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
|
||||
const auto actual = actualAppZoneHistory.begin()->second;
|
||||
Assert::AreEqual(2, actual.zoneIndex);
|
||||
}
|
||||
|
||||
TEST_METHOD(SaveWindowProcessToZoneIndexNoActiveZoneSet)
|
||||
{
|
||||
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
|
||||
Assert::IsNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
m_zoneWindow->SaveWindowProcessToZoneIndex(Mocks::Window());
|
||||
|
||||
const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap();
|
||||
Assert::IsTrue(actualAppZoneHistory.empty());
|
||||
}
|
||||
|
||||
TEST_METHOD(SaveWindowProcessToZoneIndexNullptrWindow)
|
||||
{
|
||||
m_zoneWindow = InitZoneWindowWithActiveZoneSet();
|
||||
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
m_zoneWindow->SaveWindowProcessToZoneIndex(nullptr);
|
||||
|
||||
const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap();
|
||||
Assert::IsTrue(actualAppZoneHistory.empty());
|
||||
}
|
||||
|
||||
TEST_METHOD(SaveWindowProcessToZoneIndexNoWindowAdded)
|
||||
{
|
||||
m_zoneWindow = InitZoneWindowWithActiveZoneSet();
|
||||
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
auto window = Mocks::WindowCreate(m_hInst);
|
||||
auto zone = MakeZone(RECT{ 0, 0, 100, 100 });
|
||||
m_zoneWindow->ActiveZoneSet()->AddZone(zone);
|
||||
|
||||
m_zoneWindow->SaveWindowProcessToZoneIndex(window);
|
||||
|
||||
const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap();
|
||||
Assert::IsTrue(actualAppZoneHistory.empty());
|
||||
}
|
||||
|
||||
TEST_METHOD(SaveWindowProcessToZoneIndexNoWindowAddedWithFilledAppZoneHistory)
|
||||
{
|
||||
m_zoneWindow = InitZoneWindowWithActiveZoneSet();
|
||||
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
const auto window = Mocks::WindowCreate(m_hInst);
|
||||
const auto processPath = get_process_path(window);
|
||||
const auto deviceId = m_zoneWindow->UniqueId();
|
||||
const auto zoneSetId = m_zoneWindow->ActiveZoneSet()->Id();
|
||||
|
||||
//fill app zone history map
|
||||
Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, deviceId, GuidString(zoneSetId), 0));
|
||||
Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size());
|
||||
Assert::AreEqual(0, m_fancyZonesData.GetAppZoneHistoryMap().at(processPath).zoneIndex);
|
||||
|
||||
//add zone without window
|
||||
const auto zone = MakeZone(RECT{ 0, 0, 100, 100 });
|
||||
m_zoneWindow->ActiveZoneSet()->AddZone(zone);
|
||||
|
||||
m_zoneWindow->SaveWindowProcessToZoneIndex(window);
|
||||
Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size());
|
||||
Assert::AreEqual(0, m_fancyZonesData.GetAppZoneHistoryMap().at(processPath).zoneIndex);
|
||||
}
|
||||
|
||||
TEST_METHOD(SaveWindowProcessToZoneIndexWindowAdded)
|
||||
{
|
||||
m_zoneWindow = InitZoneWindowWithActiveZoneSet();
|
||||
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
auto window = Mocks::WindowCreate(m_hInst);
|
||||
const auto processPath = get_process_path(window);
|
||||
const auto deviceId = m_zoneWindow->UniqueId();
|
||||
const auto zoneSetId = m_zoneWindow->ActiveZoneSet()->Id();
|
||||
|
||||
auto zone = MakeZone(RECT{ 0, 0, 100, 100 });
|
||||
zone->AddWindowToZone(window, Mocks::Window(), false);
|
||||
m_zoneWindow->ActiveZoneSet()->AddZone(zone);
|
||||
|
||||
//fill app zone history map
|
||||
Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, deviceId, GuidString(zoneSetId), 2));
|
||||
Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size());
|
||||
Assert::AreEqual(2, m_fancyZonesData.GetAppZoneHistoryMap().at(processPath).zoneIndex);
|
||||
|
||||
m_zoneWindow->SaveWindowProcessToZoneIndex(window);
|
||||
|
||||
const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap();
|
||||
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
|
||||
const auto expected = m_zoneWindow->ActiveZoneSet()->GetZoneIndexFromWindow(window);
|
||||
const auto actual = m_fancyZonesData.GetAppZoneHistoryMap().at(processPath).zoneIndex;
|
||||
Assert::AreEqual(expected, actual);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user