Merge pull request #391 from microsoft/user/bretan/fz-multimon

Fix for #195 - Fancy Zones new editor needs to support multiple monitors
Fix for #292 - Zone Editor opens behind PowerToys Window
This commit is contained in:
Bret
2019-09-17 17:58:53 -07:00
committed by GitHub
9 changed files with 577 additions and 500 deletions

View File

@@ -27,12 +27,11 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser
return TRUE; return TRUE;
} }
// TODO: multimon support, need to pass the HMONITOR from the editor to here instead
// of using MonitorFromPoint
// This function is exported and called from FancyZonesEditor.exe to save a layout from the editor. // This function is exported and called from FancyZonesEditor.exe to save a layout from the editor.
STDAPI PersistZoneSet( STDAPI PersistZoneSet(
PCWSTR activeKey, // Registry key holding ActiveZoneSet PCWSTR activeKey, // Registry key holding ActiveZoneSet
PCWSTR resolutionKey, // Registry key for screen resolution PCWSTR resolutionKey, // Registry key to persist ZoneSet to
HMONITOR monitor,
WORD layoutId, // LayoutModel Id WORD layoutId, // LayoutModel Id
int zoneCount, // Number of zones in zones int zoneCount, // Number of zones in zones
int zones[]) // Array of zones serialized in left/top/right/bottom chunks int zones[]) // Array of zones serialized in left/top/right/bottom chunks
@@ -73,7 +72,7 @@ STDAPI PersistZoneSet(
ZoneSetConfig( ZoneSetConfig(
id, id,
layoutId, layoutId,
MonitorFromPoint({}, MONITOR_DEFAULTTOPRIMARY), reinterpret_cast<HMONITOR>(monitor),
resolutionKey, resolutionKey,
ZoneSetLayout::Custom, ZoneSetLayout::Custom,
0, 0, 0)); 0, 0, 0));

View File

@@ -20,7 +20,6 @@ namespace FancyZonesEditor
private ushort _idInitial = 0; private ushort _idInitial = 0;
public App() public App()
{ {
//init settings
_settings = new Settings(); _settings = new Settings();
} }
@@ -64,8 +63,7 @@ namespace FancyZonesEditor
} }
foundModel.IsSelected = true; foundModel.IsSelected = true;
// TODO: multimon
// Pass in the correct args to show on the desired monitor
EditorOverlay overlay = new EditorOverlay(); EditorOverlay overlay = new EditorOverlay();
overlay.Show(); overlay.Show();
overlay.DataContext = foundModel; overlay.DataContext = foundModel;

View File

@@ -41,7 +41,7 @@ namespace FancyZonesEditor
} }
else if (xDelta > 0) else if (xDelta > 0)
{ {
xDelta = Math.Min(xDelta, c_workArea.Width - rect.Width - rect.X); xDelta = Math.Min(xDelta, _settings.WorkArea.Width - rect.Width - rect.X);
} }
if (yDelta < 0) if (yDelta < 0)
@@ -50,7 +50,7 @@ namespace FancyZonesEditor
} }
else if (yDelta > 0) else if (yDelta > 0)
{ {
yDelta = Math.Min(yDelta, c_workArea.Height - rect.Height - rect.Y); yDelta = Math.Min(yDelta, _settings.WorkArea.Height - rect.Height - rect.Y);
} }
rect.X += (int) xDelta; rect.X += (int) xDelta;
@@ -69,13 +69,13 @@ namespace FancyZonesEditor
{ {
int newWidth = rect.Width + (int) xDelta; int newWidth = rect.Width + (int) xDelta;
if (newWidth < 48) if (newWidth < c_minZoneSize)
{ {
newWidth = 48; newWidth = c_minZoneSize;
} }
else if (newWidth > (c_workArea.Width - rect.X)) else if (newWidth > (_settings.WorkArea.Width - rect.X))
{ {
newWidth = (int) c_workArea.Width - rect.X; newWidth = (int) _settings.WorkArea.Width - rect.X;
} }
MinWidth = rect.Width = newWidth; MinWidth = rect.Width = newWidth;
} }
@@ -84,13 +84,13 @@ namespace FancyZonesEditor
{ {
int newHeight = rect.Height + (int)yDelta; int newHeight = rect.Height + (int)yDelta;
if (newHeight < 48) if (newHeight < c_minZoneSize)
{ {
newHeight = 48; newHeight = c_minZoneSize;
} }
else if (newHeight > (c_workArea.Height - rect.Y)) else if (newHeight > (_settings.WorkArea.Height - rect.Y))
{ {
newHeight = (int)c_workArea.Height - rect.Y; newHeight = (int)_settings.WorkArea.Height - rect.Y;
} }
MinHeight = rect.Height = newHeight; MinHeight = rect.Height = newHeight;
} }
@@ -98,10 +98,7 @@ namespace FancyZonesEditor
} }
private static int c_zIndex = 0; private static int c_zIndex = 0;
private static int c_minZoneSize = 48;
// TODO: multimon
// This needs to be the work area of the monitor we get launched on
private static Rect c_workArea = System.Windows.SystemParameters.WorkArea;
protected override void OnPreviewMouseDown(MouseButtonEventArgs e) protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{ {
@@ -163,5 +160,7 @@ namespace FancyZonesEditor
((Panel)Parent).Children.Remove(this); ((Panel)Parent).Children.Remove(this);
Model.RemoveZoneAt(ZoneIndex); Model.RemoveZoneAt(ZoneIndex);
} }
private Settings _settings = ((App)Application.Current).ZoneSettings;
} }
} }

