Skip to content

Commit

Permalink
[FancyZones Editor] UI fixes (#18966)
Browse files Browse the repository at this point in the history
* canvas scaling

* moved editor params saving

* show monitor size

* removed unused cmd args

* separate dpi unaware thread

* tests

* dpi unaware monitor size

* spell

* early return on editor params saving error

* show scaling value

* changed font
  • Loading branch information
SeraphimaZykova committed Jun 29, 2022
1 parent 81f9926 commit 9e0781d
Show file tree
Hide file tree
Showing 20 changed files with 297 additions and 509 deletions.
1 change: 1 addition & 0 deletions .github/actions/spell-check/expect.txt
Expand Up @@ -1747,6 +1747,7 @@ robmensching
Roboto
roslyn
Rothera
roundf
ROUNDSMALL
royvou
Rpc
Expand Down
198 changes: 198 additions & 0 deletions src/modules/fancyzones/FancyZonesLib/EditorParameters.cpp
@@ -0,0 +1,198 @@
#include "pch.h"
#include "EditorParameters.h"

#include <FancyZonesLib/FancyZonesWindowProperties.h>
#include <FancyZonesLib/on_thread_executor.h>
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/VirtualDesktop.h>
#include <FancyZonesLib/util.h>

#include <common/Display/dpi_aware.h>
#include <common/logger/logger.h>


// Non-localizable strings
namespace NonLocalizable
{
const wchar_t FancyZonesEditorParametersFile[] = L"editor-parameters.json";
}

namespace JsonUtils
{
struct MonitorInfo
{
std::wstring monitorName;
std::wstring virtualDesktop;
int dpi;
int top;
int left;
int workAreaWidth;
int workAreaHeight;
int monitorWidth;
int monitorHeight;
bool isSelected = false;

static json::JsonObject ToJson(const MonitorInfo& monitor)
{
json::JsonObject result{};

result.SetNamedValue(NonLocalizable::EditorParametersIds::MonitorNameId, json::value(monitor.monitorName));
result.SetNamedValue(NonLocalizable::EditorParametersIds::VirtualDesktopId, json::value(monitor.virtualDesktop));
result.SetNamedValue(NonLocalizable::EditorParametersIds::Dpi, json::value(monitor.dpi));
result.SetNamedValue(NonLocalizable::EditorParametersIds::TopCoordinate, json::value(monitor.top));
result.SetNamedValue(NonLocalizable::EditorParametersIds::LeftCoordinate, json::value(monitor.left));
result.SetNamedValue(NonLocalizable::EditorParametersIds::WorkAreaWidth, json::value(monitor.workAreaWidth));
result.SetNamedValue(NonLocalizable::EditorParametersIds::WorkAreaHeight, json::value(monitor.workAreaHeight));
result.SetNamedValue(NonLocalizable::EditorParametersIds::MonitorWidth, json::value(monitor.monitorWidth));
result.SetNamedValue(NonLocalizable::EditorParametersIds::MonitorHeight, json::value(monitor.monitorHeight));
result.SetNamedValue(NonLocalizable::EditorParametersIds::IsSelected, json::value(monitor.isSelected));

return result;
}
};

struct EditorArgs
{
DWORD processId;
bool spanZonesAcrossMonitors;
std::vector<MonitorInfo> monitors;

static json::JsonObject ToJson(const EditorArgs& args)
{
json::JsonObject result{};

result.SetNamedValue(NonLocalizable::EditorParametersIds::ProcessId, json::value(args.processId));
result.SetNamedValue(NonLocalizable::EditorParametersIds::SpanZonesAcrossMonitors, json::value(args.spanZonesAcrossMonitors));

json::JsonArray monitors;
for (const auto& monitor : args.monitors)
{
monitors.Append(MonitorInfo::ToJson(monitor));
}

result.SetNamedValue(NonLocalizable::EditorParametersIds::Monitors, monitors);

return result;
}
};
}

bool EditorParameters::Save() noexcept
{
const auto virtualDesktopIdStr = FancyZonesUtils::GuidToString(VirtualDesktop::instance().GetCurrentVirtualDesktopId());
if (!virtualDesktopIdStr)
{
Logger::error(L"Save editor params: no virtual desktop id");
return false;
}

OnThreadExecutor dpiUnawareThread;
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [] {
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED);
} }).wait();

const bool spanZonesAcrossMonitors = FancyZonesSettings::settings().spanZonesAcrossMonitors;

JsonUtils::EditorArgs argsJson;
argsJson.processId = GetCurrentProcessId();
argsJson.spanZonesAcrossMonitors = spanZonesAcrossMonitors;

if (spanZonesAcrossMonitors)
{
RECT combinedWorkArea;
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&]() {
combinedWorkArea = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>();

} }).wait();

RECT combinedMonitorArea = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcMonitor>();

