Merge remote-tracking branch 'origin/main' into dev/snickler/net8-upgrade

This commit is contained in:
Jeremy Sinclair 2023-08-23 20:01:08 -04:00
commit 723e49b662
60 changed files with 3162 additions and 1469 deletions

View File

@ -192,6 +192,29 @@ configuration:
- addLabel:
label: Needs-Author-Feedback
description:
- if:
- payloadType: Issue_Comment
- commentContains:
pattern: '\/helped'
isRegex: True
- or:
- activitySenderHasAssociation:
association: Owner
- activitySenderHasAssociation:
association: Member
- activitySenderHasAssociation:
association: Collaborator
then:
- removeLabel:
label: Needs-Triage
- removeLabel:
label: Needs-Team-Response
- addLabel:
label: Resolution-Helped User
- addReply:
reply: This issue is now marked as resolved. If you have any follow-up questions, please don't hesitate to ask. You can find out more about PowerToys functionalities in our [end-user documentation](https://aka.ms/powertoy-docs].
- closeIssue
description:
- if:
- payloadType: Issue_Comment
- commentContains:

View File

@ -73,4 +73,4 @@
<PackageVersion Include="Microsoft.VariantAssignment.Client" Version="2.4.17140001" />
<PackageVersion Include="Microsoft.VariantAssignment.Contract" Version="3.0.16990001" />
</ItemGroup>
</Project>
</Project>

View File