View File

@@ -71,14 +71,10 @@ namespace FancyZonesEditor
InitializeComponent(); InitializeComponent();
Current = this; Current = this;
// TODO: multimon Left = _settings.WorkArea.Left;
// Need to set Left and Top to the correct monitor based on the Top = _settings.WorkArea.Top;
// foreground window passed in the command line arguments Width = _settings.WorkArea.Width;
Rect workArea = System.Windows.SystemParameters.WorkArea; Height = _settings.WorkArea.Height;
Left = workArea.Left;
Top = workArea.Top;
Width = workArea.Width;
Height = workArea.Height;
} }
void onLoad(object sender, RoutedEventArgs e) void onLoad(object sender, RoutedEventArgs e)
@@ -98,6 +94,7 @@ namespace FancyZonesEditor
MainWindow window = new MainWindow(); MainWindow window = new MainWindow();
window.Owner = this; window.Owner = this;
window.ShowActivated = true;
window.Show(); window.Show();
} }

View File

@@ -155,7 +155,8 @@ namespace FancyZonesEditor.Models
internal delegate int PersistZoneSet( internal delegate int PersistZoneSet(
[MarshalAs(UnmanagedType.LPWStr)] string activeKey, [MarshalAs(UnmanagedType.LPWStr)] string activeKey,
[MarshalAs(UnmanagedType.LPWStr)] string key, [MarshalAs(UnmanagedType.LPWStr)] string resolutionKey,
uint monitor,
ushort layoutId, ushort layoutId,
int zoneCount, int zoneCount,
[MarshalAs(UnmanagedType.LPArray)] int[] zoneArray); [MarshalAs(UnmanagedType.LPArray)] int[] zoneArray);
@@ -186,14 +187,12 @@ namespace FancyZonesEditor.Models
// Scale all the zones to the DPI and then pack them up to be marshalled. // Scale all the zones to the DPI and then pack them up to be marshalled.
int zoneCount = zones.Length; int zoneCount = zones.Length;
var zoneArray = new int[zoneCount * 4]; var zoneArray = new int[zoneCount * 4];
var graphics = System.Drawing.Graphics.FromHwnd(IntPtr.Zero);
float dpi = graphics.DpiX / 96;
for (int i = 0; i < zones.Length; i++) for (int i = 0; i < zones.Length; i++)
{ {
var left = (int)(zones[i].X * dpi); var left = (int)(zones[i].X * Settings.Dpi);
var top = (int)(zones[i].Y * dpi); var top = (int)(zones[i].Y * Settings.Dpi);
var right = left + (int)(zones[i].Width * dpi); var right = left + (int)(zones[i].Width * Settings.Dpi);
var bottom = top + (int)(zones[i].Height * dpi); var bottom = top + (int)(zones[i].Height * Settings.Dpi);
var index = i * 4; var index = i * 4;
zoneArray[index] = left; zoneArray[index] = left;
@@ -202,22 +201,8 @@ namespace FancyZonesEditor.Models
zoneArray[index+3] = bottom; zoneArray[index+3] = bottom;
} }
string[] args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{
// args[1] = registry key value of currently active ZoneSet
// args[2] = id of layout to load at startup
string uniqueId = args[1];
// TODO: multimon
double height = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;
double width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
var key = width.ToString() + "_" + height.ToString();
var persistZoneSet = Marshal.GetDelegateForFunctionPointer<Native.PersistZoneSet>(pfn); var persistZoneSet = Marshal.GetDelegateForFunctionPointer<Native.PersistZoneSet>(pfn);
persistZoneSet(uniqueId, key, _id, zoneCount, zoneArray); persistZoneSet(Settings.UniqueKey, Settings.WorkAreaKey, Settings.Monitor, _id, zoneCount, zoneArray);
}
} }
private static readonly string c_registryPath = Settings.RegistryPath + "\\Layouts"; private static readonly string c_registryPath = Settings.RegistryPath + "\\Layouts";