JsonUtils::MonitorInfo monitorJson;
monitorJson.monitorName = ZonedWindowProperties::MultiMonitorDeviceID;
monitorJson.virtualDesktop = virtualDesktopIdStr.value();
monitorJson.top = combinedWorkArea.top;
monitorJson.left = combinedWorkArea.left;
monitorJson.workAreaWidth = combinedWorkArea.right - combinedWorkArea.left;
monitorJson.workAreaHeight = combinedWorkArea.bottom - combinedWorkArea.top;
monitorJson.monitorWidth = combinedMonitorArea.right - combinedMonitorArea.left;
monitorJson.monitorHeight = combinedMonitorArea.bottom - combinedMonitorArea.top;
monitorJson.isSelected = true;
monitorJson.dpi = 0; // unused

argsJson.monitors.emplace_back(std::move(monitorJson));
}
else
{
// device id map for correct device ids
std::unordered_map<std::wstring, DWORD> displayDeviceIdxMap;

std::vector<std::pair<HMONITOR, MONITORINFOEX>> allMonitors;
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&]() {
allMonitors = FancyZonesUtils::GetAllMonitorInfo<&MONITORINFOEX::rcWork>();
} }).wait();

HMONITOR targetMonitor{};
if (FancyZonesSettings::settings().use_cursorpos_editor_startupscreen)
{
POINT currentCursorPos{};
GetCursorPos(&currentCursorPos);
targetMonitor = MonitorFromPoint(currentCursorPos, MONITOR_DEFAULTTOPRIMARY);
}
else
{
targetMonitor = MonitorFromWindow(GetForegroundWindow(), MONITOR_DEFAULTTOPRIMARY);
}

if (!targetMonitor)
{
Logger::error("No target monitor to open editor");
return false;
}

for (auto& monitorData : allMonitors)
{
HMONITOR monitor = monitorData.first;
auto monitorInfo = monitorData.second;

JsonUtils::MonitorInfo monitorJson;

std::wstring deviceId = FancyZonesUtils::GetDisplayDeviceId(monitorInfo.szDevice, displayDeviceIdxMap);

if (monitor == targetMonitor)
{
monitorJson.isSelected = true; /* Is monitor selected for the main editor window opening */
}

monitorJson.monitorName = FancyZonesUtils::TrimDeviceId(deviceId);
monitorJson.virtualDesktop = virtualDesktopIdStr.value();

UINT dpi = 0;
if (DPIAware::GetScreenDPIForMonitor(monitor, dpi) != S_OK)
{
continue;
}

monitorJson.dpi = dpi;
monitorJson.top = monitorInfo.rcWork.top;
monitorJson.left = monitorInfo.rcWork.left;
monitorJson.workAreaWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
monitorJson.workAreaHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;

float width = static_cast<float>(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left);
float height = static_cast<float>(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top);
DPIAware::Convert(monitor, width, height);

monitorJson.monitorWidth = static_cast<int>(std::roundf(width));
monitorJson.monitorHeight = static_cast<int>(std::roundf(height));

argsJson.monitors.emplace_back(std::move(monitorJson));
}
}

std::wstring folderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
std::wstring fileName = folderPath + L"\\" + std::wstring(NonLocalizable::FancyZonesEditorParametersFile);
json::to_file(fileName, JsonUtils::EditorArgs::ToJson(argsJson));

return true;
}
27 changes: 27 additions & 0 deletions src/modules/fancyzones/FancyZonesLib/EditorParameters.h
@@ -0,0 +1,27 @@
#pragma once

namespace NonLocalizable
{
namespace EditorParametersIds
{
const static wchar_t* Dpi = L"dpi";
const static wchar_t* MonitorNameId = L"monitor";
const static wchar_t* VirtualDesktopId = L"virtual-desktop";
const static wchar_t* TopCoordinate = L"top-coordinate";
const static wchar_t* LeftCoordinate = L"left-coordinate";
const static wchar_t* WorkAreaWidth = L"work-area-width";
const static wchar_t* WorkAreaHeight = L"work-area-height";
const static wchar_t* MonitorWidth = L"monitor-width";
const static wchar_t* MonitorHeight = L"monitor-height";
const static wchar_t* IsSelected = L"is-selected";
const static wchar_t* ProcessId = L"process-id";
const static wchar_t* SpanZonesAcrossMonitors = L"span-zones-across-monitors";
const static wchar_t* Monitors = L"monitors";
}
}

class EditorParameters
{
public:
static bool Save() noexcept;
};
117 changes: 5 additions & 112 deletions src/modules/fancyzones/FancyZonesLib/FancyZones.cpp
Expand Up @@ -11,6 +11,7 @@
#include <common/utils/window.h>
#include <common/SettingsAPI/FileWatcher.h>

#include <FancyZonesLib/EditorParameters.h>
#include <FancyZonesLib/FancyZonesData.h>
#include <FancyZonesLib/FancyZonesData/AppliedLayouts.h>
#include <FancyZonesLib/FancyZonesData/AppZoneHistory.h>
Expand Down Expand Up @@ -487,124 +488,16 @@ void FancyZones::ToggleEditor() noexcept