@ -24,6 +24,10 @@ The Value Generator plugin is used to generate hashes for strings, to calculate
- Implements IComputeRequest
- `Compute()` will populate `Result` with the base64 encoding of the byte array passed in the constructor
### [`Base64DecodeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs)
- Implements IComputeRequest
- `Compute()` will populate `Result` with the decoded byte array of the base64 string passed in the constructor
### [`GUIDRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDRequest.cs)
- Implements IComputeRequest
- Uses the [`GUIDGenerator`](#guidgenerator) class to generate or compute the requested GUID

View File

@ -1,7 +1,7 @@
# UI Architecture
The UI code is distributed between two projects: [`PowerToys.Settings`](/src/settings-ui/Settings.UI) and [`Settings.UI`](/src/settings-ui/Settings.UI.Library). [`PowerToys.Settings`](/src/settings-ui/Settings.UI) is a Windows App Sdk .net Unpackaged application. It contains the views for base navigation and modules. Parent display window and corresponding code is present in [`MainWindow.xaml.`](/src/settings-ui/Settings.UI/MainWindow.xaml). Fig 1 provides a description of the UI controls hierarchy and each of the controls have been summarized below :
- [`ShellPage.xaml`](/src/settings-ui/Settings.UI/Views/ShellPage.xaml) is a WinUI control, consisting of a side navigation panel with an icon for each module. Clicking on a module icon loads the corresponding `setting.json` file and displays the data in the UI.
The UI code is distributed between two projects: [`PowerToys.Settings`](/src/settings-ui/Settings.UI) and [`Settings.UI`](/src/settings-ui/Settings.UI.Library). [`PowerToys.Settings`](/src/settings-ui/Settings.UI) is a Windows App Sdk .net Unpackaged application. It contains the views for base navigation and modules. Parent display window and corresponding code is present in [`MainWindow.xaml`](/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml). Fig 1 provides a description of the UI controls hierarchy and each of the controls have been summarized below :
- [`ShellPage.xaml`](/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml) is a WinUI control, consisting of a side navigation panel with an icon for each module. Clicking on a module icon loads the corresponding `setting.json` file and displays the data in the UI.
![Settings UI architecture](/doc/images/settingsv2/ui-architecture.png)
**Fig 1: UI Architecture for settingsv2**

View File

@ -5,6 +5,8 @@
#include "keyboard_layout_impl.h"
#include "shared_constants.h"
constexpr DWORD numpadOriginBit = 1ull << 31;
LayoutMap::LayoutMap() :
impl(new LayoutMap::LayoutMapImpl())
{
@ -110,7 +112,6 @@ void LayoutMap::LayoutMapImpl::UpdateLayout()
keyboardLayoutMap[VK_BACK] = L"Backspace";
keyboardLayoutMap[VK_TAB] = L"Tab";
keyboardLayoutMap[VK_CLEAR] = L"Clear";
keyboardLayoutMap[VK_RETURN] = L"Enter";
keyboardLayoutMap[VK_SHIFT] = L"Shift";
keyboardLayoutMap[VK_CONTROL] = L"Ctrl";
keyboardLayoutMap[VK_MENU] = L"Alt";
@ -118,20 +119,36 @@ void LayoutMap::LayoutMapImpl::UpdateLayout()
keyboardLayoutMap[VK_CAPITAL] = L"Caps Lock";
keyboardLayoutMap[VK_ESCAPE] = L"Esc";
keyboardLayoutMap[VK_SPACE] = L"Space";
keyboardLayoutMap[VK_LEFT] = L"Left";
keyboardLayoutMap[VK_RIGHT] = L"Right";
keyboardLayoutMap[VK_UP] = L"Up";
keyboardLayoutMap[VK_DOWN] = L"Down";
keyboardLayoutMap[VK_INSERT] = L"Insert";
keyboardLayoutMap[VK_DELETE] = L"Delete";
keyboardLayoutMap[VK_PRIOR] = L"PgUp";
keyboardLayoutMap[VK_NEXT] = L"PgDn";
keyboardLayoutMap[VK_END] = L"End";
keyboardLayoutMap[VK_HOME] = L"Home";
keyboardLayoutMap[VK_LEFT] = L"Left";
keyboardLayoutMap[VK_UP] = L"Up";
keyboardLayoutMap[VK_RIGHT] = L"Right";
keyboardLayoutMap[VK_DOWN] = L"Down";
keyboardLayoutMap[VK_END] = L"End";
keyboardLayoutMap[VK_RETURN] = L"Enter";
keyboardLayoutMap[VK_LEFT | numpadOriginBit] = L"Left (Numpad)";
keyboardLayoutMap[VK_RIGHT | numpadOriginBit] = L"Right (Numpad)";
keyboardLayoutMap[VK_UP | numpadOriginBit] = L"Up (Numpad)";
keyboardLayoutMap[VK_DOWN | numpadOriginBit] = L"Down (Numpad)";
keyboardLayoutMap[VK_INSERT | numpadOriginBit] = L"Insert (Numpad)";
keyboardLayoutMap[VK_DELETE | numpadOriginBit] = L"Delete (Numpad)";
keyboardLayoutMap[VK_PRIOR | numpadOriginBit] = L"PgUp (Numpad)";
keyboardLayoutMap[VK_NEXT | numpadOriginBit] = L"PgDn (Numpad)";
keyboardLayoutMap[VK_HOME | numpadOriginBit] = L"Home (Numpad)";
keyboardLayoutMap[VK_END | numpadOriginBit] = L"End (Numpad)";
keyboardLayoutMap[VK_RETURN | numpadOriginBit] = L"Enter (Numpad)";
keyboardLayoutMap[VK_DIVIDE | numpadOriginBit] = L"/ (Numpad)";
keyboardLayoutMap[VK_SELECT] = L"Select";
keyboardLayoutMap[VK_PRINT] = L"Print";
keyboardLayoutMap[VK_EXECUTE] = L"Execute";
keyboardLayoutMap[VK_SNAPSHOT] = L"Print Screen";
keyboardLayoutMap[VK_INSERT] = L"Insert";
keyboardLayoutMap[VK_DELETE] = L"Delete";
keyboardLayoutMap[VK_HELP] = L"Help";
keyboardLayoutMap[VK_LWIN] = L"Win (Left)";
keyboardLayoutMap[VK_RWIN] = L"Win (Right)";
@ -275,6 +292,12 @@ std::vector<DWORD> LayoutMap::LayoutMapImpl::GetKeyCodeList(const bool isShortcu
}
}
// Add numpad keys
for (auto it = keyboardLayoutMap.rbegin(); it->first & numpadOriginBit; ++it)
{
keyCodes.push_back(it->first);
}
// Sort the special keys in alphabetical order
std::sort(specialKeys.begin(), specialKeys.end(), [&](const DWORD& lhs, const DWORD& rhs) {
return keyboardLayoutMap[lhs] < keyboardLayoutMap[rhs];

View File

@ -26,7 +26,8 @@
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/SettingsObserver.h>
#include <FancyZonesLib/trace.h>
#include <FancyZonesLib/WindowDrag.h>
#include <FancyZonesLib/WindowKeyboardSnap.h>
#include <FancyZonesLib/WindowMouseSnap.h>
#include <FancyZonesLib/WorkArea.h>
enum class DisplayChangeType
@ -142,10 +143,6 @@ protected:
private:
void UpdateWorkAreas(bool updateWindowPositions) noexcept;
void CycleWindows(bool reverse) noexcept;
bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept;
bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept;
bool OnSnapHotkey(DWORD vkCode) noexcept;
bool ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea) noexcept;
void SyncVirtualDesktops() noexcept;
@ -153,13 +150,11 @@ private:
bool MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept;
void UpdateActiveLayouts() noexcept;
void RefreshLayouts() noexcept;
bool ShouldProcessSnapHotkey(DWORD vkCode) noexcept;
void ApplyQuickLayout(int key) noexcept;
void FlashZones() noexcept;
std::vector<std::pair<HMONITOR, RECT>> GetRawMonitorData() noexcept;
std::vector<HMONITOR> GetMonitorsSorted() noexcept;
HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept;
virtual void SettingsUpdate(SettingId type) override;
@ -167,7 +162,8 @@ private:
const HINSTANCE m_hinstance{};
HWND m_window{};
std::unique_ptr<WindowDrag> m_windowDrag{};
std::unique_ptr<WindowMouseSnap> m_windowMouseSnapper{};
WindowKeyboardSnap m_windowKeyboardSnapper{};
MonitorWorkAreaMap m_workAreaHandler;
DraggingState m_draggingState;
@ -288,8 +284,8 @@ FancyZones::VirtualDesktopChanged() noexcept
void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor)
{
m_windowDrag = WindowDrag::Create(window, m_workAreaHandler.GetAllWorkAreas());
if (m_windowDrag)
m_windowMouseSnapper = WindowMouseSnap::Create(window, m_workAreaHandler.GetAllWorkAreas());
if (m_windowMouseSnapper)
{
if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{
@ -298,13 +294,13 @@ void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor)
m_draggingState.Enable();
m_draggingState.UpdateDraggingState();
m_windowDrag->MoveSizeStart(monitor, m_draggingState.IsDragging());
m_windowMouseSnapper->MoveSizeStart(monitor, m_draggingState.IsDragging());
}
}
void FancyZones::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen)
{
if (m_windowDrag)
if (m_windowMouseSnapper)
{
if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{
@ -312,17 +308,17 @@ void FancyZones::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen)
}
m_draggingState.UpdateDraggingState();
m_windowDrag->MoveSizeUpdate(monitor, ptScreen, m_draggingState.IsDragging(), m_draggingState.IsSelectManyZonesState());
m_windowMouseSnapper->MoveSizeUpdate(monitor, ptScreen, m_draggingState.IsDragging(), m_draggingState.IsSelectManyZonesState());
}
}
void FancyZones::MoveSizeEnd()
{
if (m_windowDrag)
if (m_windowMouseSnapper)
{
m_windowDrag->MoveSizeEnd();
m_windowMouseSnapper->MoveSizeEnd();
m_draggingState.Disable();
m_windowDrag = nullptr;
m_windowMouseSnapper = nullptr;
}
}
@ -339,7 +335,7 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept
workArea = workAreas.at(monitor).get();
if (workArea)
{
indexes = workArea->GetWindowZoneIndexes(window);
indexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId());
}
}
else
@ -353,7 +349,7 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept
{
if (secondaryWorkArea)
{
indexes = secondaryWorkArea->GetWindowZoneIndexes(window);
indexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, secondaryWorkArea->UniqueId(), secondaryWorkArea->GetLayoutId());
workArea = secondaryWorkArea.get();
if (!indexes.empty())
{
@ -365,8 +361,8 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept
if (!indexes.empty() && workArea)
{
Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
workArea->MoveWindowIntoZoneByIndexSet(window, indexes);
Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
workArea->Snap(window, indexes);
return true;
}
@ -604,7 +600,40 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
if (message == WM_PRIV_SNAP_HOTKEY)
{
OnSnapHotkey(static_cast<DWORD>(lparam));
// We already checked in ShouldProcessSnapHotkey whether the foreground window is a candidate for zoning
auto foregroundWindow = GetForegroundWindow();
HMONITOR monitor{ nullptr };
if (!FancyZonesSettings::settings().spanZonesAcrossMonitors)
{
monitor = MonitorFromWindow(foregroundWindow, MONITOR_DEFAULTTONULL);
}
if (FancyZonesSettings::settings().moveWindowsBasedOnPosition)
{
auto monitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
RECT windowRect;
if (GetWindowRect(foregroundWindow, &windowRect))
{
// Check whether Alt is used in the shortcut key combination
if (GetAsyncKeyState(VK_MENU) & 0x8000)
{
m_windowKeyboardSnapper.Extend(foregroundWindow, windowRect, monitor, static_cast<DWORD>(lparam), m_workAreaHandler.GetAllWorkAreas());
}
else
{
m_windowKeyboardSnapper.Snap(foregroundWindow, windowRect, monitor, static_cast<DWORD>(lparam), m_workAreaHandler.GetAllWorkAreas(), monitors);
}
}
else
{
Logger::error("Error snapping window by keyboard shortcut: failed to get window rect");
}
}
else
{
m_windowKeyboardSnapper.Snap(foregroundWindow, monitor, static_cast<DWORD>(lparam), m_workAreaHandler.GetAllWorkAreas(), FancyZonesUtils::GetMonitorsOrdered());
}
}
else if (message == WM_PRIV_INIT)
{
@ -661,7 +690,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
else if (message == WM_PRIV_APPLIED_LAYOUTS_FILE_UPDATE)
{
AppliedLayouts::instance().LoadData();
UpdateActiveLayouts();
RefreshLayouts();
}
else if (message == WM_PRIV_DEFAULT_LAYOUTS_FILE_UPDATE)
{
@ -800,7 +829,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
{
for (const auto& [window, zones] : windowsToSnap)
{
workArea->SnapWindow(window, zones, false);
workArea->Snap(window, zones, false);
}
}
}
@ -813,9 +842,9 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
const auto zones = iter->second;
const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
const auto workAreaForMonitor = m_workAreaHandler.GetWorkArea(monitor);
if (workAreaForMonitor && workAreaForMonitor->GetWindowZoneIndexes(window) == zones)
if (workAreaForMonitor && AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaForMonitor->UniqueId(), workAreaForMonitor->GetLayoutId()) == zones)
{
workAreaForMonitor->SnapWindow(window, zones, false);
workAreaForMonitor->Snap(window, zones, false);
iter = windowsToSnap.erase(iter);
}
else
@ -829,10 +858,10 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
{
for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas())
{
const auto savedIndexes = workArea->GetWindowZoneIndexes(window);
const auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId());
if (savedIndexes == zones)
{
workArea->SnapWindow(window, zones, false);
workArea->Snap(window, zones, false);
}
}
}
@ -862,257 +891,6 @@ void FancyZones::CycleWindows(bool reverse) noexcept
}
}
bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept
{
HMONITOR current = WorkAreaKeyFromWindow(window);
std::vector<HMONITOR> monitorInfo = GetMonitorsSorted();
if (current && monitorInfo.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
{
// Multi monitor environment.
auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current);
do
{
auto workArea = m_workAreaHandler.GetWorkArea(*currMonitorInfo);
if (workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */))
{
// unassign from previous work area
for (auto& [_, prevWorkArea] : m_workAreaHandler.GetAllWorkAreas())
{
if (prevWorkArea && workArea != prevWorkArea.get())
{
prevWorkArea->UnsnapWindow(window);
}
}
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
return true;
}
// We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction).
if (vkCode == VK_RIGHT)
{
currMonitorInfo = std::next(currMonitorInfo);
if (currMonitorInfo == std::end(monitorInfo))
{
currMonitorInfo = std::begin(monitorInfo);
}
}
else if (vkCode == VK_LEFT)
{
if (currMonitorInfo == std::begin(monitorInfo))
{
currMonitorInfo = std::end(monitorInfo);
}
currMonitorInfo = std::prev(currMonitorInfo);
}
} while (*currMonitorInfo != current);
}
else
{
auto workArea = m_workAreaHandler.GetWorkArea(current);
// Single monitor environment, or combined multi-monitor environment.
if (FancyZonesSettings::settings().restoreSize)
{
bool moved = workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */);
if (!moved)
{
FancyZonesWindowUtils::RestoreWindowOrigin(window);
FancyZonesWindowUtils::RestoreWindowSize(window);
}
else if (workArea)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
return moved;
}
else
{
bool moved = workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */);
if (moved)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
return moved;
}
}
return false;
}
bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
{
HMONITOR current = WorkAreaKeyFromWindow(window);
auto allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
if (current && allMonitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
{
// Multi monitor environment.
// First, try to stay on the same monitor
bool success = ProcessDirectedSnapHotkey(window, vkCode, false, m_workAreaHandler.GetWorkArea(current));
if (success)
{
return true;
}
// If that didn't work, extract zones from all other monitors and target one of them
std::vector<RECT> zoneRects;
std::vector<std::pair<ZoneIndex, WorkArea*>> zoneRectsInfo;
RECT currentMonitorRect{ .top = 0, .bottom = -1 };
for (const auto& [monitor, monitorRect] : allMonitors)
{
if (monitor == current)
{
currentMonitorRect = monitorRect;
}
else
{
auto workArea = m_workAreaHandler.GetWorkArea(monitor);
if (workArea)
{
const auto& layout = workArea->GetLayout();
if (layout)
{
const auto& zones = layout->Zones();
for (const auto& [zoneId, zone] : zones)
{
RECT zoneRect = zone.GetZoneRect();
zoneRect.left += monitorRect.left;
zoneRect.right += monitorRect.left;
zoneRect.top += monitorRect.top;
zoneRect.bottom += monitorRect.top;
zoneRects.emplace_back(zoneRect);
zoneRectsInfo.emplace_back(zoneId, workArea);
}
}
}
}
}
// Ensure we can get the windowRect, if not, just quit
RECT windowRect;
if (!GetWindowRect(window, &windowRect))
{
return false;
}
auto chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (chosenIdx < zoneRects.size())
{
// Moving to another monitor succeeded
const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
if (workArea)
{
workArea->MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx });
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
return true;
}
// We reached the end of all monitors.
// Try again, cycling on all monitors.
// First, add zones from the origin monitor to zoneRects
// Sanity check: the current monitor is valid
if (currentMonitorRect.top <= currentMonitorRect.bottom)
{
auto workArea = m_workAreaHandler.GetWorkArea(current);
if (workArea)
{
const auto& layout = workArea->GetLayout();
if (layout)
{
const auto& zones = layout->Zones();
for (const auto& [zoneId, zone] : zones)
{
RECT zoneRect = zone.GetZoneRect();
zoneRect.left += currentMonitorRect.left;
zoneRect.right += currentMonitorRect.left;
zoneRect.top += currentMonitorRect.top;
zoneRect.bottom += currentMonitorRect.top;
zoneRects.emplace_back(zoneRect);
zoneRectsInfo.emplace_back(zoneId, workArea);
}
}
}
}
else
{
return false;
}
RECT combinedRect = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>();
windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, combinedRect, vkCode);
chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (chosenIdx < zoneRects.size())
{
// Moving to another monitor succeeded
const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
if (workArea)
{
workArea->MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx });
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
return true;
}
else
{
// Giving up
return false;
}
}
else
{
// Single monitor environment, or combined multi-monitor environment.
return ProcessDirectedSnapHotkey(window, vkCode, true, m_workAreaHandler.GetWorkArea(current));
}
}
bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
{
// We already checked in ShouldProcessSnapHotkey whether the foreground window is a candidate for zoning
auto window = GetForegroundWindow();
if (FancyZonesSettings::settings().moveWindowsBasedOnPosition)
{
return OnSnapHotkeyBasedOnPosition(window, vkCode);
}
return (vkCode == VK_LEFT || vkCode == VK_RIGHT) && OnSnapHotkeyBasedOnZoneNumber(window, vkCode);
}
bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea) noexcept
{
// Check whether Alt is used in the shortcut key combination
if (GetAsyncKeyState(VK_MENU) & 0x8000)
{
bool result = workArea && workArea->ExtendWindowByDirectionAndPosition(window, vkCode);
if (result)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
return result;
}
else
{
bool result = workArea && workArea->MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle);
if (result)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
return result;
}
}
void FancyZones::SyncVirtualDesktops() noexcept
{
auto guids = VirtualDesktop::instance().GetVirtualDesktopIdsFromRegistry();
@ -1186,13 +964,13 @@ void FancyZones::SettingsUpdate(SettingId id)
}
}
void FancyZones::UpdateActiveLayouts() noexcept
void FancyZones::RefreshLayouts() noexcept
{
for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas())
{
if (workArea)
{
workArea->UpdateActiveZoneSet();
workArea->InitLayout();
if (FancyZonesSettings::settings().zoneSetChange_moveWindows)
{
@ -1265,7 +1043,7 @@ void FancyZones::ApplyQuickLayout(int key) noexcept
{
AppliedLayouts::instance().ApplyLayout(workArea->UniqueId(), layout.value());
AppliedLayouts::instance().SaveData();
UpdateActiveLayouts();
RefreshLayouts();
FlashZones();
}
}
@ -1284,32 +1062,6 @@ void FancyZones::FlashZones() noexcept
}
}
std::vector<HMONITOR> FancyZones::GetMonitorsSorted() noexcept
{
auto monitorInfo = GetRawMonitorData();
FancyZonesUtils::OrderMonitors(monitorInfo);
std::vector<HMONITOR> output;
std::transform(std::begin(monitorInfo), std::end(monitorInfo), std::back_inserter(output), [](const auto& info) { return info.first; });
return output;
}
std::vector<std::pair<HMONITOR, RECT>> FancyZones::GetRawMonitorData() noexcept
{
std::vector<std::pair<HMONITOR, RECT>> monitorInfo;
const auto& activeWorkAreaMap = m_workAreaHandler.GetAllWorkAreas();
for (const auto& [monitor, workArea] : activeWorkAreaMap)
{
if (workArea && workArea->GetLayout() != nullptr)
{
MONITORINFOEX mi;
mi.cbSize = sizeof(mi);
GetMonitorInfo(monitor, &mi);
monitorInfo.push_back({ monitor, mi.rcMonitor });
}
}
return monitorInfo;
}
HMONITOR FancyZones::WorkAreaKeyFromWindow(HWND window) noexcept
{
if (FancyZonesSettings::settings().spanZonesAcrossMonitors)

View File

@ -103,13 +103,14 @@ namespace JsonUtils
}
data.workAreaId = deviceIdOpt.value();
data.zoneSetUuid = json.GetNamedString(NonLocalizable::AppZoneHistoryIds::LayoutIdID);
if (!FancyZonesUtils::IsValidGuid(data.zoneSetUuid))
std::wstring layoutIdStr = json.GetNamedString(NonLocalizable::AppZoneHistoryIds::LayoutIdID).c_str();
auto layoutIdOpt = FancyZonesUtils::GuidFromString(layoutIdStr);
if (!layoutIdOpt.has_value())
{
return std::nullopt;
}
data.layoutId = layoutIdOpt.value();
return data;
}
@ -187,7 +188,11 @@ namespace JsonUtils
desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIndexesID, jsonIndexSet);
desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::DeviceID, device);
desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIdID, json::value(data.zoneSetUuid));
auto layoutIdStr = FancyZonesUtils::GuidToString(data.layoutId);
if (layoutIdStr)
{
desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIdID, json::value(layoutIdStr.value()));
}
appHistoryArray.Append(desktopData);
}
@ -334,7 +339,7 @@ void AppZoneHistory::AdjustWorkAreaIds(const std::vector<FancyZonesDataTypes::Mo
}
}
bool AppZoneHistory::SetAppLastZones(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId, const ZoneIndexSet& zoneIndexSet)
bool AppZoneHistory::SetAppLastZones(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId, const ZoneIndexSet& zoneIndexSet)
{
if (IsAnotherWindowOfApplicationInstanceZoned(window, workAreaId))
{
@ -347,8 +352,12 @@ bool AppZoneHistory::SetAppLastZones(HWND window, const FancyZonesDataTypes::Wor
return false;
}
Logger::info(L"Add app zone history, device: {}, layout: {}", workAreaId.toString(), zoneSetId);
auto layoutIdStr = FancyZonesUtils::GuidToString(layoutId);
if (layoutIdStr)
{
Logger::info(L"Add app zone history, device: {}, layout: {}", workAreaId.toString(), layoutIdStr.value());
}
DWORD processId = 0;
GetWindowThreadProcessId(window, &processId);
@ -362,7 +371,7 @@ bool AppZoneHistory::SetAppLastZones(HWND window, const FancyZonesDataTypes::Wor
{
// application already has history on this work area, update it with new window position
data.processIdToHandleMap[processId] = window;
data.zoneSetUuid = zoneSetId;
data.layoutId = layoutId;
data.zoneIndexSet = zoneIndexSet;
SaveData();
return true;
@ -373,7 +382,7 @@ bool AppZoneHistory::SetAppLastZones(HWND window, const FancyZonesDataTypes::Wor
std::unordered_map<DWORD, HWND> processIdToHandleMap{};
processIdToHandleMap[processId] = window;
FancyZonesDataTypes::AppZoneHistoryData data{ .processIdToHandleMap = processIdToHandleMap,
.zoneSetUuid = zoneSetId,
.layoutId = layoutId,
.workAreaId = workAreaId,
.zoneIndexSet = zoneIndexSet };
@ -392,56 +401,66 @@ bool AppZoneHistory::SetAppLastZones(HWND window, const FancyZonesDataTypes::Wor
return true;
}
bool AppZoneHistory::RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId)
bool AppZoneHistory::RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId)
{
Logger::info(L"Remove app zone history, device: {}, layout: {}", workAreaId.toString(), zoneSetId);
auto processPath = get_process_path_waiting_uwp(window);
if (!processPath.empty())
if (processPath.empty())
{
auto history = m_history.find(processPath);
if (history != std::end(m_history))
{
auto& perDesktopData = history->second;
for (auto data = std::begin(perDesktopData); data != std::end(perDesktopData);)
{
if (data->workAreaId == workAreaId && data->zoneSetUuid == zoneSetId)
{
if (!IsAnotherWindowOfApplicationInstanceZoned(window, workAreaId))
{
DWORD processId = 0;
GetWindowThreadProcessId(window, &processId);
data->processIdToHandleMap.erase(processId);
}
// if there is another instance of same application placed in the same zone don't erase history
auto windowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window);
for (auto placedWindow : data->processIdToHandleMap)
{
auto placedWindowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(placedWindow.second);
if (IsWindow(placedWindow.second) && (windowZoneStamps == placedWindowZoneStamps))
{
return false;
}
}
data = perDesktopData.erase(data);
if (perDesktopData.empty())
{
m_history.erase(processPath);
}
SaveData();
return true;
}
else
{
++data;
}
}
}
return false;
}
auto history = m_history.find(processPath);
if (history == std::end(m_history))
{
return false;
}
auto layoutIdStrOpt = FancyZonesUtils::GuidToString(layoutId);
if (!layoutIdStrOpt)
{
Logger::error("Invalid layout id");
return false;
}
Logger::info(L"Remove app zone history, device: {}, layout: {}", workAreaId.toString(), layoutIdStrOpt.value());
auto& perDesktopData = history->second;
for (auto data = std::begin(perDesktopData); data != std::end(perDesktopData);)
{
if (data->workAreaId == workAreaId && data->layoutId == layoutId)
{
if (!IsAnotherWindowOfApplicationInstanceZoned(window, workAreaId))
{
DWORD processId = 0;
GetWindowThreadProcessId(window, &processId);
data->processIdToHandleMap.erase(processId);
}
// if there is another instance of same application placed in the same zone don't erase history
auto windowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window);
for (auto placedWindow : data->processIdToHandleMap)
{
auto placedWindowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(placedWindow.second);
if (IsWindow(placedWindow.second) && (windowZoneStamps == placedWindowZoneStamps))
{
return false;
}
}
data = perDesktopData.erase(data);
if (perDesktopData.empty())
{
m_history.erase(processPath);
}
SaveData();
return true;
}
else
{
++data;
}
}
return false;
}
@ -532,7 +551,7 @@ bool AppZoneHistory::IsAnotherWindowOfApplicationInstanceZoned(HWND window, cons
return false;
}
ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const
ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const
{
auto processPath = get_process_path_waiting_uwp(window);
if (processPath.empty())
@ -559,7 +578,7 @@ ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZone
const auto& perDesktopData = history->second;
for (const auto& data : perDesktopData)
{
if (data.zoneSetUuid == zoneSetId && data.workAreaId == workAreaId)
if (data.layoutId == layoutId && data.workAreaId == workAreaId)
{
if (data.workAreaId.virtualDesktopId == workAreaId.virtualDesktopId || data.workAreaId.virtualDesktopId == GUID_NULL)
{

View File

@ -45,8 +45,8 @@ public:
void SaveData();
void AdjustWorkAreaIds(const std::vector<FancyZonesDataTypes::MonitorId>& ids);
bool SetAppLastZones(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId, const ZoneIndexSet& zoneIndexSet);
bool RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId);
bool SetAppLastZones(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId, const ZoneIndexSet& zoneIndexSet);
bool RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId);
void RemoveApp(const std::wstring& appPath);
@ -54,7 +54,7 @@ public:
std::optional<FancyZonesDataTypes::AppZoneHistoryData> GetZoneHistory(const std::wstring& appPath, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept;
bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept;
ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const;
ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const;
void SyncVirtualDesktops();
void RemoveDeletedVirtualDesktops(const std::vector<GUID>& activeDesktops);

View File

@ -144,9 +144,9 @@ namespace FancyZonesDataTypes
{
std::unordered_map<DWORD, HWND> processIdToHandleMap; // Maps process id(DWORD) of application to zoned window handle(HWND)
std::wstring zoneSetUuid;
WorkAreaId workAreaId;
ZoneIndexSet zoneIndexSet;
GUID layoutId = {};
WorkAreaId workAreaId = {};
ZoneIndexSet zoneIndexSet = {};
};
struct DeviceInfoData

View File

@ -71,7 +71,8 @@
<ClInclude Include="trace.h" />
<ClInclude Include="util.h" />
<ClInclude Include="VirtualDesktop.h" />
<ClInclude Include="WindowDrag.h" />
<ClInclude Include="WindowKeyboardSnap.h" />
<ClInclude Include="WindowMouseSnap.h" />
<ClInclude Include="FancyZonesWindowProperties.h" />
<ClInclude Include="WindowUtils.h" />
<ClInclude Include="Zone.h" />
@ -123,7 +124,8 @@
<ClCompile Include="trace.cpp" />
<ClCompile Include="util.cpp" />
<ClCompile Include="VirtualDesktop.cpp" />
<ClCompile Include="WindowDrag.cpp" />
<ClCompile Include="WindowKeyboardSnap.cpp" />
<ClCompile Include="WindowMouseSnap.cpp" />
<ClCompile Include="WindowUtils.cpp" />
<ClCompile Include="Zone.cpp" />
<ClCompile Include="WorkArea.cpp" />

View File

@ -162,7 +162,10 @@
<ClInclude Include="DraggingState.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WindowDrag.h">
<ClInclude Include="WindowMouseSnap.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WindowKeyboardSnap.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
@ -263,7 +266,10 @@
<ClCompile Include="DraggingState.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="WindowDrag.cpp">
<ClCompile Include="WindowMouseSnap.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="WindowKeyboardSnap.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>

View File

@ -15,7 +15,7 @@ namespace ZonedWindowProperties
const wchar_t PropertySortKeyWithinZone[] = L"FancyZones_TabSortKeyWithinZone";
}
void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet)
bool FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet)
{
RemoveZoneIndexProperty(window);
ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(zoneSet);
@ -33,6 +33,7 @@ void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneI
if (!SetProp(window, ZonedWindowProperties::PropertyMultipleZone64ID, rawData))
{
Logger::error(L"Failed to stamp window {}", get_last_error_or_default(GetLastError()));
return false;
}
}
@ -49,8 +50,11 @@ void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneI
if (!SetProp(window, ZonedWindowProperties::PropertyMultipleZone128ID, rawData))
{
Logger::error(L"Failed to stamp window {}", get_last_error_or_default(GetLastError()));
return false;
}
}
return true;
}
void FancyZonesWindowProperties::RemoveZoneIndexProperty(HWND window)

View File

@ -18,7 +18,7 @@ namespace ZonedWindowProperties
namespace FancyZonesWindowProperties
{
void StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet);
bool StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet);
void RemoveZoneIndexProperty(HWND window);
ZoneIndexSet RetrieveZoneIndexProperty(HWND window);

View File