View File

@@ -23,11 +23,11 @@ namespace FancyZonesEditor
{ {
public Settings() public Settings()
{ {
Rect workArea = System.Windows.SystemParameters.WorkArea; ParseCommandLineArgs();
// Initialize the five default layout models: Focus, Columns, Rows, Grid, and PriorityGrid // Initialize the five default layout models: Focus, Columns, Rows, Grid, and PriorityGrid
_defaultModels = new List<LayoutModel>(5); _defaultModels = new List<LayoutModel>(5);
_focusModel = new CanvasLayoutModel("Focus", c_focusModelId, (int)workArea.Width, (int)workArea.Height); _focusModel = new CanvasLayoutModel("Focus", c_focusModelId, (int)_workArea.Width, (int)_workArea.Height);
_defaultModels.Add(_focusModel); _defaultModels.Add(_focusModel);
_columnsModel = new GridLayoutModel("Columns", c_columnsModelId); _columnsModel = new GridLayoutModel("Columns", c_columnsModelId);
@@ -46,11 +46,11 @@ namespace FancyZonesEditor
_priorityGridModel = new GridLayoutModel("Priority Grid", c_priorityGridModelId); _priorityGridModel = new GridLayoutModel("Priority Grid", c_priorityGridModelId);
_defaultModels.Add(_priorityGridModel); _defaultModels.Add(_priorityGridModel);
_blankCustomModel = new CanvasLayoutModel("Create new custom", c_blankCustomModelId, (int)workArea.Width, (int)workArea.Height); _blankCustomModel = new CanvasLayoutModel("Create new custom", c_blankCustomModelId, (int)_workArea.Width, (int)_workArea.Height);
_zoneCount = (int)Registry.GetValue(FullRegistryPath, "ZoneCount", 3); _zoneCount = (int)Registry.GetValue(_uniqueRegistryPath, "ZoneCount", 3);
_spacing = (int)Registry.GetValue(FullRegistryPath, "Spacing", 16); _spacing = (int)Registry.GetValue(_uniqueRegistryPath, "Spacing", 16);
_showSpacing = (int)Registry.GetValue(FullRegistryPath, "ShowSpacing", 1) == 1; _showSpacing = (int)Registry.GetValue(_uniqueRegistryPath, "ShowSpacing", 1) == 1;
UpdateLayoutModels(); UpdateLayoutModels();
} }
@@ -64,7 +64,7 @@ namespace FancyZonesEditor
if (_zoneCount != value) if (_zoneCount != value)
{ {
_zoneCount = value; _zoneCount = value;
Registry.SetValue(FullRegistryPath, "ZoneCount", _zoneCount, RegistryValueKind.DWord); Registry.SetValue(_uniqueRegistryPath, "ZoneCount", _zoneCount, RegistryValueKind.DWord);
UpdateLayoutModels(); UpdateLayoutModels();
FirePropertyChanged("ZoneCount"); FirePropertyChanged("ZoneCount");
} }
@@ -81,7 +81,7 @@ namespace FancyZonesEditor
if (_spacing != value) if (_spacing != value)
{ {
_spacing = value; _spacing = value;
Registry.SetValue(FullRegistryPath, "Spacing", _spacing, RegistryValueKind.DWord); Registry.SetValue(_uniqueRegistryPath, "Spacing", _spacing, RegistryValueKind.DWord);
FirePropertyChanged("Spacing"); FirePropertyChanged("Spacing");
} }
} }
@@ -97,7 +97,7 @@ namespace FancyZonesEditor
if (_showSpacing != value) if (_showSpacing != value)
{ {
_showSpacing = value; _showSpacing = value;
Registry.SetValue(FullRegistryPath, "ShowSpacing", _showSpacing, RegistryValueKind.DWord); Registry.SetValue(_uniqueRegistryPath, "ShowSpacing", _showSpacing, RegistryValueKind.DWord);
FirePropertyChanged("ShowSpacing"); FirePropertyChanged("ShowSpacing");
} }
} }
@@ -134,6 +134,37 @@ namespace FancyZonesEditor
} }
private bool _isCtrlKeyPressed; private bool _isCtrlKeyPressed;
public Rect WorkArea
{
get { return _workArea; }
}
private Rect _workArea;
public static uint Monitor
{
get { return _monitor; }
}
private static uint _monitor;
public static String UniqueKey
{
get { return _uniqueKey; }
}
private static String _uniqueKey;
private String _uniqueRegistryPath;
public static String WorkAreaKey
{
get { return _workAreaKey; }
}
private static String _workAreaKey;
public static float Dpi
{
get { return _dpi; }
}
private static float _dpi;
// UpdateLayoutModels // UpdateLayoutModels
// Update the five default layouts based on the new ZoneCount // Update the five default layouts based on the new ZoneCount
private void UpdateLayoutModels() private void UpdateLayoutModels()
@@ -233,6 +264,46 @@ namespace FancyZonesEditor
} }
} }
private void ParseCommandLineArgs()
{
_workArea = System.Windows.SystemParameters.WorkArea;
_monitor = 0;
_uniqueRegistryPath = FullRegistryPath;
_uniqueKey = "";
_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 (where EditorOverlay shows up)
// 5 = resolution key (passed back to engine to persist data)
// 6 = monitor DPI (float)
_uniqueKey = args[1];
_uniqueRegistryPath += "\\" + _uniqueKey;
var parsedLocation = args[4].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];
_dpi = float.Parse(args[6]);
_workArea = new Rect(x, y, width, height);
uint monitor = 0;
if (uint.TryParse(args[4], out monitor))
{
_monitor = monitor;
}
}
}
public IList<LayoutModel> DefaultModels { get { return _defaultModels; } } public IList<LayoutModel> DefaultModels { get { return _defaultModels; } }
public ObservableCollection<LayoutModel> CustomModels public ObservableCollection<LayoutModel> CustomModels
{ {

View File

@@ -1,4 +1,5 @@
#include "pch.h" #include "pch.h"
#include "common/dpi_aware.h"
struct FancyZones : public winrt::implements<FancyZones, IFancyZones, IFancyZonesCallback, IZoneWindowHost> struct FancyZones : public winrt::implements<FancyZones, IFancyZones, IFancyZonesCallback, IZoneWindowHost>
{ {
@@ -237,18 +238,43 @@ void FancyZones::ToggleEditor() noexcept
m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr)); m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr));
} }
// TODO: multimon support const HWND foregroundWindow = GetForegroundWindow();
// Pass in args so that the editor shows up on the correct monitor if (const HMONITOR monitor = MonitorFromWindow(foregroundWindow, MONITOR_DEFAULTTOPRIMARY))
// This can be an HWND, HMONITOR, or the X/Y/Width/Height of the monitor's work area, (whichever works best).
if (const HMONITOR monitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY))
{ {
std::shared_lock readLock(m_lock); std::shared_lock readLock(m_lock);
auto iter = m_zoneWindowMap.find(monitor); auto iter = m_zoneWindowMap.find(monitor);
if (iter != m_zoneWindowMap.end()) if (iter != m_zoneWindowMap.end())
{ {
// Pass command line args to the editor to tell it which layout it should pick by default UINT dpi_x = 96;
auto activeZoneSet = iter->second->ActiveZoneSet(); UINT dpi_y = 96;
std::wstring params = iter->second->UniqueId() + L" " + std::to_wstring(activeZoneSet->LayoutId()); DPIAware::GetScreenDPIForWindow(foregroundWindow, dpi_x, dpi_y);
MONITORINFOEX mi;
mi.cbSize = sizeof(mi);
GetMonitorInfo(monitor, &mi);
// X/Y need to start in unscaled screen coordinates to get to the proper top/left of the monitor
// From there, we need to scale the difference between the monitor and workarea rects to get the
// appropriate offset where the overlay should appear.
// This covers the cases where the taskbar is not at the bottom of the screen.
const auto x = mi.rcMonitor.left + MulDiv(mi.rcWork.left - mi.rcMonitor.left, 96, dpi_x);
const auto y = mi.rcMonitor.top + MulDiv(mi.rcWork.top - mi.rcMonitor.top, 96, dpi_y);
// Location that the editor should occupy, scaled by DPI
std::wstring editorLocation =
std::to_wstring(x) + L"_" +
std::to_wstring(y) + L"_" +
std::to_wstring(MulDiv(mi.rcWork.right - mi.rcWork.left, 96, dpi_x)) + L"_" +
std::to_wstring(MulDiv(mi.rcWork.bottom - mi.rcWork.top, 96, dpi_y));
const std::wstring params =
iter->second->UniqueId() + L" " +
std::to_wstring(iter->second->ActiveZoneSet()->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) / 96.0f);
SHELLEXECUTEINFO sei{ sizeof(sei) }; SHELLEXECUTEINFO sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = L"modules\\FancyZonesEditor.exe"; sei.lpFile = L"modules\\FancyZonesEditor.exe";

View File

@@ -18,6 +18,7 @@ public:
IFACEMETHODIMP_(void) CycleActiveZoneSet(DWORD vkCode) noexcept; IFACEMETHODIMP_(void) CycleActiveZoneSet(DWORD vkCode) noexcept;
IFACEMETHODIMP_(std::wstring) DeviceId() noexcept { return { m_deviceId.get() }; } IFACEMETHODIMP_(std::wstring) DeviceId() noexcept { return { m_deviceId.get() }; }
IFACEMETHODIMP_(std::wstring) UniqueId() noexcept { return { m_uniqueId }; } IFACEMETHODIMP_(std::wstring) UniqueId() noexcept { return { m_uniqueId }; }
IFACEMETHODIMP_(std::wstring) WorkAreaKey() noexcept { return { m_workArea }; }
IFACEMETHODIMP_(void) SaveWindowProcessToZoneIndex(HWND window) noexcept; IFACEMETHODIMP_(void) SaveWindowProcessToZoneIndex(HWND window) noexcept;
IFACEMETHODIMP_(IZoneSet*) ActiveZoneSet() noexcept { return m_activeZoneSet.get(); } IFACEMETHODIMP_(IZoneSet*) ActiveZoneSet() noexcept { return m_activeZoneSet.get(); }

View File

@@ -16,6 +16,7 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
IFACEMETHOD_(void, SaveWindowProcessToZoneIndex)(HWND window) = 0; IFACEMETHOD_(void, SaveWindowProcessToZoneIndex)(HWND window) = 0;
IFACEMETHOD_(std::wstring, DeviceId)() = 0; IFACEMETHOD_(std::wstring, DeviceId)() = 0;
IFACEMETHOD_(std::wstring, UniqueId)() = 0; IFACEMETHOD_(std::wstring, UniqueId)() = 0;
IFACEMETHOD_(std::wstring, WorkAreaKey)() = 0;
IFACEMETHOD_(IZoneSet*, ActiveZoneSet)() = 0; IFACEMETHOD_(IZoneSet*, ActiveZoneSet)() = 0;
}; };