m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr));

HMONITOR targetMonitor{};

const bool use_cursorpos_editor_startupscreen = FancyZonesSettings::settings().use_cursorpos_editor_startupscreen;
if (use_cursorpos_editor_startupscreen)
{
POINT currentCursorPos{};
GetCursorPos(&currentCursorPos);
targetMonitor = MonitorFromPoint(currentCursorPos, MONITOR_DEFAULTTOPRIMARY);
}
else
{
targetMonitor = MonitorFromWindow(GetForegroundWindow(), MONITOR_DEFAULTTOPRIMARY);
}

if (!targetMonitor)
{
return;
}

wil::unique_cotaskmem_string virtualDesktopId;
if (!SUCCEEDED(StringFromCLSID(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), &virtualDesktopId)))
if (!EditorParameters::Save())
{
Logger::error(L"Failed to save editor startup parameters");
return;
}

/*
* Divider: /
* Parts:
* (1) Process id
* (2) Span zones across monitors
* (3) Monitor id where the Editor should be opened
* (4) Monitors count
*
* Data for each monitor:
* (5) Monitor id
* (6) DPI
* (7) work area left
* (8) work area top
* (9) work area width
* (10) work area height
* ...
*/
std::wstring params;
const std::wstring divider = L"/";
params += std::to_wstring(GetCurrentProcessId()) + divider; /* Process id */
const bool spanZonesAcrossMonitors = FancyZonesSettings::settings().spanZonesAcrossMonitors;
params += std::to_wstring(spanZonesAcrossMonitors) + divider; /* Span zones */
std::vector<std::pair<HMONITOR, MONITORINFOEX>> allMonitors;

m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
allMonitors = FancyZonesUtils::GetAllMonitorInfo<&MONITORINFOEX::rcWork>();
} }).wait();

if (spanZonesAcrossMonitors)
{
params += FancyZonesUtils::GenerateUniqueIdAllMonitorsArea(virtualDesktopId.get()) + divider; /* Monitor id where the Editor should be opened */
}

// device id map
std::unordered_map<std::wstring, DWORD> displayDeviceIdxMap;

bool showDpiWarning = false;
int prevDpi = -1;
std::wstring monitorsDataStr;

for (auto& monitorData : allMonitors)
{
HMONITOR monitor = monitorData.first;
auto monitorInfo = monitorData.second;

std::wstring deviceId = FancyZonesUtils::GetDisplayDeviceId(monitorInfo.szDevice, displayDeviceIdxMap);
std::wstring monitorId = FancyZonesUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get());

if (monitor == targetMonitor && !spanZonesAcrossMonitors)
{
params += monitorId + divider; /* Monitor id where the Editor should be opened */
}

UINT dpi = 0;
if (DPIAware::GetScreenDPIForMonitor(monitor, dpi) != S_OK)
{
continue;
}

if (spanZonesAcrossMonitors && prevDpi != -1 && prevDpi != dpi)
{
showDpiWarning = true;
}

monitorsDataStr += std::move(monitorId) + divider; /* Monitor id */
monitorsDataStr += std::to_wstring(dpi) + divider; /* DPI */
monitorsDataStr += std::to_wstring(monitorInfo.rcWork.left) + divider; /* Top coordinate */
monitorsDataStr += std::to_wstring(monitorInfo.rcWork.top) + divider; /* Left coordinate */
monitorsDataStr += std::to_wstring(monitorInfo.rcWork.right - monitorInfo.rcWork.left) + divider; /* Width */
monitorsDataStr += std::to_wstring(monitorInfo.rcWork.bottom - monitorInfo.rcWork.top) + divider; /* Height */
}

params += std::to_wstring(allMonitors.size()) + divider; /* Monitors count */
params += monitorsDataStr;

FancyZonesDataInstance().SaveFancyZonesEditorParameters(spanZonesAcrossMonitors, virtualDesktopId.get(), targetMonitor, allMonitors); /* Write parameters to json file */

if (showDpiWarning)
{
// We must show the message box in a separate thread, since this code is called from a low-level
// keyboard hook callback, and launching messageboxes from it has unexpected side effects
//std::thread{ [] {
// MessageBoxW(nullptr,
// GET_RESOURCE_STRING(IDS_SPAN_ACROSS_ZONES_WARNING).c_str(),
// GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(),
// MB_OK | MB_ICONWARNING);
//} }.detach();
}


SHELLEXECUTEINFO sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = NonLocalizable::FZEditorExecutablePath;
sei.lpParameters = params.c_str();
sei.lpParameters = L"";
sei.nShow = SW_SHOWDEFAULT;
ShellExecuteEx(&sei);
Trace::FancyZones::EditorLaunched(1);
Expand Down

0 comments on commit 9e0781d

Please sign in to comment.