@ -277,13 +277,16 @@ namespace
.monitorId = { .deviceId = MonitorUtils::Display::ConvertObsoleteDeviceId(deviceId->deviceName) },
.virtualDesktopId = deviceId->virtualDesktopId
};
data.zoneSetUuid = json.GetNamedString(NonLocalizable::ZoneSetUuidStr);
if (!FancyZonesUtils::IsValidGuid(data.zoneSetUuid))
std::wstring layoutIdStr = json.GetNamedString(NonLocalizable::ZoneSetUuidStr).c_str();
auto layoutIdOpt = FancyZonesUtils::GuidFromString(layoutIdStr);
if (!layoutIdOpt.has_value())
{
return std::nullopt;
}
data.layoutId = layoutIdOpt.value();
return data;
}

View File

@ -6,37 +6,10 @@
#include <FancyZonesLib/VirtualDesktop.h>
#include <FancyZonesLib/WindowUtils.h>
LayoutAssignedWindows::LayoutAssignedWindows()
{
m_extendData = std::make_unique<ExtendWindowModeData>();
}
void LayoutAssignedWindows::Assign(HWND window, const ZoneIndexSet& zones)
{
Dismiss(window);
// clear info about extension
std::erase_if(m_extendData->windowInitialIndexSet, [window](const auto& item) { return item.first == window; });
std::erase_if(m_extendData->windowFinalIndex, [window](const auto& item) { return item.first == window; });
for (const auto& index : zones)
{
m_windowIndexSet[window].push_back(index);
}
if (FancyZonesSettings::settings().disableRoundCorners)
{
FancyZonesWindowUtils::DisableRoundCorners(window);
}
auto tabSortKeyWithinZone = FancyZonesWindowProperties::GetTabSortKeyWithinZone(window);
InsertWindowIntoZone(window, tabSortKeyWithinZone, zones);
}
void LayoutAssignedWindows::Extend(HWND window, const ZoneIndexSet& zones)
{
Dismiss(window);
for (const auto& index : zones)
{
m_windowIndexSet[window].push_back(index);
@ -133,11 +106,6 @@ void LayoutAssignedWindows::CycleWindows(HWND window, bool reverse)
}
}
const std::unique_ptr<LayoutAssignedWindows::ExtendWindowModeData>& LayoutAssignedWindows::ExtendWindowData()
{
return m_extendData;
}
void LayoutAssignedWindows::InsertWindowIntoZone(HWND window, std::optional<size_t> tabSortKeyWithinZone, const ZoneIndexSet& indexSet)
{
if (tabSortKeyWithinZone.has_value())

View File

@ -4,19 +4,11 @@
class LayoutAssignedWindows
{
public:
struct ExtendWindowModeData
{
std::map<HWND, ZoneIndexSet> windowInitialIndexSet;
std::map<HWND, ZoneIndex> windowFinalIndex;
};
public :
LayoutAssignedWindows();
LayoutAssignedWindows() = default;
~LayoutAssignedWindows() = default;
void Assign(HWND window, const ZoneIndexSet& zones);
void Extend(HWND window, const ZoneIndexSet& zones);
void Dismiss(HWND window);
std::map<HWND, ZoneIndexSet> SnappedWindows() const noexcept;
@ -25,12 +17,9 @@ public :
void CycleWindows(HWND window, bool reverse);
const std::unique_ptr<ExtendWindowModeData>& ExtendWindowData();
private:
std::map<HWND, ZoneIndexSet> m_windowIndexSet{};
std::map<ZoneIndexSet, std::vector<HWND>> m_windowsByIndexSets{};
std::unique_ptr<ExtendWindowModeData> m_extendData{}; // Needed for ExtendWindowByDirectionAndPosition
void InsertWindowIntoZone(HWND window, std::optional<size_t> tabSortKeyWithinZone, const ZoneIndexSet& indexSet);
HWND GetNextZoneWindow(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept;

View File

@ -80,6 +80,13 @@ public:
#endif
}
#if defined(UNIT_TESTS)
inline void SetSettings(const Settings& settings)
{
m_settings = settings;
}
#endif
void AddObserver(SettingsObserver& observer);
void RemoveObserver(SettingsObserver& observer);

View File

@ -0,0 +1,489 @@
#include "pch.h"
#include "WindowKeyboardSnap.h"
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/trace.h>
#include <FancyZonesLib/WindowUtils.h>
#include <FancyZonesLib/WorkArea.h>
#include <FancyZonesLib/util.h>
#include <common/logger/logger.h>
#include <common/utils/winapi_error.h>
bool WindowKeyboardSnap::Snap(HWND window, HMONITOR monitor, DWORD vkCode, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas, const std::vector<HMONITOR>& monitors)
{
return (vkCode == VK_LEFT || vkCode == VK_RIGHT) && SnapHotkeyBasedOnZoneNumber(window, vkCode, monitor, activeWorkAreas, monitors);
}
bool WindowKeyboardSnap::Snap(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas, const std::vector<std::pair<HMONITOR, RECT>>& monitors)
{
if (!activeWorkAreas.contains(monitor))
{
return false;
}
// clean previous extension data
m_extendData.Reset();
const auto& currentWorkArea = activeWorkAreas.at(monitor);
if (monitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
{
// Multi monitor environment.
// First, try to stay on the same monitor
bool success = MoveByDirectionAndPosition(window, windowRect, vkCode, false, currentWorkArea.get());
if (success)
{
return true;
}
// Try to snap on another monitor
success = SnapBasedOnPositionOnAnotherMonitor(window, windowRect, vkCode, monitor, activeWorkAreas, monitors);
if (success)
{
// Unsnap from previous work area
currentWorkArea->Unsnap(window);
}
return success;
}
else
{
// Single monitor environment, or combined multi-monitor environment.
return MoveByDirectionAndPosition(window, windowRect, vkCode, true, currentWorkArea.get());
}
}
bool WindowKeyboardSnap::Extend(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas)
{
if (!activeWorkAreas.contains(monitor))
{
return false;
}
// continue extension process
const auto& workArea = activeWorkAreas.at(monitor);
return Extend(window, windowRect, vkCode, workArea.get());
}
bool WindowKeyboardSnap::SnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode, HMONITOR current, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas, const std::vector<HMONITOR>& monitors)
{
// clean previous extension data
m_extendData.Reset();
if (current && monitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
{
// Multi monitor environment.
auto currMonitor = std::find(std::begin(monitors), std::end(monitors), current);
do
{
if (activeWorkAreas.contains(*currMonitor))
{
const auto& workArea = activeWorkAreas.at(*currMonitor);
if (MoveByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea.get()))
{
// unassign from previous work area
for (auto& [_, prevWorkArea] : activeWorkAreas)
{
if (prevWorkArea && workArea != prevWorkArea)
{
prevWorkArea->Unsnap(window);
}
}
return true;
}
// We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction).
if (vkCode == VK_RIGHT)
{
currMonitor = std::next(currMonitor);
if (currMonitor == std::end(monitors))
{
currMonitor = std::begin(monitors);
}
}
else if (vkCode == VK_LEFT)
{
if (currMonitor == std::begin(monitors))
{
currMonitor = std::end(monitors);
}
currMonitor = std::prev(currMonitor);
}
}
} while (*currMonitor != current);
}
else
{
if (activeWorkAreas.contains(current))
{
const auto& workArea = activeWorkAreas.at(current);
bool moved = MoveByDirectionAndIndex(window, vkCode, FancyZonesSettings::settings().moveWindowAcrossMonitors /* cycle through zones */, workArea.get());
if (FancyZonesSettings::settings().restoreSize && !moved)
{
FancyZonesWindowUtils::RestoreWindowOrigin(window);
FancyZonesWindowUtils::RestoreWindowSize(window);
}
return moved;
}
}
return false;
}
bool WindowKeyboardSnap::SnapBasedOnPositionOnAnotherMonitor(HWND window, RECT windowRect, DWORD vkCode, HMONITOR current, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas, const std::vector<std::pair<HMONITOR, RECT>>& monitors)
{
// Extract zones from all other monitors and target one of them
std::vector<RECT> zoneRects;
std::vector<std::pair<ZoneIndex, WorkArea*>> zoneRectsInfo;
RECT currentMonitorRect{ .top = 0, .bottom = -1 };
for (const auto& [monitor, monitorRect] : monitors)
{
if (monitor == current)
{
currentMonitorRect = monitorRect;
}
else
{
if (activeWorkAreas.contains(monitor))
{
const auto& workArea = activeWorkAreas.at(monitor);
const auto& layout = workArea->GetLayout();
if (layout)
{
const auto& zones = layout->Zones();
for (const auto& [zoneId, zone] : zones)
{
RECT zoneRect = zone.GetZoneRect();
zoneRect.left += monitorRect.left;
zoneRect.right += monitorRect.left;
zoneRect.top += monitorRect.top;
zoneRect.bottom += monitorRect.top;
zoneRects.emplace_back(zoneRect);
zoneRectsInfo.emplace_back(zoneId, workArea.get());
}
}
}
}
}
auto chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (chosenIdx < zoneRects.size())
{
// Moving to another monitor succeeded
const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
bool snapped = false;
if (workArea)
{
snapped = workArea->Snap(window, { trueZoneIdx });
}
if (snapped)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
}
return snapped;
}
// We reached the end of all monitors.
// Try again, cycling on all monitors.
// First, add zones from the origin monitor to zoneRects
// Sanity check: the current monitor is valid
if (currentMonitorRect.top <= currentMonitorRect.bottom)
{
const auto& currentWorkArea = activeWorkAreas.at(current);
if (currentWorkArea)
{
const auto& layout = currentWorkArea->GetLayout();
if (layout)
{
const auto& zones = layout->Zones();
for (const auto& [zoneId, zone] : zones)
{
RECT zoneRect = zone.GetZoneRect();
zoneRect.left += currentMonitorRect.left;
zoneRect.right += currentMonitorRect.left;
zoneRect.top += currentMonitorRect.top;
zoneRect.bottom += currentMonitorRect.top;
zoneRects.emplace_back(zoneRect);
zoneRectsInfo.emplace_back(zoneId, currentWorkArea.get());
}
}
}
}
else
{
return false;
}
RECT combinedRect = FancyZonesUtils::GetMonitorsCombinedRect<&MONITORINFOEX::rcWork>(monitors);
windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, combinedRect, vkCode);
chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (chosenIdx < zoneRects.size())
{
// Moving to another monitor succeeded
const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
bool snapped = false;
if (workArea)
{
snapped = workArea->Snap(window, { trueZoneIdx });
}
if (snapped)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
}
return snapped;
}
else
{
// Giving up
return false;
}
}
bool WindowKeyboardSnap::MoveByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea)
{
if (!workArea)
{
return false;
}
const auto& layout = workArea->GetLayout();
const auto& zones = layout->Zones();
const auto& layoutWindows = workArea->GetLayoutWindows();
if (!layout || zones.empty())
{
return false;
}
auto zoneIndexes = layoutWindows.GetZoneIndexSetFromWindow(window);
const auto numZones = zones.size();
bool snapped = false;
// The window was not assigned to any zone here
if (zoneIndexes.size() == 0)
{
const ZoneIndex zone = vkCode == VK_LEFT ? numZones - 1 : 0;
snapped = workArea->Snap(window, { zone });
}
else
{
const ZoneIndex oldId = zoneIndexes[0];
// We reached the edge
if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == static_cast<int64_t>(numZones) - 1))
{
if (!cycle)
{
return false;
}
const ZoneIndex zone = vkCode == VK_LEFT ? numZones - 1 : 0;
snapped = workArea->Snap(window, { zone });
}
else
{
// We didn't reach the edge
if (vkCode == VK_LEFT)
{
snapped = workArea->Snap(window, { oldId - 1 });
}
else
{
snapped = workArea->Snap(window, { oldId + 1 });
}
}
}
if (snapped)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
}
return snapped;
}
bool WindowKeyboardSnap::MoveByDirectionAndPosition(HWND window, RECT windowRect, DWORD vkCode, bool cycle, WorkArea* const workArea)
{
if (!workArea)
{
return false;
}
const auto& layout = workArea->GetLayout();
const auto& zones = layout->Zones();
const auto& layoutWindows = workArea->GetLayoutWindows();
if (!layout || zones.empty())
{
return false;
}
std::vector<bool> usedZoneIndices(zones.size(), false);
auto windowZones = layoutWindows.GetZoneIndexSetFromWindow(window);
for (const ZoneIndex id : windowZones)
{
usedZoneIndices[id] = true;
}
std::vector<RECT> zoneRects;
ZoneIndexSet freeZoneIndices;
for (const auto& [zoneId, zone] : zones)
{
if (!usedZoneIndices[zoneId])
{
zoneRects.emplace_back(zones.at(zoneId).GetZoneRect());
freeZoneIndices.emplace_back(zoneId);
}
}
// Move to coordinates relative to windowZone
const auto& workAreaRect = workArea->GetWorkAreaRect();
windowRect.top -= workAreaRect.top();
windowRect.bottom -= workAreaRect.top();
windowRect.left -= workAreaRect.left();
windowRect.right -= workAreaRect.left();
ZoneIndex result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (static_cast<size_t>(result) < zoneRects.size())
{
bool success = workArea->Snap(window, { freeZoneIndices[result] });
if (success)
{
Trace::FancyZones::KeyboardSnapWindowToZone(layout.get(), layoutWindows);
}
return success;
}
else if (cycle)
{
// Try again from the position off the screen in the opposite direction to vkCode
// Consider all zones as available
zoneRects.resize(zones.size());
std::transform(zones.begin(), zones.end(), zoneRects.begin(), [](auto zone) { return zone.second.GetZoneRect(); });
windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, RECT(workAreaRect.left(), workAreaRect.top(), workAreaRect.right(), workAreaRect.bottom()), vkCode);
result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (static_cast<size_t>(result) < zoneRects.size())
{
bool success = workArea->Snap(window, { result });
if (success)
{
Trace::FancyZones::KeyboardSnapWindowToZone(layout.get(), layoutWindows);
}
return success;
}
}
return false;
}
bool WindowKeyboardSnap::Extend(HWND window, RECT windowRect, DWORD vkCode, WorkArea* const workArea)
{
if (!workArea)
{
return false;
}
const auto& layout = workArea->GetLayout();
const auto& layoutWindows = workArea->GetLayoutWindows();
if (!layout || layout->Zones().empty())
{
return false;
}
const auto& zones = layout->Zones();
auto appliedZones = layoutWindows.GetZoneIndexSetFromWindow(window);
std::vector<bool> usedZoneIndices(zones.size(), false);
std::vector<RECT> zoneRects;
ZoneIndexSet freeZoneIndices;
// If selectManyZones = true for the second time, use the last zone into which we moved
// instead of the window rect and enable moving to all zones except the old one
if (m_extendData.IsExtended(window))
{
usedZoneIndices[m_extendData.windowFinalIndex] = true;
windowRect = zones.at(m_extendData.windowFinalIndex).GetZoneRect();
}
else
{
for (const ZoneIndex idx : appliedZones)
{
usedZoneIndices[idx] = true;
}
// Move to coordinates relative to windowZone
const auto& workAreaRect = workArea->GetWorkAreaRect();
windowRect.top -= workAreaRect.top();
windowRect.bottom -= workAreaRect.top();
windowRect.left -= workAreaRect.left();
windowRect.right -= workAreaRect.left();
m_extendData.Set(window);
}
for (size_t i = 0; i < zones.size(); i++)
{
if (!usedZoneIndices[i])
{
zoneRects.emplace_back(zones.at(i).GetZoneRect());
freeZoneIndices.emplace_back(i);
}
}
const auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (result >= zoneRects.size())
{
return false;
}
ZoneIndex targetZone = freeZoneIndices[result];
ZoneIndexSet resultIndexSet;
// First time with selectManyZones = true for this window?
if (m_extendData.windowFinalIndex == -1)
{
// Already zoned?
if (appliedZones.size())
{
m_extendData.windowInitialIndexSet = appliedZones;
m_extendData.windowFinalIndex = targetZone;
resultIndexSet = layout->GetCombinedZoneRange(appliedZones, { targetZone });
}
else
{
m_extendData.windowInitialIndexSet = { targetZone };
m_extendData.windowFinalIndex = targetZone;
resultIndexSet = { targetZone };
}
}
else
{
auto deletethis = m_extendData.windowInitialIndexSet;
m_extendData.windowFinalIndex = targetZone;
resultIndexSet = layout->GetCombinedZoneRange(m_extendData.windowInitialIndexSet, { targetZone });
}
bool success = workArea->Snap(window, resultIndexSet);
if (success)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
}
return success;
}

View File

@ -0,0 +1,56 @@
#pragma once
#include <FancyZonesLib/Zone.h>
class WorkArea;
class WindowKeyboardSnap
{
struct ExtendWindowModeData
{
HWND window{ nullptr };
ZoneIndexSet windowInitialIndexSet{};
ZoneIndex windowFinalIndex{ -1 };
bool IsExtended(HWND wnd) const
{
return window == wnd && windowFinalIndex != -1;
}
void Set(HWND w)
{
window = w;
windowFinalIndex = -1;
windowInitialIndexSet.clear();
}
void Reset()
{
window = nullptr;
windowFinalIndex = -1;
windowInitialIndexSet.clear();
}
};
public:
WindowKeyboardSnap() = default;
~WindowKeyboardSnap() = default;
bool Snap(HWND window, HMONITOR activeMonitor, DWORD vkCode,
const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas,
const std::vector<HMONITOR>& monitors);
bool Snap(HWND window, RECT windowRect, HMONITOR activeMonitor, DWORD vkCode,
const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas,
const std::vector<std::pair<HMONITOR, RECT>>& monitors);
bool Extend(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas);
private:
bool SnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode, HMONITOR monitor, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas, const std::vector<HMONITOR>& monitors);
bool SnapBasedOnPositionOnAnotherMonitor(HWND window, RECT windowRect, DWORD vkCode, HMONITOR monitor, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas, const std::vector<std::pair<HMONITOR, RECT>>& monitors);
bool MoveByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea);
bool MoveByDirectionAndPosition(HWND window, RECT windowRect, DWORD vkCode, bool cycle, WorkArea* const workArea);
bool Extend(HWND window, RECT windowRect, DWORD vkCode, WorkArea* const workArea);
ExtendWindowModeData m_extendData{}; // Needed for ExtendWindowByDirectionAndPosition
};

View File

@ -0,0 +1,244 @@
#include "pch.h"
#include "WindowMouseSnap.h"
#include <FancyZonesLib/FancyZonesData/AppZoneHistory.h>
#include <FancyZonesLib/FancyZonesWindowProcessing.h>
#include <FancyZonesLib/FancyZonesWindowProperties.h>
#include <FancyZonesLib/NotificationUtil.h>
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/WindowUtils.h>
#include <FancyZonesLib/WorkArea.h>
#include <FancyZonesLib/trace.h>
#include <common/utils/elevation.h>
WindowMouseSnap::WindowMouseSnap(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas) :
m_window(window),
m_activeWorkAreas(activeWorkAreas),
m_currentWorkArea(nullptr),
m_snappingMode(false)
{
m_windowProperties.hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(m_window);
m_windowProperties.isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(m_window) &&
(!FancyZonesWindowUtils::IsPopupWindow(m_window) || FancyZonesSettings::settings().allowSnapPopupWindows);
}
WindowMouseSnap::~WindowMouseSnap()
{
ResetWindowTransparency();
}
std::unique_ptr<WindowMouseSnap> WindowMouseSnap::Create(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas)
{
if (!FancyZonesWindowProcessing::IsProcessable(window) ||
!FancyZonesWindowUtils::IsCandidateForZoning(window) ||
FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent())
{
return nullptr;
}
if (!is_process_elevated() && FancyZonesWindowUtils::IsProcessOfWindowElevated(window))
{
// Notifies user if unable to drag elevated window
FancyZonesNotifications::WarnIfElevationIsRequired();
return nullptr;
}
return std::unique_ptr<WindowMouseSnap>(new WindowMouseSnap(window, activeWorkAreas));
}
bool WindowMouseSnap::MoveSizeStart(HMONITOR monitor, bool isSnapping)
{
auto iter = m_activeWorkAreas.find(monitor);
if (iter == end(m_activeWorkAreas))
{
return false;
}
m_currentWorkArea = iter->second.get();
SwitchSnappingMode(isSnapping);
if (m_currentWorkArea)
{
m_currentWorkArea->Unsnap(m_window);
}
return true;
}
void WindowMouseSnap::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, bool isSnapping, bool isSelectManyZonesState)
{
auto iter = m_activeWorkAreas.find(monitor);
if (isSnapping && iter != m_activeWorkAreas.end())
{
// The drag has moved to a different monitor.
// Change work area
if (iter->second.get() != m_currentWorkArea)
{
m_highlightedZones.Reset();
if (m_currentWorkArea)
{
if (!FancyZonesSettings::settings().showZonesOnAllMonitors)
{
m_currentWorkArea->HideZones();
}
else
{
m_currentWorkArea->ShowZones({}, m_window);
}
}
m_currentWorkArea = iter->second.get();
}
if (m_currentWorkArea)
{
POINT ptClient = ptScreen;
MapWindowPoints(nullptr, m_currentWorkArea->GetWorkAreaWindow(), &ptClient, 1);
const bool redraw = m_highlightedZones.Update(m_currentWorkArea->GetLayout().get(), ptClient, isSelectManyZonesState);
if (redraw)
{
m_currentWorkArea->ShowZones(m_highlightedZones.Zones(), m_window);
}
}
}
SwitchSnappingMode(isSnapping);
}
void WindowMouseSnap::MoveSizeEnd()
{
if (m_snappingMode)
{
const bool hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(m_window);
const bool isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(m_window);
if ((isStandardWindow == false && hasNoVisibleOwner == true &&
m_windowProperties.isStandardWindow == true && m_windowProperties.hasNoVisibleOwner == true) ||
FancyZonesWindowUtils::IsWindowMaximized(m_window))
{
// Abort the zoning, this is a Chromium based tab that is merged back with an existing window
// or if the window is maximized by Windows when the cursor hits the screen top border
}
else if (m_currentWorkArea)
{
m_currentWorkArea->Snap(m_window, m_highlightedZones.Zones());
}
}
else
{
FancyZonesWindowUtils::ResetRoundCornersPreference(m_window);
if (FancyZonesSettings::settings().restoreSize)
{
if (FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent())
{
::RemoveProp(m_window, ZonedWindowProperties::PropertyRestoreSizeID);
}
else if (!FancyZonesWindowUtils::IsWindowMaximized(m_window))
{
FancyZonesWindowUtils::RestoreWindowSize(m_window);
}
}
}
SwitchSnappingMode(false);
}
void WindowMouseSnap::SwitchSnappingMode(bool isSnapping)
{
if (!m_snappingMode && isSnapping) // turn on
{
m_highlightedZones.Reset();
SetWindowTransparency();
if (FancyZonesSettings::settings().showZonesOnAllMonitors)
{
for (const auto& [_, workArea] : m_activeWorkAreas)
{
if (workArea)
{
workArea->ShowZones({}, m_window);
}
}
}
else if (m_currentWorkArea)
{
m_currentWorkArea->ShowZones({}, m_window);
}
if (m_currentWorkArea)
{
m_currentWorkArea->Unsnap(m_window);
Trace::WorkArea::MoveOrResizeStarted(m_currentWorkArea->GetLayout().get(), m_currentWorkArea->GetLayoutWindows());
}
}
else if (m_snappingMode && !isSnapping) // turn off
{
ResetWindowTransparency();
m_highlightedZones.Reset();
// Hide all layouts (regardless of settings)
for (auto& [_, workArea] : m_activeWorkAreas)
{
if (workArea)
{
workArea->HideZones();
}
}
if (m_currentWorkArea)
{
Trace::WorkArea::MoveOrResizeEnd(m_currentWorkArea->GetLayout().get(), m_currentWorkArea->GetLayoutWindows());
}
}
m_snappingMode = isSnapping;
}
void WindowMouseSnap::SetWindowTransparency()
{
if (FancyZonesSettings::settings().makeDraggedWindowTransparent)
{
m_windowProperties.exstyle = GetWindowLong(m_window, GWL_EXSTYLE);
SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle | WS_EX_LAYERED);
if (!GetLayeredWindowAttributes(m_window, &m_windowProperties.crKey, &m_windowProperties.alpha, &m_windowProperties.dwFlags))
{
Logger::error(L"Window transparency: GetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
return;
}
if (!SetLayeredWindowAttributes(m_window, 0, (255 * 50) / 100, LWA_ALPHA))
{
Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
return;
}
m_windowProperties.transparencySet = true;
}
}
void WindowMouseSnap::ResetWindowTransparency()
{
if (FancyZonesSettings::settings().makeDraggedWindowTransparent && m_windowProperties.transparencySet)
{
bool reset = true;
if (!SetLayeredWindowAttributes(m_window, m_windowProperties.crKey, m_windowProperties.alpha, m_windowProperties.dwFlags))
{
Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
reset = false;
}
if (SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle) == 0)
{
Logger::error(L"Window transparency: SetWindowLong failed, {}", get_last_error_or_default(GetLastError()));
reset = false;
}
m_windowProperties.transparencySet = !reset;
}
}

View File

@ -4,13 +4,13 @@
class WorkArea;
class WindowDrag
class WindowMouseSnap
{
WindowDrag(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas);
WindowMouseSnap(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas);
public:
static std::unique_ptr<WindowDrag> Create(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas);
~WindowDrag();
static std::unique_ptr<WindowMouseSnap> Create(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas);
~WindowMouseSnap();
bool MoveSizeStart(HMONITOR monitor, bool isSnapping);
void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, bool isSnapping, bool isSelectManyZonesState);

View File

@ -456,15 +456,19 @@ RECT FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(HWND window, RECT rect
::GetWindowRect(window, &windowRect);
// Take care of borders
RECT frameRect{};
if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect))))
// Skip when windowOfRect is not initialized (in unit tests)
if (windowOfRect)
{
LONG leftMargin = frameRect.left - windowRect.left;
LONG rightMargin = frameRect.right - windowRect.right;
LONG bottomMargin = frameRect.bottom - windowRect.bottom;
newWindowRect.left -= leftMargin;
newWindowRect.right -= rightMargin;
newWindowRect.bottom -= bottomMargin;
RECT frameRect{};
if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect))))
{
LONG leftMargin = frameRect.left - windowRect.left;
LONG rightMargin = frameRect.right - windowRect.right;
LONG bottomMargin = frameRect.bottom - windowRect.bottom;
newWindowRect.left -= leftMargin;
newWindowRect.right -= rightMargin;
newWindowRect.bottom -= bottomMargin;
}
}
// Take care of windows that cannot be resized
@ -475,7 +479,10 @@ RECT FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(HWND window, RECT rect
}
// Convert to screen coordinates
MapWindowRect(windowOfRect, nullptr, &newWindowRect);
if (windowOfRect)
{
MapWindowRect(windowOfRect, nullptr, &newWindowRect);
}
return newWindowRect;
}

View File

@ -1,26 +1,16 @@
#include "pch.h"
#include "WorkArea.h"
#include <common/logger/call_tracer.h>
#include <common/logger/logger.h>
#include <common/utils/winapi_error.h>
#include "FancyZonesData/AppliedLayouts.h"
#include "FancyZonesData/AppZoneHistory.h"
#include "FancyZonesDataTypes.h"
#include "SettingsObserver.h"
#include "ZonesOverlay.h"
#include "trace.h"
#include "on_thread_executor.h"
#include "Settings.h"
#include <FancyZonesLib/FancyZonesWindowProperties.h>
#include <FancyZonesLib/VirtualDesktop.h>
#include <FancyZonesLib/WindowUtils.h>
#include <ShellScalingApi.h>
#include <mutex>
#include <fileapi.h>
// disabling warning 4458 - declaration of 'identifier' hides class member
// to avoid warnings from GDI files - can't add winRT directory to external code
// in the Cpp.Build.props
@ -127,284 +117,41 @@ WorkArea::~WorkArea()
windowPool.FreeZonesOverlayWindow(m_window);
}
void WorkArea::MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index)
bool WorkArea::Snap(HWND window, const ZoneIndexSet& zones, bool updatePosition)
{
MoveWindowIntoZoneByIndexSet(window, { index });
}
void WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool updatePosition /* = true*/)
{
if (!m_layout || !m_layoutWindows || m_layout->Zones().empty() || indexSet.empty())
if (!m_layout || zones.empty())
{
return;
return false;
}
FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window);
m_layoutWindows.Assign(window, zones);
AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, m_layout->Id(), zones);
if (updatePosition)
{
const auto rect = m_layout->GetCombinedZonesRect(indexSet);
if (rect.bottom - rect.top > 0 && rect.right - rect.left > 0)
{
const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window);
FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect);
}
const auto rect = m_layout->GetCombinedZonesRect(zones);
const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window);
FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window);
FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect);
}
SnapWindow(window, indexSet);
return FancyZonesWindowProperties::StampZoneIndexProperty(window, zones);
}
bool WorkArea::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle)
bool WorkArea::Unsnap(HWND window)
{
if (!m_layout || !m_layoutWindows || m_layout->Zones().empty())
if (!m_layout)
{
return false;
}
auto zoneIndexes = m_layoutWindows->GetZoneIndexSetFromWindow(window);
const auto numZones = m_layout->Zones().size();
// The window was not assigned to any zone here
if (zoneIndexes.size() == 0)
{
MoveWindowIntoZoneByIndex(window, vkCode == VK_LEFT ? numZones - 1 : 0);
}
else
{
const ZoneIndex oldId = zoneIndexes[0];
// We reached the edge
if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == static_cast<int64_t>(numZones) - 1))
{
if (!cycle)
{
return false;
}
MoveWindowIntoZoneByIndex(window, vkCode == VK_LEFT ? numZones - 1 : 0);
}
else
{
// We didn't reach the edge
if (vkCode == VK_LEFT)
{
MoveWindowIntoZoneByIndex(window, oldId - 1);
}
else
{
MoveWindowIntoZoneByIndex(window, oldId + 1);
}
}
}
m_layoutWindows.Dismiss(window);
AppZoneHistory::instance().RemoveAppLastZone(window, m_uniqueId, m_layout->Id());
FancyZonesWindowProperties::RemoveZoneIndexProperty(window);
return true;
}
bool WorkArea::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle)
{
if (!m_layout || !m_layoutWindows || m_layout->Zones().empty())
{
return false;
}
const auto& zones = m_layout->Zones();
std::vector<bool> usedZoneIndices(zones.size(), false);
auto windowZones = m_layoutWindows->GetZoneIndexSetFromWindow(window);
for (const ZoneIndex id : windowZones)
{
usedZoneIndices[id] = true;
}
std::vector<RECT> zoneRects;
ZoneIndexSet freeZoneIndices;
for (const auto& [zoneId, zone] : zones)
{
if (!usedZoneIndices[zoneId])
{
zoneRects.emplace_back(zones.at(zoneId).GetZoneRect());
freeZoneIndices.emplace_back(zoneId);
}
}
RECT windowRect;
if (!GetWindowRect(window, &windowRect))
{
Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError()));
return false;
}
// Move to coordinates relative to windowZone
windowRect.top -= m_workAreaRect.top();
windowRect.bottom -= m_workAreaRect.top();
windowRect.left -= m_workAreaRect.left();
windowRect.right -= m_workAreaRect.left();
auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (result < zoneRects.size())
{
MoveWindowIntoZoneByIndex(window, freeZoneIndices[result]);
Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get());
return true;
}
else if (cycle)
{
// Try again from the position off the screen in the opposite direction to vkCode
// Consider all zones as available
zoneRects.resize(zones.size());
std::transform(zones.begin(), zones.end(), zoneRects.begin(), [](auto zone) { return zone.second.GetZoneRect(); });
windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, RECT(m_workAreaRect.left(), m_workAreaRect.top(), m_workAreaRect.right(), m_workAreaRect.bottom()), vkCode);
result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (result < zoneRects.size())
{
MoveWindowIntoZoneByIndex(window, result);
Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get());
return true;
}
}
return false;
}
bool WorkArea::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode)
{
if (!m_layout || !m_layoutWindows || m_layout->Zones().empty())
{
return false;
}
RECT windowRect;
if (!GetWindowRect(window, &windowRect))
{
Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError()));
return false;
}
const auto& zones = m_layout->Zones();
auto appliedZones = m_layoutWindows->GetZoneIndexSetFromWindow(window);
const auto& extendModeData = m_layoutWindows->ExtendWindowData();
std::vector<bool> usedZoneIndices(zones.size(), false);
std::vector<RECT> zoneRects;
ZoneIndexSet freeZoneIndices;
// If selectManyZones = true for the second time, use the last zone into which we moved
// instead of the window rect and enable moving to all zones except the old one
auto finalIndexIt = extendModeData->windowFinalIndex.find(window);
if (finalIndexIt != extendModeData->windowFinalIndex.end())
{
usedZoneIndices[finalIndexIt->second] = true;
windowRect = zones.at(finalIndexIt->second).GetZoneRect();
}
else
{
for (const ZoneIndex idx : appliedZones)
{
usedZoneIndices[idx] = true;
}
// Move to coordinates relative to windowZone
windowRect.top -= m_workAreaRect.top();
windowRect.bottom -= m_workAreaRect.top();
windowRect.left -= m_workAreaRect.left();
windowRect.right -= m_workAreaRect.left();
}
for (size_t i = 0; i < zones.size(); i++)
{
if (!usedZoneIndices[i])
{
zoneRects.emplace_back(zones.at(i).GetZoneRect());
freeZoneIndices.emplace_back(i);
}
}
const auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (result < zoneRects.size())
{
ZoneIndex targetZone = freeZoneIndices[result];
ZoneIndexSet resultIndexSet;
// First time with selectManyZones = true for this window?
if (finalIndexIt == extendModeData->windowFinalIndex.end())
{
// Already zoned?
if (appliedZones.size())
{
extendModeData->windowInitialIndexSet[window] = appliedZones;
extendModeData->windowFinalIndex[window] = targetZone;
resultIndexSet = m_layout->GetCombinedZoneRange(appliedZones, { targetZone });
}
else
{
extendModeData->windowInitialIndexSet[window] = { targetZone };
extendModeData->windowFinalIndex[window] = targetZone;
resultIndexSet = { targetZone };
}
}
else
{
auto deletethis = extendModeData->windowInitialIndexSet[window];
extendModeData->windowFinalIndex[window] = targetZone;
resultIndexSet = m_layout->GetCombinedZoneRange(extendModeData->windowInitialIndexSet[window], { targetZone });
}
const auto rect = m_layout->GetCombinedZonesRect(resultIndexSet);
const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window);
FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect);
SnapWindow(window, resultIndexSet, true);
return true;
}
return false;
}
void WorkArea::SnapWindow(HWND window, const ZoneIndexSet& zones, bool extend)
{
if (!m_layoutWindows || !m_layout)
{
return;
}
if (extend)
{
m_layoutWindows->Extend(window, zones);
}
else
{
m_layoutWindows->Assign(window, zones);
}
auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id());
if (guidStr.has_value())
{
AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, guidStr.value(), zones);
}
FancyZonesWindowProperties::StampZoneIndexProperty(window, zones);
}
void WorkArea::UnsnapWindow(HWND window)
{
if (!m_layoutWindows || !m_layout)
{
return;
}
m_layoutWindows->Dismiss(window);
auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id());
if (guidStr.has_value())
{
AppZoneHistory::instance().RemoveAppLastZone(window, m_uniqueId, guidStr.value());
}
FancyZonesWindowProperties::RemoveZoneIndexProperty(window);
}
const GUID WorkArea::GetLayoutId() const noexcept
{
if (m_layout)
@ -415,29 +162,7 @@ const GUID WorkArea::GetLayoutId() const noexcept
return GUID{};
}
ZoneIndexSet WorkArea::GetWindowZoneIndexes(HWND window) const
{
if (m_layout)
{
auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id());
if (guidStr.has_value())
{
return AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, guidStr.value());
}
else
{
Logger::error(L"Failed to convert to string layout GUID on the requested work area");
}
}
else
{
Logger::error(L"No layout initialized on the requested work area");
}
return {};
}
void WorkArea::ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindow/* = nullptr*/)
void WorkArea::ShowZones(const ZoneIndexSet& highlight, HWND draggedWindow/* = nullptr*/)
{
if (m_layout && m_zonesOverlay)
{
@ -447,7 +172,7 @@ void WorkArea::ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindo
}
}
void WorkArea::HideZonesOverlay()
void WorkArea::HideZones()
{
if (m_zonesOverlay)
{
@ -465,15 +190,10 @@ void WorkArea::FlashZones()
}
}
void WorkArea::UpdateActiveZoneSet()
void WorkArea::InitLayout()
{
const bool isLayoutAlreadyApplied = AppliedLayouts::instance().IsLayoutApplied(m_uniqueId);
if (!isLayoutAlreadyApplied)
{
AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId);
}
InitLayout({});
CalculateZoneSet();
if (m_window && m_layout)
{
m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), {}, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
@ -482,24 +202,16 @@ void WorkArea::UpdateActiveZoneSet()
void WorkArea::UpdateWindowPositions()
{
if (!m_layoutWindows)
{
return;
}
const auto& snappedWindows = m_layoutWindows->SnappedWindows();
const auto& snappedWindows = m_layoutWindows.SnappedWindows();
for (const auto& [window, zones] : snappedWindows)
{
MoveWindowIntoZoneByIndexSet(window, zones, true);
Snap(window, zones, true);
}
}
void WorkArea::CycleWindows(HWND window, bool reverse)
{
if (m_layoutWindows)
{
m_layoutWindows->CycleWindows(window, reverse);
}
m_layoutWindows.CycleWindows(window, reverse);
}
#pragma region private
@ -537,6 +249,46 @@ void WorkArea::InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId)
CalculateZoneSet();
}
void WorkArea::InitSnappedWindows()
{
static bool updatePositionOnceOnStartFlag = true;
Logger::info(L"Init work area {} windows, update positions = {}", m_uniqueId.toString(), updatePositionOnceOnStartFlag);
for (const auto& window : VirtualDesktop::instance().GetWindowsFromCurrentDesktop())
{
auto indexes = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window);
if (indexes.size() == 0)
{
continue;
}
if (!m_uniqueId.monitorId.monitor) // one work area across monitors
{
Snap(window, indexes, updatePositionOnceOnStartFlag);
}
else
{
const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
if (monitor && m_uniqueId.monitorId.monitor == monitor)
{
// prioritize snapping on the current monitor if the window was snapped to several work areas
Snap(window, indexes, updatePositionOnceOnStartFlag);
}
else
{
// if the window is not snapped on the current monitor, then check the others
auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, GetLayoutId());
if (savedIndexes == indexes)
{
Snap(window, indexes, updatePositionOnceOnStartFlag);
}
}
}
}
updatePositionOnceOnStartFlag = false;
}
void WorkArea::CalculateZoneSet()
{
const auto appliedLayout = AppliedLayouts::instance().GetDeviceLayout(m_uniqueId);
@ -548,11 +300,6 @@ void WorkArea::CalculateZoneSet()
m_layout = std::make_unique<Layout>(appliedLayout.value());
m_layout->Init(m_workAreaRect, m_uniqueId.monitorId.monitor);
if (!m_layoutWindows)
{
m_layoutWindows = std::make_unique<LayoutAssignedWindows>();
}
}
LRESULT WorkArea::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept

View File

@ -1,6 +1,5 @@
#pragma once
#include <FancyZonesLib/FancyZonesDataTypes.h>
#include <FancyZonesLib/Layout.h>
#include <FancyZonesLib/LayoutAssignedWindows.h>
@ -39,26 +38,20 @@ public:
FancyZonesDataTypes::WorkAreaId UniqueId() const noexcept { return { m_uniqueId }; }
const std::unique_ptr<Layout>& GetLayout() const noexcept { return m_layout; }
const std::unique_ptr<LayoutAssignedWindows>& GetLayoutWindows() const noexcept { return m_layoutWindows; }
const LayoutAssignedWindows& GetLayoutWindows() const noexcept { return m_layoutWindows; }
const HWND GetWorkAreaWindow() const noexcept { return m_window; }
const GUID GetLayoutId() const noexcept;
const FancyZonesUtils::Rect& GetWorkAreaRect() const noexcept { return m_workAreaRect; }
ZoneIndexSet GetWindowZoneIndexes(HWND window) const;
void MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index);
void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool updatePosition = true);
bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle);
bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle);
bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode);
void SnapWindow(HWND window, const ZoneIndexSet& zones, bool extend = false);
void UnsnapWindow(HWND window);
void UpdateActiveZoneSet();
void InitLayout();
void InitSnappedWindows();
void UpdateWindowPositions();
void ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindow = nullptr);
void HideZonesOverlay();
bool Snap(HWND window, const ZoneIndexSet& zones, bool updatePosition = true);
bool Unsnap(HWND window);
void ShowZones(const ZoneIndexSet& highlight, HWND draggedWindow = nullptr);
void HideZones();
void FlashZones();
void CycleWindows(HWND window, bool reverse);
@ -79,6 +72,6 @@ private:
const FancyZonesDataTypes::WorkAreaId m_uniqueId;
HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area.
std::unique_ptr<Layout> m_layout;
std::unique_ptr<LayoutAssignedWindows> m_layoutWindows;
LayoutAssignedWindows m_layoutWindows{};
std::unique_ptr<ZonesOverlay> m_zonesOverlay;
};

View File

@ -90,17 +90,17 @@ struct ZoneSetInfo
};
ZoneSetInfo GetZoneSetInfo(_In_opt_ Layout* layout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept
ZoneSetInfo GetZoneSetInfo(_In_opt_ Layout* layout, const LayoutAssignedWindows& layoutWindows) noexcept
{
ZoneSetInfo info;
if (layout && layoutWindows)
if (layout)
{
auto zones = layout->Zones();
info.NumberOfZones = zones.size();
info.NumberOfWindows = 0;
for (int i = 0; i < static_cast<int>(zones.size()); i++)
{
if (!layoutWindows->IsZoneEmpty(i))
if (!layoutWindows.IsZoneEmpty(i))
{
info.NumberOfWindows++;
}
@ -258,7 +258,7 @@ void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept
TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed));
}
void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept
void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept
{
auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows);
TraceLoggingWrite(
@ -271,7 +271,7 @@ void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssign
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey));
}
void Trace::FancyZones::KeyboardSnapWindowToZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept
void Trace::FancyZones::KeyboardSnapWindowToZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept
{
auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows);
TraceLoggingWrite(
@ -356,7 +356,7 @@ void Trace::WorkArea::KeyUp(WPARAM wParam) noexcept
TraceLoggingValue(wParam, KeyboardValueKey));
}
void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept
void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept
{
auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows);
TraceLoggingWrite(
@ -369,7 +369,7 @@ void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey));
}
void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept
void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept
{
auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows);
TraceLoggingWrite(
@ -382,7 +382,7 @@ void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ La
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey));
}
void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows, InputMode mode) noexcept
void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows, InputMode mode) noexcept
{
auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows);
TraceLoggingWrite(

View File

@ -19,8 +19,8 @@ public:
static void EditorLaunched(int value) noexcept;
static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept;
static void QuickLayoutSwitched(bool shortcutUsed) noexcept;
static void SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept;
static void KeyboardSnapWindowToZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept;
static void SnapNewWindowIntoZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept;
static void KeyboardSnapWindowToZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept;
};
static void SettingsTelemetry(const Settings& settings) noexcept;
@ -36,8 +36,8 @@ public:
};
static void KeyUp(WPARAM wparam) noexcept;
static void MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept;
static void MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept;
static void CycleActiveZoneSet(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows, InputMode mode) noexcept;
static void MoveOrResizeStarted(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept;
static void MoveOrResizeEnd(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept;
static void CycleActiveZoneSet(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows, InputMode mode) noexcept;
};
};

View File

@ -123,6 +123,15 @@ namespace FancyZonesUtils
monitorInfo = std::move(sortedMonitorInfo);
}
std::vector<HMONITOR> GetMonitorsOrdered()
{
auto monitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
FancyZonesUtils::OrderMonitors(monitors);
std::vector<HMONITOR> output;
std::transform(std::begin(monitors), std::end(monitors), std::back_inserter(output), [](const auto& info) { return info.first; });
return output;
}
bool IsValidGuid(const std::wstring& str)
{
GUID id;

View File

@ -143,13 +143,12 @@ namespace FancyZonesUtils
}
template<RECT MONITORINFO::*member>
RECT GetAllMonitorsCombinedRect()
RECT GetMonitorsCombinedRect(const std::vector<std::pair<HMONITOR, RECT>>& monitorRects)
{
auto allMonitors = GetAllMonitorRects<member>();
bool empty = true;
RECT result{ 0, 0, 0, 0 };
for (auto& [monitor, rect] : allMonitors)
for (auto& [monitor, rect] : monitorRects)
{
if (empty)
{
@ -168,6 +167,13 @@ namespace FancyZonesUtils
return result;
}
template<RECT MONITORINFO::*member>
RECT GetAllMonitorsCombinedRect()
{
auto allMonitors = GetAllMonitorRects<member>();
return GetMonitorsCombinedRect<member>(allMonitors);
}
constexpr RECT PrepareRectForCycling(RECT windowRect, RECT workAreaRect, DWORD vkCode) noexcept
{
LONG deltaX = 0, deltaY = 0;
@ -196,6 +202,7 @@ namespace FancyZonesUtils
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo);
std::vector<HMONITOR> GetMonitorsOrdered();
bool IsValidGuid(const std::wstring& str);
std::optional<GUID> GuidFromString(const std::wstring& str) noexcept;

View File

@ -207,22 +207,22 @@ namespace FancyZonesUnitTests
TEST_METHOD (AppLastZoneInvalidWindow)
{
const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
};
const auto window = Mocks::Window();
Assert::IsTrue(std::vector<ZoneIndex>{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId));
Assert::IsTrue(std::vector<ZoneIndex>{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId));
const int expectedZoneIndex = 1;
Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { expectedZoneIndex }));
Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { expectedZoneIndex }));
}
TEST_METHOD (AppLastZoneNullWindow)
{
const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
@ -230,12 +230,12 @@ namespace FancyZonesUnitTests
const auto window = nullptr;
const int expectedZoneIndex = 1;
Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { expectedZoneIndex }));
Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { expectedZoneIndex }));
}
TEST_METHOD (AppLastdeviceIdTest)
{
const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId1{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
@ -247,15 +247,15 @@ namespace FancyZonesUnitTests
const auto window = Mocks::WindowCreate(m_hInst);
const int expectedZoneIndex = 10;
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId1, zoneSetId, { expectedZoneIndex }));
Assert::IsTrue(std::vector<ZoneIndex>{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId1, zoneSetId));
Assert::IsTrue(std::vector<ZoneIndex>{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId2, zoneSetId));
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId1, layoutId, { expectedZoneIndex }));
Assert::IsTrue(std::vector<ZoneIndex>{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId1, layoutId));
Assert::IsTrue(std::vector<ZoneIndex>{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId2, layoutId));
}
TEST_METHOD (AppLastZoneSetIdTest)
{
const std::wstring zoneSetId1 = L"{B7A1F5A9-9DC2-4505-84AB-993253839093}";
const std::wstring zoneSetId2 = L"{B7A1F5A9-9DC2-4505-84AB-993253839094}";
const auto layoutId1 = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839093}").value();
const auto layoutId2 = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839094}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
@ -263,56 +263,56 @@ namespace FancyZonesUnitTests
const auto window = Mocks::WindowCreate(m_hInst);
const int expectedZoneIndex = 10;
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId1, { expectedZoneIndex }));
Assert::IsTrue(std::vector<ZoneIndex>{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId1));
Assert::IsTrue(std::vector<ZoneIndex>{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId2));
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId1, { expectedZoneIndex }));
Assert::IsTrue(std::vector<ZoneIndex>{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId1));
Assert::IsTrue(std::vector<ZoneIndex>{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId2));
}
TEST_METHOD (AppLastZoneRemoveWindow)
{
const std::wstring zoneSetId = L"{B7A1F5A9-9DC2-4505-84AB-993253839093}";
const auto layoutId = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839093}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
};
const auto window = Mocks::WindowCreate(m_hInst);
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { 1 }));
Assert::IsTrue(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetId));
Assert::IsTrue(std::vector<ZoneIndex>{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId));
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { 1 }));
Assert::IsTrue(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutId));
Assert::IsTrue(std::vector<ZoneIndex>{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId));
}
TEST_METHOD (AppLastZoneRemoveUnknownWindow)
{
const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
};
const auto window = Mocks::WindowCreate(m_hInst);
Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetId));
Assert::IsTrue(std::vector<ZoneIndex>{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId));
Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutId));
Assert::IsTrue(std::vector<ZoneIndex>{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId));
}
TEST_METHOD (AppLastZoneRemoveUnknownZoneSetId)
{
const std::wstring zoneSetIdToInsert = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
const std::wstring zoneSetIdToRemove = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F1}";
const auto layoutIdToInsert = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const auto layoutIdToRemove = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F1}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
};
const auto window = Mocks::WindowCreate(m_hInst);
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetIdToInsert, { 1 }));
Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetIdToRemove));
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetIdToInsert));
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutIdToInsert, { 1 }));
Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutIdToRemove));
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutIdToInsert));
}
TEST_METHOD (AppLastZoneRemoveUnknownWindowId)
{
const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaIdToInsert{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
@ -323,20 +323,20 @@ namespace FancyZonesUnitTests
};
const auto window = Mocks::WindowCreate(m_hInst);
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaIdToInsert, zoneSetId, { 1 }));
Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaIdToRemove, zoneSetId));
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaIdToInsert, zoneSetId));
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaIdToInsert, layoutId, { 1 }));
Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaIdToRemove, layoutId));
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaIdToInsert, layoutId));
}
TEST_METHOD (AppLastZoneRemoveNullWindow)
{
const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
};
Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, zoneSetId));
Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, layoutId));
}
};
}

View File

@ -140,10 +140,9 @@ namespace FancyZonesUnitTests
data.zoneCount = 4;
// prepare settings
PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey);
values.add_property(L"fancyzones_overlappingZonesAlgorithm", json::value(static_cast<int>(OverlappingZonesAlgorithm::Smallest)));
json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
FancyZonesSettings::instance().LoadSettings();
auto settings = FancyZonesSettings::settings();
settings.overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest;
FancyZonesSettings::instance().SetSettings(settings);
auto layout = std::make_unique<Layout>(data);
layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor());
@ -168,10 +167,9 @@ namespace FancyZonesUnitTests
data.zoneCount = 4;
// prepare settings
PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey);
values.add_property(L"fancyzones_overlappingZonesAlgorithm", json::value(static_cast<int>(OverlappingZonesAlgorithm::Smallest)));
json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
FancyZonesSettings::instance().LoadSettings();
auto settings = FancyZonesSettings::settings();
settings.overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest;
FancyZonesSettings::instance().SetSettings(settings);
auto layout = std::make_unique<Layout>(data);
layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor());

View File

@ -55,6 +55,7 @@
</ClCompile>
<ClCompile Include="Util.Spec.cpp" />
<ClCompile Include="Util.cpp" />
<ClCompile Include="WindowKeyboardSnap.Spec.cpp" />
<ClCompile Include="WorkArea.Spec.cpp" />
<ClCompile Include="WorkAreaIdTests.Spec.cpp" />
<ClCompile Include="Zone.Spec.cpp" />

View File

@ -63,6 +63,9 @@
<ClCompile Include="DefaultLayoutsTests.Spec.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="WindowKeyboardSnap.Spec.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,6 @@ namespace FancyZonesUnitTests
{
std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
std::filesystem::remove(DefaultLayouts::DefaultLayoutsFileName());
}
@ -58,7 +57,6 @@ namespace FancyZonesUnitTests
const auto& layout = workArea->GetLayout();
Assert::IsNotNull(layout.get());
Assert::IsNotNull(workArea->GetLayoutWindows().get());
Assert::AreEqual(static_cast<int>(defaultLayout.type), static_cast<int>(layout->Type()));
Assert::AreEqual(defaultLayout.zoneCount, static_cast<int>(layout->Zones().size()));
}
@ -73,7 +71,6 @@ namespace FancyZonesUnitTests
const auto& layout = workArea->GetLayout();
Assert::IsNotNull(layout.get());
Assert::IsNotNull(workArea->GetLayoutWindows().get());
Assert::AreEqual(static_cast<int>(defaultLayout.type), static_cast<int>(layout->Type()));
Assert::AreEqual(defaultLayout.zoneCount, static_cast<int>(layout->Zones().size()));
}
@ -102,7 +99,6 @@ namespace FancyZonesUnitTests
auto actualWorkArea = WorkArea::Create(m_hInst, m_workAreaId, parentUniqueId, m_workAreaRect);
Assert::IsNotNull(actualWorkArea->GetLayout().get());
Assert::IsNotNull(actualWorkArea->GetLayoutWindows().get());
Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().contains(m_workAreaId));
const auto& actualLayout = AppliedLayouts::instance().GetAppliedLayoutMap().at(m_workAreaId);
@ -179,428 +175,46 @@ namespace FancyZonesUnitTests
}
};
TEST_CLASS (WorkAreaMoveWindowUnitTests)
TEST_CLASS (WorkAreaSnapUnitTests)
{
const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}";
const FancyZonesDataTypes::WorkAreaId m_workAreaId{
HINSTANCE m_hInst{};
const HMONITOR m_monitor = Mocks::Monitor();
const FancyZonesUtils::Rect m_workAreaRect{ RECT(0, 0, 1920, 1080) };
const FancyZonesDataTypes::WorkAreaId m_parentUniqueId = {};
const FancyZonesDataTypes::WorkAreaId m_workAreaId = {
.monitorId = {
.monitor = Mocks::Monitor(),
.deviceId = {
.id = L"DELA026",
.monitor = m_monitor,
.deviceId = {
.id = L"device-id-1",
.instanceId = L"5&10a58c63&0&UID16777488",
.number = 1,
},
.serialNumber = L"serial-number"
},
.virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value()
.serialNumber = L"serial-number-1" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{310F2924-B587-4D87-97C2-90031BDBE3F1}").value()
};
FancyZonesDataTypes::WorkAreaId m_parentUniqueId; // default empty
HINSTANCE m_hInst{};
FancyZonesUtils::Rect m_workAreaRect{ RECT(0, 0, 1920, 1080) };
void PrepareEmptyLayout()
{
json::JsonObject root{};
json::JsonArray layoutsArray{};
{
json::JsonObject layout{};
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Blank)));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(0));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(0));
json::JsonObject workAreaId{};
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(m_workAreaId.monitorId.deviceId.id));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_workAreaId.monitorId.deviceId.instanceId));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_workAreaId.monitorId.serialNumber));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_workAreaId.monitorId.deviceId.number));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
json::JsonObject obj{};
obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaId);
obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
layoutsArray.Append(obj);
}
root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
AppliedLayouts::instance().LoadData();
}
void PrepareGridLayout()
{
json::JsonObject root{};
json::JsonArray layoutsArray{};
{
json::JsonObject layout{};
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid)));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20));
json::JsonObject workAreaId{};
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(m_workAreaId.monitorId.deviceId.id));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_workAreaId.monitorId.deviceId.instanceId));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_workAreaId.monitorId.serialNumber));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_workAreaId.monitorId.deviceId.number));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
json::JsonObject obj{};
obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaId);
obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
layoutsArray.Append(obj);
}
root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
AppliedLayouts::instance().LoadData();
}
TEST_METHOD_INITIALIZE(Init) noexcept
{
AppZoneHistory::instance().LoadData();
AppliedLayouts::instance().LoadData();
}
TEST_METHOD_CLEANUP(CleanUp) noexcept
{
std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
}
TEST_METHOD (EmptyZonesMoveLeftByIndex)
{
// prepare
PrepareEmptyLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)0, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (EmptyZonesRightByIndex)
{
// prepare
PrepareEmptyLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)0, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveLeftNonAppliedWindowByIndex)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveRightNonAppliedWindowByIndex)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByIndex)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
const auto& layoutWindows = workArea->GetLayoutWindows();
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true);
Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
}
TEST_METHOD (MoveAppliedWindowByIndexCycle)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ static_cast<ZoneIndex>(workArea->GetLayout()->Zones().size() - 1) } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByIndexNoCycle)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, false);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (EmptyZonesMoveByPosition)
{
// prepare
PrepareEmptyLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)0, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveLeftNonAppliedWindowByPosition)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveRightNonAppliedWindowByPosition)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_RIGHT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowHorizontallyByPosition)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_RIGHT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowVerticallyByPosition)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_DOWN, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByPositionHorizontallyCycle)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByPositionHorizontallyNoCycle)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, false);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByPositionVerticallyCycle)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
const auto& layoutWindows = workArea->GetLayoutWindows();
workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_UP, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByPositionVerticallyNoCycle)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_UP, false);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (ExtendZoneHorizontally)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
// test
workArea->ExtendWindowByDirectionAndPosition(window, VK_RIGHT);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (ExtendZoneVertically)
{
// prepare
PrepareGridLayout();
auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
// test
workArea->ExtendWindowByDirectionAndPosition(window, VK_DOWN);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt)
{
LayoutData layout{
.uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(),
.type = FancyZonesDataTypes::ZoneSetLayoutType::Grid,
.showSpacing = false,
.spacing = 0,
.zoneCount = 4,
.sensitivityRadius = 20,
};
AppliedLayouts::instance().ApplyLayout(m_workAreaId, layout);
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
@ -610,7 +224,10 @@ namespace FancyZonesUnitTests
SetWindowPos(window, nullptr, 150, 150, originalWidth, originalHeight, SWP_SHOWWINDOW);
SetWindowLong(window, GWL_STYLE, GetWindowLong(window, GWL_STYLE) & ~WS_SIZEBOX);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
Assert::IsTrue(workArea->Snap(window, { 1 }, true));
// wait for the window to be resized
std::this_thread::sleep_for(std::chrono::milliseconds(10));
RECT inZoneRect;
GetWindowRect(window, &inZoneRect);
@ -619,13 +236,46 @@ namespace FancyZonesUnitTests
Assert::AreEqual(originalHeight, (int)inZoneRect.bottom - (int)inZoneRect.top);
}
TEST_METHOD (WhenWindowIsResizablePlacingItIntoTheZoneShouldResizeIt)
{
LayoutData layout{
.uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(),
.type = FancyZonesDataTypes::ZoneSetLayoutType::Grid,
.showSpacing = false,
.spacing = 0,
.zoneCount = 4,
.sensitivityRadius = 20,
};
AppliedLayouts::instance().ApplyLayout(m_workAreaId, layout);
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
SetWindowPos(window, nullptr, 150, 150, 450, 550, SWP_SHOWWINDOW);
Assert::IsTrue(workArea->Snap(window, { 1 }, true));
// wait for the window to be resized
std::this_thread::sleep_for(std::chrono::milliseconds(10));
RECT zonedWindowRect;
GetWindowRect(window, &zonedWindowRect);
RECT zoneRect = workArea->GetLayout()->Zones().at(1).GetZoneRect();
Assert::AreEqual(zoneRect.left, zonedWindowRect.left);
Assert::AreEqual(zoneRect.right, zonedWindowRect.right);
Assert::AreEqual(zoneRect.top, zonedWindowRect.top);
Assert::AreEqual(zoneRect.bottom, zonedWindowRect.bottom);
}
TEST_METHOD (SnapWindowPropertyTest)
{
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
const ZoneIndexSet expected = { 1, 2 };
workArea->SnapWindow(window, expected);
Assert::IsTrue(workArea->Snap(window, expected));
const auto actual = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window);
Assert::AreEqual(expected.size(), actual.size());
@ -641,7 +291,7 @@ namespace FancyZonesUnitTests
const auto window = Mocks::WindowCreate(m_hInst);
const ZoneIndexSet expected = { 1, 2 };
workArea->SnapWindow(window, expected);
Assert::IsTrue(workArea->Snap(window, expected));
const auto processPath = get_process_path(window);
const auto history = AppZoneHistory::instance().GetZoneHistory(processPath, m_workAreaId);
@ -654,13 +304,25 @@ namespace FancyZonesUnitTests
}
}
TEST_METHOD (SnapLayoutAssignedWindowsTest)
{
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
const ZoneIndexSet expected = { 1, 2 };
Assert::IsTrue(workArea->Snap(window, expected));
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(expected == layoutWindows.GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (UnsnapPropertyTest)
{
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->SnapWindow(window, { 1, 2 });
workArea->UnsnapWindow(window);
Assert::IsTrue(workArea->Snap(window, { 1, 2 }));
Assert::IsTrue(workArea->Unsnap(window));
const auto actual = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window);
Assert::IsTrue(actual.empty());
@ -671,13 +333,25 @@ namespace FancyZonesUnitTests
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->SnapWindow(window, { 1, 2 });
workArea->UnsnapWindow(window);
Assert::IsTrue(workArea->Snap(window, { 1, 2 }));
Assert::IsTrue(workArea->Unsnap(window));
const auto processPath = get_process_path(window);
const auto history = AppZoneHistory::instance().GetZoneHistory(processPath, m_workAreaId);
Assert::IsFalse(history.has_value());
}
TEST_METHOD (UnsnapLayoutAssignedWindowsTest)
{
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
Assert::IsTrue(workArea->Snap(window, { 1, 2 }));
Assert::IsTrue(workArea->Unsnap(window));
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(layoutWindows.GetZoneIndexSetFromWindow(window).empty());
}
};
}

View File

@ -45,6 +45,14 @@ namespace FancyZonesEditor
protected void OnCancel(object sender, RoutedEventArgs e)
{
// restore backup, clean up
App.Overlay.EndEditing(true);
// select and draw applied layout
var settings = ((App)Application.Current).MainWindowSettings;
settings.SetSelectedModel(settings.AppliedModel);
App.Overlay.CurrentDataContext = settings.AppliedModel;
Close();
}
}

View File

@ -16,15 +16,11 @@ namespace FancyZonesEditor
KeyUp += GridEditorWindow_KeyUp;
KeyDown += ((App)Application.Current).App_KeyDown;
_stashedModel = (GridLayoutModel)(App.Overlay.CurrentDataContext as GridLayoutModel).Clone();
}
protected new void OnCancel(object sender, RoutedEventArgs e)
{
base.OnCancel(sender, e);
GridLayoutModel model = App.Overlay.CurrentDataContext as GridLayoutModel;
_stashedModel.RestoreTo(model);
}
private void GridEditorWindow_KeyUp(object sender, KeyEventArgs e)
@ -37,8 +33,6 @@ namespace FancyZonesEditor
((App)Application.Current).App_KeyUp(sender, e);
}
private GridLayoutModel _stashedModel;
// This is required to fix a WPF rendering bug when using custom chrome
private void EditorWindow_ContentRendered(object sender, System.EventArgs e)
{

View File

@ -0,0 +1,69 @@
// 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.Collections.Generic;
using System.Windows;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
{
public class LayoutBackup
{
private LayoutModel _backup;
private List<LayoutModel> _defaultLayoutsBackup;
public LayoutBackup()
{
}
public void Backup(LayoutModel model)
{
if (model is GridLayoutModel grid)
{
_backup = new GridLayoutModel(grid);
}
else if (model is CanvasLayoutModel canvas)
{
_backup = new CanvasLayoutModel(canvas);
}
_defaultLayoutsBackup = new List<LayoutModel>(MainWindowSettingsModel.DefaultLayouts.Layouts);
}
public void Restore()
{
if (_backup != null)
{
var settings = ((App)Application.Current).MainWindowSettings;
var selectedModel = settings.SelectedModel;
if (selectedModel == null)
{
return;
}
if (_backup is GridLayoutModel grid)
{
grid.RestoreTo((GridLayoutModel)selectedModel);
grid.InitTemplateZones();
}
else if (_backup is CanvasLayoutModel canvas)
{
canvas.RestoreTo((CanvasLayoutModel)selectedModel);
}
}
if (_defaultLayoutsBackup != null)
{
MainWindowSettingsModel.DefaultLayouts.Restore(_defaultLayoutsBackup);
}
}
public void Clear()
{
_backup = null;
_defaultLayoutsBackup = null;
}
}
}

View File

@ -30,8 +30,6 @@ namespace FancyZonesEditor
private const int MinimalForDefaultWrapPanelsHeight = 900;
private readonly MainWindowSettingsModel _settings = ((App)Application.Current).MainWindowSettings;
private LayoutModel _backup;
private List<LayoutModel> _defaultLayoutsBackup;
private ContentDialog _openedDialog;
private TextBlock _createLayoutAnnounce;
@ -244,21 +242,15 @@ namespace FancyZonesEditor
Logger.LogTrace();
var dataContext = ((FrameworkElement)sender).DataContext;
Select((LayoutModel)dataContext);
EditLayoutDialog.Hide();
var mainEditor = App.Overlay;
if (mainEditor.CurrentDataContext is not LayoutModel model)
if (dataContext is not LayoutModel model)
{
return;
}
model.IsSelected = false;
// make a copy
model = model.Clone();
mainEditor.CurrentDataContext = model;
string name = model.Name;
var index = name.LastIndexOf('(');
@ -293,11 +285,8 @@ namespace FancyZonesEditor
}
model.Name = name + " (" + (++maxCustomIndex) + ')';
model.Persist();
App.Overlay.Monitors[App.Overlay.CurrentDesktop].SetLayoutSettings(model);
App.FancyZonesEditorIO.SerializeAppliedLayouts();
App.FancyZonesEditorIO.SerializeCustomLayouts();
}
@ -327,7 +316,7 @@ namespace FancyZonesEditor
private void OnClosing(object sender, EventArgs e)
{
Logger.LogTrace();
CancelLayoutChanges();
App.Overlay.EndEditing(true);
App.FancyZonesEditorIO.SerializeAppliedLayouts();
App.FancyZonesEditorIO.SerializeCustomLayouts();
@ -356,17 +345,7 @@ namespace FancyZonesEditor
var dataContext = ((FrameworkElement)sender).DataContext;
Select((LayoutModel)dataContext);
if (_settings.SelectedModel is GridLayoutModel grid)
{
_backup = new GridLayoutModel(grid);
}
else if (_settings.SelectedModel is CanvasLayoutModel canvas)
{
_backup = new CanvasLayoutModel(canvas);
}
_defaultLayoutsBackup = new List<LayoutModel>(MainWindowSettingsModel.DefaultLayouts.Layouts);
App.Overlay.StartEditing((LayoutModel)dataContext);
Keyboard.ClearFocus();
EditLayoutDialogTitle.Text = string.Format(CultureInfo.CurrentCulture, Properties.Resources.Edit_Template, ((LayoutModel)dataContext).Name);
@ -459,7 +438,7 @@ namespace FancyZonesEditor
// EditLayout: Cancel changes
private void EditLayoutDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
CancelLayoutChanges();
App.Overlay.EndEditing(false);
Select(_settings.AppliedModel);
}
@ -474,8 +453,7 @@ namespace FancyZonesEditor
return;
}
_backup = null;
_defaultLayoutsBackup = null;
mainEditor.EndEditing(false);
// update current settings
if (model == _settings.AppliedModel)
@ -510,12 +488,6 @@ namespace FancyZonesEditor
if (result == ContentDialogResult.Primary)
{
LayoutModel model = element.DataContext as LayoutModel;
if (_backup != null && model.Guid == _backup.Guid)
{
_backup = null;
}
MainWindowSettingsModel.DefaultLayouts.Reset(model.Uuid);
if (model == _settings.AppliedModel)
@ -591,21 +563,6 @@ namespace FancyZonesEditor
}
}
private void CancelLayoutChanges()
{
if (_backup != null)
{
_settings.RestoreSelectedModel(_backup);
_backup = null;
}
if (_defaultLayoutsBackup != null)
{
MainWindowSettingsModel.DefaultLayouts.Restore(_defaultLayoutsBackup);
_defaultLayoutsBackup = null;
}
}
private void NumberBox_Loaded(object sender, RoutedEventArgs e)
{
// The TextBox inside a NumberBox doesn't inherit the Automation Properties name, so we have to set it.

View File

@ -267,27 +267,6 @@ namespace FancyZonesEditor
return foundModel;
}
public void RestoreSelectedModel(LayoutModel model)
{
if (SelectedModel == null || model == null)
{
return;
}
SelectedModel.SensitivityRadius = model.SensitivityRadius;
SelectedModel.TemplateZoneCount = model.TemplateZoneCount;
SelectedModel.IsSelected = model.IsSelected;
SelectedModel.IsApplied = model.IsApplied;
SelectedModel.Name = model.Name;
SelectedModel.QuickKey = model.QuickKey;
if (model is GridLayoutModel grid)
{
((GridLayoutModel)SelectedModel).Spacing = grid.Spacing;
((GridLayoutModel)SelectedModel).ShowSpacing = grid.ShowSpacing;
}
}
public void SetSelectedModel(LayoutModel model)
{
if (_selectedModel != null)

View File

@ -17,6 +17,7 @@ namespace FancyZonesEditor
private LayoutPreview _layoutPreview;
private UserControl _editorLayout;
private EditorWindow _editorWindow;
private LayoutBackup _layoutBackup = new LayoutBackup();
public List<Monitor> Monitors { get; private set; }
@ -261,6 +262,21 @@ namespace FancyZonesEditor
}
}
public void StartEditing(LayoutModel model)
{
_layoutBackup.Backup(model);
}
public void EndEditing(bool restoreBackup)
{
if (restoreBackup)
{
_layoutBackup.Restore();
}
_layoutBackup.Clear();
}
public void CloseLayoutWindow()
{
for (int i = 0; i < DesktopsCount; i++)

View File

@ -207,6 +207,8 @@ LRESULT KeyboardManagerEditor::KeyHookProc(int nCode, WPARAM wParam, LPARAM lPar
{
event.lParam = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
event.wParam = wParam;
event.lParam->vkCode = Helpers::EncodeKeyNumpadOrigin(event.lParam->vkCode, event.lParam->flags & LLKHF_EXTENDED);
if (editor->HandleKeyboardHookEvent(&event) == 1)
{
// Reset Num Lock whenever a NumLock key down event is suppressed since Num Lock key state change occurs before it is intercepted by low level hooks

View File

@ -27,7 +27,7 @@ DWORD KeyDropDownControl::GetSelectedValue(ComboBox comboBox)
}
auto value = winrt::unbox_value<hstring>(dataContext);
return stoi(std::wstring(value));
return stoul(std::wstring(value));
}
void KeyDropDownControl::SetSelectedValue(std::wstring value)

View File

@ -42,8 +42,7 @@ namespace KeyboardEventHandlers
key_count = std::get<Shortcut>(it->second).Size();
}
LPINPUT keyEventList = new INPUT[size_t(key_count)]();
memset(keyEventList, 0, sizeof(keyEventList));
LPINPUT keyEventList = new INPUT[size_t(key_count)]{};
// Handle remaps to VK_WIN_BOTH
DWORD target;
@ -148,7 +147,6 @@ namespace KeyboardEventHandlers
}
int key_count = 2;
LPINPUT keyEventList = new INPUT[size_t(key_count)]();
memset(keyEventList, 0, sizeof(keyEventList));
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
Helpers::SetKeyEvent(keyEventList, 1, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
@ -232,8 +230,7 @@ namespace KeyboardEventHandlers
{
// key down for all new shortcut keys except the common modifiers
key_count = dest_size - commonKeys;
keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
keyEventList = new INPUT[key_count]{};
int i = 0;
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
@ -243,8 +240,7 @@ namespace KeyboardEventHandlers
{
// Dummy key, key up for all the original shortcut modifier keys and key down for all the new shortcut keys but common keys in each are not repeated
key_count = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE + (src_size - 1) + (dest_size) - (2 * static_cast<size_t>(commonKeys));
keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
keyEventList = new INPUT[key_count]{};
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->Ctrl+V, press Win+A, since Win will be released here we need to send a dummy event before it
int i = 0;
@ -282,8 +278,7 @@ namespace KeyboardEventHandlers
it->second.isOriginalActionKeyPressed = true;
}
keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
keyEventList = new INPUT[key_count]{};
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->V, press Win+A, since Win will be released here we need to send a dummy event before it
int i = 0;
@ -302,7 +297,7 @@ namespace KeyboardEventHandlers
// Modifier state reset might be required for this key depending on the shortcut's action and target modifier - ex: Win+Caps -> Ctrl
if (it->first.GetCtrlKey() == NULL && it->first.GetAltKey() == NULL && it->first.GetShiftKey() == NULL)
{
ResetIfModifierKeyForLowerLevelKeyHandlers(ii,static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), data->lParam->vkCode);
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), data->lParam->vkCode);
}
}
@ -361,8 +356,7 @@ namespace KeyboardEventHandlers
key_count += 1;
}
keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
keyEventList = new INPUT[key_count]{};
// Release new shortcut state (release in reverse order of shortcut to be accurate)
int i = 0;
@ -400,8 +394,7 @@ namespace KeyboardEventHandlers
key_count--;
}
keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
keyEventList = new INPUT[key_count]{};
// Release new key state
int i = 0;
@ -453,8 +446,7 @@ namespace KeyboardEventHandlers
}
size_t key_count = 1;
LPINPUT keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
LPINPUT keyEventList = new INPUT[key_count]{};
if (remapToShortcut)
{
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
@ -476,13 +468,12 @@ namespace KeyboardEventHandlers
LPINPUT keyEventList;
if (remapToShortcut)
{
keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
keyEventList = new INPUT[key_count]{};
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else if (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
{
// If remapped to disable, do nothing and suppress the key event
// If remapped to disable, do nothing and suppress the key event
// Since the original shortcut's action key is released, set it to false
it->second.isOriginalActionKeyPressed = false;
return 1;
@ -491,13 +482,12 @@ namespace KeyboardEventHandlers
{
// Check if the keyboard state is clear apart from the target remap key (by creating a temp Shortcut object with the target key)
bool isKeyboardStateClear = Shortcut(std::vector<int32_t>({ Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)) })).IsKeyboardStateClearExceptShortcut(ii);
// If the keyboard state is clear, we release the target key but do not reset the remap state
if (isKeyboardStateClear)
{
keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD,static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
keyEventList = new INPUT[key_count]{};
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else
{
@ -507,8 +497,7 @@ namespace KeyboardEventHandlers
// 1 for releasing new key and original shortcut modifiers, and dummy key
key_count = dest_size + (src_size - 1) + KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE;
keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
keyEventList = new INPUT[key_count]{};
// Release new key state
int i = 0;
@ -574,7 +563,7 @@ namespace KeyboardEventHandlers
size_t key_count;
LPINPUT keyEventList = nullptr;
// Check if a new remapping should be applied
Shortcut currentlyPressed = it->first;
currentlyPressed.actionKey = data->lParam->vkCode;
@ -588,8 +577,7 @@ namespace KeyboardEventHandlers
DWORD to = std::get<0>(newRemapping.targetShortcut);
bool isLastKeyStillPressed = ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey));
key_count = static_cast<size_t>(from.Size()) - 1 + 1 + (isLastKeyStillPressed ? 1 : 0);
keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
keyEventList = new INPUT[key_count]{};
int i = 0;
Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
if (ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey)))
@ -599,7 +587,8 @@ namespace KeyboardEventHandlers
i++;
}
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(to), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}else
}
else
{
Shortcut to = std::get<Shortcut>(newRemapping.targetShortcut);
bool isLastKeyStillPressed = ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey));
@ -608,7 +597,7 @@ namespace KeyboardEventHandlers
temp_key_count_calculation += static_cast<size_t>(to.Size()) - 1;
temp_key_count_calculation -= static_cast<size_t>(2) * from.GetCommonModifiersCount(to);
key_count = temp_key_count_calculation + 1 + (isLastKeyStillPressed ? 1 : 0);
keyEventList = new INPUT[key_count]();
keyEventList = new INPUT[key_count]{};
int i = 0;
Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, to);
@ -635,7 +624,7 @@ namespace KeyboardEventHandlers
}
else
{
// Key up for all new shortcut keys, key down for original shortcut modifiers and current key press but common keys aren't repeated
// Key up for all new shortcut keys, key down for original shortcut modifiers and current key press but common keys aren't repeated
key_count = (dest_size) + (src_size - 1) - (2 * static_cast<size_t>(commonKeys));
// If the target shortcut's action key is pressed, then it should be released and original shortcut's action key should be set
@ -646,8 +635,7 @@ namespace KeyboardEventHandlers
key_count += 2;
}
keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
keyEventList = new INPUT[key_count]{};
// Release new shortcut state (release in reverse order of shortcut to be accurate)
int i = 0;
@ -719,8 +707,7 @@ namespace KeyboardEventHandlers
// Key down for original shortcut modifiers and action key, and current key press
size_t key_count = src_size + 1;
LPINPUT keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
LPINPUT keyEventList = new INPUT[key_count]{};
// Set original shortcut key down state
int i = 0;
@ -730,7 +717,7 @@ namespace KeyboardEventHandlers
if (isRemapToDisable && isOriginalActionKeyPressed)
{
// Set original action key
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD,static_cast<WORD>(it->first.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(it->first.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
i++;
}
else
@ -812,7 +799,7 @@ namespace KeyboardEventHandlers
std::wstring query_string;
AppSpecificShortcutRemapTable::iterator it;
// Check if an app-specific shortcut is already activated
if (state.GetActivatedApp() == KeyboardManagerConstants::NoActivatedApp)
{
@ -854,8 +841,7 @@ namespace KeyboardEventHandlers
if (Helpers::IsModifierKey(key) && !(key == VK_LWIN || key == VK_RWIN || key == CommonSharedConstants::VK_WIN_BOTH))
{
int key_count = 1;
LPINPUT keyEventList = new INPUT[size_t(key_count)]();
memset(keyEventList, 0, sizeof(keyEventList));
LPINPUT keyEventList = new INPUT[size_t(key_count)]{};
// Use the suppress flag to ensure these are not intercepted by any remapped keys or shortcuts
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(key), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG);

View File

@ -66,13 +66,14 @@ void KeyboardManager::LoadSettings()
}
}
LRESULT CALLBACK KeyboardManager::HookProc(int nCode, WPARAM wParam, LPARAM lParam)
LRESULT CALLBACK KeyboardManager::HookProc(int nCode, const WPARAM wParam, const LPARAM lParam)
{
LowlevelKeyboardEvent event;
if (nCode == HC_ACTION)
{
event.lParam = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
event.wParam = wParam;
event.lParam->vkCode = Helpers::EncodeKeyNumpadOrigin(event.lParam->vkCode, event.lParam->flags & LLKHF_EXTENDED);
if (keyboardManagerObjectPtr->HandleKeyboardHookEvent(&event) == 1)
{
// Reset Num Lock whenever a NumLock key down event is suppressed since Num Lock key state change occurs before it is intercepted by low level hooks
@ -83,7 +84,7 @@ LRESULT CALLBACK KeyboardManager::HookProc(int nCode, WPARAM wParam, LPARAM lPar
return 1;
}
}
return CallNextHookEx(hookHandleCopy, nCode, wParam, lParam);
}

View File

@ -9,6 +9,50 @@
namespace Helpers
{
DWORD EncodeKeyNumpadOrigin(const DWORD key, const bool extended)
{
bool numpad_originated = false;
switch (key)
{
case VK_LEFT:
case VK_RIGHT:
case VK_UP:
case VK_DOWN:
case VK_INSERT:
case VK_DELETE:
case VK_PRIOR:
case VK_NEXT:
case VK_HOME:
case VK_END:
numpad_originated = !extended;
break;
case VK_RETURN:
case VK_DIVIDE:
numpad_originated = extended;
break;
}
if (numpad_originated)
return key | GetNumpadOriginEncodingBit();
else
return key;
}
DWORD ClearKeyNumpadOrigin(const DWORD key)
{
return (key & ~GetNumpadOriginEncodingBit());
}
bool IsNumpadOriginated(const DWORD key)
{
return !!(key & GetNumpadOriginEncodingBit());
}
DWORD GetNumpadOriginEncodingBit()
{
// Intentionally do not mimic KF_EXTENDED to avoid confusion, because it's not the same thing
// See EncodeKeyNumpadOrigin.
return 1ull << 31;
}
// Function to check if the key is a modifier key
bool IsModifierKey(DWORD key)
{
@ -18,7 +62,8 @@ namespace Helpers
// Function to get the combined key for modifier keys
DWORD GetCombinedKey(DWORD key)
{
switch (key) {
switch (key)
{
case VK_LWIN:
case VK_RWIN:
return CommonSharedConstants::VK_WIN_BOTH;

View File

@ -14,6 +14,12 @@ namespace Helpers
Shift,
Action
};
// Functions to encode that a key is originated from numpad
DWORD EncodeKeyNumpadOrigin(const DWORD key, const bool extended);
DWORD ClearKeyNumpadOrigin(const DWORD key);
bool IsNumpadOriginated(const DWORD key);
DWORD GetNumpadOriginEncodingBit();
// Function to check if the key is a modifier key
bool IsModifierKey(DWORD key);

View File

@ -194,7 +194,7 @@ DWORD Shortcut::GetShiftKey() const
}
// Function to check if the input key matches the win key expected in the shortcut
bool Shortcut::CheckWinKey(const DWORD& input) const
bool Shortcut::CheckWinKey(const DWORD input) const
{
if (winKey == ModifierKey::Disabled)
{
@ -216,7 +216,7 @@ bool Shortcut::CheckWinKey(const DWORD& input) const
}
// Function to check if the input key matches the ctrl key expected in the shortcut
bool Shortcut::CheckCtrlKey(const DWORD& input) const
bool Shortcut::CheckCtrlKey(const DWORD input) const
{
if (ctrlKey == ModifierKey::Disabled)
{
@ -238,7 +238,7 @@ bool Shortcut::CheckCtrlKey(const DWORD& input) const
}
// Function to check if the input key matches the alt key expected in the shortcut
bool Shortcut::CheckAltKey(const DWORD& input) const
bool Shortcut::CheckAltKey(const DWORD input) const
{
if (altKey == ModifierKey::Disabled)
{
@ -260,7 +260,7 @@ bool Shortcut::CheckAltKey(const DWORD& input) const
}
// Function to check if the input key matches the shift key expected in the shortcut
bool Shortcut::CheckShiftKey(const DWORD& input) const
bool Shortcut::CheckShiftKey(const DWORD input) const
{
if (shiftKey == ModifierKey::Disabled)
{
@ -282,7 +282,7 @@ bool Shortcut::CheckShiftKey(const DWORD& input) const
}
// Function to set a key in the shortcut based on the passed key code argument. Returns false if it is already set to the same value. This can be used to avoid UI refreshing
bool Shortcut::SetKey(const DWORD& input)
bool Shortcut::SetKey(const DWORD input)
{
// Since there isn't a key for a common Win key we use the key code defined by us
if (input == CommonSharedConstants::VK_WIN_BOTH)
@ -394,7 +394,7 @@ bool Shortcut::SetKey(const DWORD& input)
}
// Function to reset the state of a shortcut key based on the passed key code argument. Since there is no VK_WIN code, use the second argument for setting common win key.
void Shortcut::ResetKey(const DWORD& input)
void Shortcut::ResetKey(const DWORD input)
{
// Since there isn't a key for a common Win key this is handled with a separate argument.
if (input == CommonSharedConstants::VK_WIN_BOTH || input == VK_LWIN || input == VK_RWIN)
@ -415,7 +415,7 @@ void Shortcut::ResetKey(const DWORD& input)
}
else
{
actionKey = NULL;
actionKey = {};
}
}

View File

@ -1,5 +1,8 @@
#pragma once
#include "ModifierKey.h"
#include <compare>
#include <tuple>
#include <variant>
namespace KeyboardManagerInput
@ -14,91 +17,34 @@ private:
// Function to split a wstring based on a delimiter and return a vector of split strings
std::vector<std::wstring> splitwstring(const std::wstring& input, wchar_t delimiter);
public:
ModifierKey winKey;
ModifierKey ctrlKey;
ModifierKey altKey;
ModifierKey shiftKey;
DWORD actionKey;
// By default create an empty shortcut
Shortcut() :
winKey(ModifierKey::Disabled), ctrlKey(ModifierKey::Disabled), altKey(ModifierKey::Disabled), shiftKey(ModifierKey::Disabled), actionKey(NULL)
inline auto comparator() const
{
return std::make_tuple(winKey, ctrlKey, altKey, shiftKey, actionKey);
}
public:
ModifierKey winKey = ModifierKey::Disabled;
ModifierKey ctrlKey = ModifierKey::Disabled;
ModifierKey altKey = ModifierKey::Disabled;
ModifierKey shiftKey = ModifierKey::Disabled;
DWORD actionKey = {};
Shortcut() = default;
// Constructor to initialize Shortcut from it's virtual key code string representation.
Shortcut(const std::wstring& shortcutVK);
// Constructor to initialize shortcut from a list of keys
Shortcut(const std::vector<int32_t>& keys);
// == operator
inline bool operator==(const Shortcut& sc) const
inline friend auto operator<=>(const Shortcut& lhs, const Shortcut& rhs) noexcept
{
return (winKey == sc.winKey && ctrlKey == sc.ctrlKey && altKey == sc.altKey && shiftKey == sc.shiftKey && actionKey == sc.actionKey);
return lhs.comparator() <=> rhs.comparator();
}
// Less than operator must be defined to use with std::map.
inline bool operator<(const Shortcut& sc) const
inline friend bool operator==(const Shortcut& lhs, const Shortcut& rhs) noexcept
{
// Compare win key first
if (winKey < sc.winKey)
{
return true;
}
else if (winKey > sc.winKey)
{
return false;
}
else
{
// If win key is equal, then compare ctrl key
if (ctrlKey < sc.ctrlKey)
{
return true;
}
else if (ctrlKey > sc.ctrlKey)
{
return false;
}
else
{
// If ctrl key is equal, then compare alt key
if (altKey < sc.altKey)
{
return true;
}
else if (altKey > sc.altKey)
{
return false;
}
else
{
// If alt key is equal, then compare shift key
if (shiftKey < sc.shiftKey)
{
return true;
}
else if (shiftKey > sc.shiftKey)
{
return false;
}
else
{
// If shift key is equal, then compare action key
if (actionKey < sc.actionKey)
{
return true;
}
else
{
return false;
}
}
}
}
}
return lhs.comparator() == rhs.comparator();
}
// Function to return the number of keys in the shortcut
@ -126,22 +72,22 @@ public:
DWORD GetShiftKey() const;
// Function to check if the input key matches the win key expected in the shortcut
bool CheckWinKey(const DWORD& input) const;
bool CheckWinKey(const DWORD input) const;
// Function to check if the input key matches the ctrl key expected in the shortcut
bool CheckCtrlKey(const DWORD& input) const;
bool CheckCtrlKey(const DWORD input) const;
// Function to check if the input key matches the alt key expected in the shortcut
bool CheckAltKey(const DWORD& input) const;
bool CheckAltKey(const DWORD input) const;
// Function to check if the input key matches the shift key expected in the shortcut
bool CheckShiftKey(const DWORD& input) const;
bool CheckShiftKey(const DWORD input) const;
// Function to set a key in the shortcut based on the passed key code argument. Returns false if it is already set to the same value. This can be used to avoid UI refreshing
bool SetKey(const DWORD& input);
bool SetKey(const DWORD input);
// Function to reset the state of a shortcut key based on the passed key code argument
void ResetKey(const DWORD& input);
void ResetKey(const DWORD input);
// Function to return the string representation of the shortcut in virtual key codes appended in a string by ";" separator.
winrt::hstring ToHstringVK() const;

View File

@ -29,6 +29,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
[DataRow("base64 abc", typeof(Base64.Base64Request))]
[DataRow("base99 abc", null)]
[DataRow("base64s abc", null)]
[DataRow("base64d abc=", typeof(Base64.Base64DecodeRequest))]
public void ParserTest(string input, Type? expectedRequestType)
{
var parser = new InputParser();
@ -77,7 +78,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
private static bool CommandIsKnown(string command)
{
string[] hashes = new string[] { "md5", "sha1", "sha256", "sha384", "sha512", "base64" };
string[] hashes = new string[] { "md5", "sha1", "sha256", "sha384", "sha512", "base64", "base64d" };
if (hashes.Contains(command.ToLowerInvariant()))
{

View File

@ -0,0 +1,58 @@
// 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.Diagnostics;
using System.Text;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Base64
{
public class Base64DecodeRequest : IComputeRequest
{
public byte[] Result { get; set; }
public string Description => "Base64 Decoding";
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private string DataToDecode { get; set; }
public Base64DecodeRequest(string dataToDecode)
{
DataToDecode = dataToDecode ?? throw new ArgumentNullException(nameof(dataToDecode));
}
public bool Compute()
{
IsSuccessful = true;
try
{
Result = System.Convert.FromBase64String(DataToDecode);
}
catch (Exception e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
if (Result != null)
{
return Encoding.UTF8.GetString(Result);
}
else
{
return string.Empty;
}
}
}
}

View File

@ -121,6 +121,12 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
request = new Base64Request(Encoding.UTF8.GetBytes(content));
}
else if (command.ToLower(null) == "base64d")
{
int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
request = new Base64DecodeRequest(content);
}
else
{
throw new FormatException($"Invalid Query: {query.RawUserQuery}");

View File

@ -13,6 +13,8 @@ using System.Linq;
using System.Reflection;
using System.Windows.Input;
using ManagedCommon;
using Microsoft.Plugin.Shell.Properties;
using Microsoft.PowerToys.Settings.UI.Library;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
using Wox.Plugin.Common;
@ -21,7 +23,7 @@ using Control = System.Windows.Controls.Control;
namespace Microsoft.Plugin.Shell
{
public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable
public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu, ISavable
{
private static readonly IFileSystem FileSystem = new FileSystem();
private static readonly IPath Path = FileSystem.Path;
@ -37,6 +39,16 @@ namespace Microsoft.Plugin.Shell
public string Description => Properties.Resources.wox_plugin_cmd_plugin_description;
public IEnumerable<PluginAdditionalOption> AdditionalOptions => new List<PluginAdditionalOption>()
{
new PluginAdditionalOption()
{
Key = "LeaveShellOpen",
DisplayLabel = Resources.wox_leave_shell_open,
Value = _settings.LeaveShellOpen,
},
};
private PluginInitContext _context;
public Main()
@ -217,17 +229,41 @@ namespace Microsoft.Plugin.Shell
if (ExistInPath(filename))
{
var arguments = parts[1];
info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsVerbArg);
if (_settings.LeaveShellOpen)
{
// Wrap the command in a cmd.exe process
info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{filename} {arguments}\"", runAsVerbArg);
}
else
{
info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsVerbArg);
}
}
else
{
if (_settings.LeaveShellOpen)
{
// Wrap the command in a cmd.exe process
info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{command}\"", runAsVerbArg);
}
else
{
info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg);
}
}
}
else
{
if (_settings.LeaveShellOpen)
{
// Wrap the command in a cmd.exe process
info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{command}\"", runAsVerbArg);
}
else
{
info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg);
}
}
else
{
info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg);
}
}
}
else
@ -375,5 +411,19 @@ namespace Microsoft.Plugin.Shell
return resultlist;
}
public void UpdateSettings(PowerLauncherPluginSettings settings)
{
var leaveShellOpen = false;
if (settings != null && settings.AdditionalOptions != null)
{
var optionLeaveShellOpen = settings.AdditionalOptions.FirstOrDefault(x => x.Key == "LeaveShellOpen");
leaveShellOpen = optionLeaveShellOpen?.Value ?? leaveShellOpen;
_settings.LeaveShellOpen = leaveShellOpen;
}
Save();
}
}
}

View File

@ -60,6 +60,15 @@ namespace Microsoft.Plugin.Shell.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Keep shell open.
/// </summary>
public static string wox_leave_shell_open {
get {
return ResourceManager.GetString("wox_leave_shell_open", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to this command has been executed {0} times.
/// </summary>

View File

@ -141,4 +141,7 @@
<data name="wox_plugin_cmd_run_as_user" xml:space="preserve">
<value>Run as different user (Ctrl+Shift+U)</value>
</data>
<data name="wox_leave_shell_open" xml:space="preserve">
<value>Keep shell open</value>
</data>
</root>

View File

@ -17,7 +17,9 @@ namespace Peek.Common.Helpers
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
List<string> format = new List<string>
{
resourceLoader.GetString("ReadableString_ByteAbbreviationFormat"), // "B"
(bytes == 1) ?
resourceLoader.GetString("ReadableString_ByteAbbreviationFormat") : // "byte"
resourceLoader.GetString("ReadableString_BytesAbbreviationFormat"), // "bytes"
resourceLoader.GetString("ReadableString_KiloByteAbbreviationFormat"), // "KB"
resourceLoader.GetString("ReadableString_MegaByteAbbreviationFormat"), // "MB"
resourceLoader.GetString("ReadableString_GigaByteAbbreviationFormat"), // "GB"

View File

@ -12,9 +12,15 @@
mc:Ignorable="d">
<Grid>
<controls:WebView2 x:Name="PreviewBrowser"
Loaded="PreviewWV2_Loaded"
NavigationStarting="PreviewBrowser_NavigationStarting"
NavigationCompleted="PreviewWV2_NavigationCompleted"/>
<controls:WebView2
x:Name="PreviewBrowser"
Loaded="PreviewWV2_Loaded"
NavigationStarting="PreviewBrowser_NavigationStarting"
NavigationCompleted="PreviewWV2_NavigationCompleted" />
<ContentDialog
x:Name="OpenUriDialog"
x:Uid="OpenUriDialog"
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
SecondaryButtonClick="OpenUriDialog_SecondaryButtonClick" />
</Grid>
</UserControl>

View File

@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using Peek.Common.Constants;
using Peek.Common.Helpers;
using Windows.ApplicationModel.DataTransfer;
using Windows.System;
using Windows.UI;
@ -96,6 +98,7 @@ namespace Peek.FilePreviewer.Controls
private void SourcePropertyChanged()
{
OpenUriDialog.Hide();
Navigate();
}
@ -135,6 +138,7 @@ namespace Peek.FilePreviewer.Controls
}
PreviewBrowser.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
PreviewBrowser.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
}
catch (Exception ex)
{
@ -149,6 +153,16 @@ namespace Peek.FilePreviewer.Controls
DOMContentLoaded?.Invoke(sender, args);
}
private async void CoreWebView2_NewWindowRequested(CoreWebView2 sender, CoreWebView2NewWindowRequestedEventArgs args)
{
// Monaco opens URI in a new window. We open the URI in the default web browser.
if (args.Uri != null && args.IsUserInitiated)
{
args.Handled = true;
await ShowOpenUriDialogAsync(new Uri(args.Uri));
}
}
private async void PreviewBrowser_NavigationStarting(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args)
{
if (_navigatedUri == null)
@ -157,10 +171,11 @@ namespace Peek.FilePreviewer.Controls
}
// In case user starts or tries to navigate from within the HTML file we launch default web browser for navigation.
if (args.Uri != null && args.Uri != _navigatedUri?.ToString() && args.IsUserInitiated)
// TODO: && args.IsUserInitiated - always false for PDF files, revert the workaround when fixed in WebView2: https://github.com/microsoft/PowerToys/issues/27403
if (args.Uri != null && args.Uri != _navigatedUri?.ToString())
{
args.Cancel = true;
await Launcher.LaunchUriAsync(new Uri(args.Uri));
await ShowOpenUriDialogAsync(new Uri(args.Uri));
}
}
@ -171,7 +186,29 @@ namespace Peek.FilePreviewer.Controls
_navigatedUri = Source;
}
NavigationCompleted?.Invoke(sender, args);
// Don't raise NavigationCompleted event if NavigationStarting has been cancelled
if (args.WebErrorStatus != CoreWebView2WebErrorStatus.OperationCanceled)
{
NavigationCompleted?.Invoke(sender, args);
}
}
private async Task ShowOpenUriDialogAsync(Uri uri)
{
OpenUriDialog.Content = uri.ToString();
var result = await OpenUriDialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
await Launcher.LaunchUriAsync(uri);
}
}
private void OpenUriDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
var dataPackage = new DataPackage();
dataPackage.SetText(sender.Content.ToString());
Clipboard.SetContent(dataPackage);
}
}
}

View File

@ -154,7 +154,7 @@
<comment>Date Modified label for the unsupported files view. {0} is the date.</comment>
</data>
<data name="ReadableString_ByteAbbreviationFormat" xml:space="preserve">
<value>{0} bytes</value>
<value>{0} byte</value>
<comment>Abbreviation for the size unit byte.</comment>
</data>
<data name="ReadableString_KiloByteAbbreviationFormat" xml:space="preserve">
@ -233,4 +233,24 @@
<value>{0} (extracted {1})</value>
<comment>{0} is the size of the archive, {1} is the extracted size</comment>
</data>
<data name="ReadableString_BytesAbbreviationFormat" xml:space="preserve">
<value>{0} bytes</value>
<comment>Abbreviation for the size bytes</comment>
</data>
<data name="OpenUriDialog.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
<comment>Dialog showed when an URI is clicked. Button to close the dialog.</comment>
</data>
<data name="OpenUriDialog.PrimaryButtonText" xml:space="preserve">
<value>Open</value>
<comment>Dialog showed when an URI is clicked. Button to open the URI.</comment>
</data>
<data name="OpenUriDialog.SecondaryButtonText" xml:space="preserve">
<value>Copy</value>
<comment>Dialog showed when an URI is clicked. Button to copy the URI.</comment>
</data>
<data name="OpenUriDialog.Title" xml:space="preserve">
<value>Do you want Peek to open the external application?</value>
<comment>Title of the dialog showed when an URI is clicked,"Peek" is the name of the utility. </comment>
</data>
</root>

View File

@ -503,6 +503,7 @@
<ToggleButton
x:Name="toggleButton_enumItems"
x:Uid="ToggleButton_EnumItems"
IsChecked="True"
Height="32"
Content="&#xEA40;"
FontFamily="{ThemeResource SymbolThemeFontFamily}" />

View File

@ -157,6 +157,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco
_webView.NavigationCompleted += WebView2Init;
_webView.Height = this.Height;
_webView.Width = this.Width;
_webView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
Controls.Add(_webView);
_webView.SendToBack();
_loadingBar.Value = 100;
@ -218,6 +219,16 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco
}
}
private async void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e)
{
// Monaco opens URI in a new window. We open the URI in the default web browser.
if (e.Uri != null && e.IsUserInitiated)
{
e.Handled = true;
await Launcher.LaunchUriAsync(new Uri(e.Uri));
}
}
/// <summary>
/// This event sets the height and width of the webview to the size of the form
/// </summary>