Skip to content

Commit

Permalink
[FancyZones] Use Ctrl+Win+Alt+arrows to Expand/shrink windows to adja…
Browse files Browse the repository at this point in the history
…cent zones (#6446)

* Added an Alt key hook

* Refactor a block of code into a method

* Again refactored existing code

* Apparently Win+Alt does not reach FancyZones

* Using Ctrl+alt instead of Win+alt for now

* It works

* Fixed VD change

* Remove unused member

* Fix compilation error

* Enable shrinking

* Fixed comments in .h files

* Remove newline

* Refactored a function into two

The next task is to simplify their code.

* Updated a comment

* Update a variable name

* Reverted to the old implementation of directional move

* More refactoring

* Remove space

* Fixed windows getting stuck

* Changed function name
  • Loading branch information
ivan100sic committed Sep 11, 2020
1 parent 82e1be2 commit 0e32edb
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 68 deletions.
50 changes: 33 additions & 17 deletions src/modules/fancyzones/lib/FancyZones.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ struct FancyZones : public winrt::implements<FancyZones, IFancyZones, IFancyZone
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, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept;

void RegisterVirtualDesktopUpdates(std::vector<GUID>& ids) noexcept;

Expand Down Expand Up @@ -524,21 +525,23 @@ FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept
// Return true to swallow the keyboard event
bool const shift = GetAsyncKeyState(VK_SHIFT) & 0x8000;
bool const win = GetAsyncKeyState(VK_LWIN) & 0x8000 || GetAsyncKeyState(VK_RWIN) & 0x8000;
if (win && !shift)
{
bool const ctrl = GetAsyncKeyState(VK_CONTROL) & 0x8000;
if (ctrl)
{
// Temporarily disable Win+Ctrl+Number functionality
//if ((info->vkCode >= '0') && (info->vkCode <= '9'))
//{
// // Win+Ctrl+Number will cycle through ZoneSets
// Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/);
// CycleActiveZoneSet(info->vkCode);
// return true;
//}
}
else if ((info->vkCode == VK_RIGHT) || (info->vkCode == VK_LEFT) || (info->vkCode == VK_UP) || (info->vkCode == VK_DOWN))
bool const alt = GetAsyncKeyState(VK_MENU) & 0x8000;
bool const ctrl = GetAsyncKeyState(VK_CONTROL) & 0x8000;
if ((win && !shift && !ctrl) || (win && ctrl && alt))
{
// Temporarily disable Win+Ctrl+Number functionality
// if (ctrl)
// {
// if ((info->vkCode >= '0') && (info->vkCode <= '9'))
// {
// // Win+Ctrl+Number will cycle through ZoneSets
// Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/);
// CycleActiveZoneSet(info->vkCode);
// return true;
// }
// }
// else
if ((info->vkCode == VK_RIGHT) || (info->vkCode == VK_LEFT) || (info->vkCode == VK_UP) || (info->vkCode == VK_DOWN))
{
if (ShouldProcessSnapHotkey(info->vkCode))
{
Expand Down Expand Up @@ -1142,7 +1145,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
{
// Multi monitor environment.
// First, try to stay on the same monitor
bool success = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, false, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current));
bool success = ProcessDirectedSnapHotkey(window, vkCode, false, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current));
if (success)
{
return true;
Expand Down Expand Up @@ -1250,7 +1253,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
else
{
// Single monitor environment, or combined multi-monitor environment.
return m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, true, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current));
return ProcessDirectedSnapHotkey(window, vkCode, true, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current));
}
}

Expand All @@ -1271,6 +1274,19 @@ bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
return false;
}

bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept
{
// Check whether Alt is used in the shortcut key combination
if (GetAsyncKeyState(VK_MENU) & 0x8000)
{
return m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, zoneWindow);
}
else
{
return m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, zoneWindow);
}
}

void FancyZones::RegisterVirtualDesktopUpdates(std::vector<GUID>& ids) noexcept
{
std::unique_lock writeLock(m_lock);
Expand Down
23 changes: 17 additions & 6 deletions src/modules/fancyzones/lib/WindowMoveHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ class WindowMoveHandlerPrivate
void MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept;

void MoveWindowIntoZoneByIndexSet(HWND window, const std::vector<size_t>& indexSet, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept;
bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow);
bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow);
bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept;
bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept;
bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept;

private:
void WarnIfElevationIsRequired(HWND window) noexcept;
Expand Down Expand Up @@ -160,16 +161,21 @@ void WindowMoveHandler::MoveWindowIntoZoneByIndexSet(HWND window, const std::vec
pimpl->MoveWindowIntoZoneByIndexSet(window, indexSet, zoneWindow);
}

bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow)
bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept
{
return pimpl->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, cycle, zoneWindow);
}

bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow)
bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept
{
return pimpl->MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, zoneWindow);
}

bool WindowMoveHandler::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept
{
return pimpl->ExtendWindowByDirectionAndPosition(window, vkCode, zoneWindow);
}

void WindowMoveHandlerPrivate::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept
{
if (!FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent())
Expand Down Expand Up @@ -376,16 +382,21 @@ void WindowMoveHandlerPrivate::MoveWindowIntoZoneByIndexSet(HWND window, const s
}
}

bool WindowMoveHandlerPrivate::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow)
bool WindowMoveHandlerPrivate::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept
{
return zoneWindow && zoneWindow->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, cycle);
}

bool WindowMoveHandlerPrivate::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow)
bool WindowMoveHandlerPrivate::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept
{
return zoneWindow && zoneWindow->MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle);
}

bool WindowMoveHandlerPrivate::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept
{
return zoneWindow && zoneWindow->ExtendWindowByDirectionAndPosition(window, vkCode);
}

void WindowMoveHandlerPrivate::WarnIfElevationIsRequired(HWND window) noexcept
{
static bool warning_shown = false;
Expand Down
5 changes: 3 additions & 2 deletions src/modules/fancyzones/lib/WindowMoveHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ class WindowMoveHandler
void MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept;

void MoveWindowIntoZoneByIndexSet(HWND window, const std::vector<size_t>& indexSet, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept;
bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow);
bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow);
bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept;
bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept;
bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr<IZoneWindow> zoneWindow) noexcept;

private:
class WindowMoveHandlerPrivate* pimpl;
Expand Down
151 changes: 150 additions & 1 deletion src/modules/fancyzones/lib/ZoneSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,16 @@ struct ZoneSet : winrt::implements<ZoneSet, IZoneSet>
MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(bool)
MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(bool)
ExtendWindowByDirectionAndPosition(HWND window, HWND windowZone, DWORD vkCode) noexcept;
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept;
IFACEMETHODIMP_(bool)
CalculateZones(RECT workArea, int zoneCount, int spacing) noexcept;
IFACEMETHODIMP_(bool)
IsZoneEmpty(int zoneIndex) noexcept;
IFACEMETHODIMP_(std::vector<size_t>)
GetCombinedZoneRange(const std::vector<size_t>& initialZones, const std::vector<size_t>& finalZones) noexcept;

private:
bool CalculateFocusLayout(Rect workArea, int zoneCount) noexcept;
Expand All @@ -161,6 +165,12 @@ struct ZoneSet : winrt::implements<ZoneSet, IZoneSet>

std::vector<winrt::com_ptr<IZone>> m_zones;
std::map<HWND, std::vector<size_t>> m_windowIndexSet;

// Needed for ExtendWindowByDirectionAndPosition
std::map<HWND, std::vector<size_t>> m_windowInitialIndexSet;
std::map<HWND, size_t> m_windowFinalIndex;
bool m_inExtendWindow = false;

ZoneSetConfig m_config;
};

Expand Down Expand Up @@ -276,6 +286,13 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND windowZone, const std::v
return;
}

// Always clear the info related to SelectManyZones if it's not being used
if (!m_inExtendWindow)
{
m_windowFinalIndex.erase(window);
m_windowInitialIndexSet.erase(window);
}

RECT size;
bool sizeEmpty = true;
size_t bitmask = 0;
Expand Down Expand Up @@ -410,12 +427,15 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND windowZone,
else if (cycle)
{
// Try again from the position off the screen in the opposite direction to vkCode
// Consider all zones as available
zoneRects.resize(zoneObjects.size());
std::transform(zoneObjects.begin(), zoneObjects.end(), zoneRects.begin(), [](auto zone) { return zone->GetZoneRect(); });
windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, windowZoneRect, vkCode);
result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);

if (result < zoneRects.size())
{
MoveWindowIntoZoneByIndex(window, windowZone, freeZoneIndices[result]);
MoveWindowIntoZoneByIndex(window, windowZone, result);
return true;
}
}
Expand All @@ -424,6 +444,93 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND windowZone,
return false;
}

IFACEMETHODIMP_(bool)
ZoneSet::ExtendWindowByDirectionAndPosition(HWND window, HWND windowZone, DWORD vkCode) noexcept
{
if (m_zones.empty())
{
return false;
}

RECT windowRect, windowZoneRect;
if (GetWindowRect(window, &windowRect) && GetWindowRect(windowZone, &windowZoneRect))
{
auto zoneObjects = GetZones();
auto oldZones = GetZoneIndexSetFromWindow(window);
std::vector<bool> usedZoneIndices(zoneObjects.size(), false);
std::vector<RECT> zoneRects;
std::vector<size_t> 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 = m_windowFinalIndex.find(window);
if (finalIndexIt != m_windowFinalIndex.end())
{
usedZoneIndices[finalIndexIt->second] = true;
windowRect = zoneObjects[finalIndexIt->second]->GetZoneRect();
}
else
{
for (size_t idx : oldZones)
{
usedZoneIndices[idx] = true;
}
// Move to coordinates relative to windowZone
windowRect.top -= windowZoneRect.top;
windowRect.bottom -= windowZoneRect.top;
windowRect.left -= windowZoneRect.left;
windowRect.right -= windowZoneRect.left;
}

for (size_t i = 0; i < zoneObjects.size(); i++)
{
if (!usedZoneIndices[i])
{
zoneRects.emplace_back(zoneObjects[i]->GetZoneRect());
freeZoneIndices.emplace_back(i);
}
}

size_t result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (result < zoneRects.size())
{
size_t targetZone = freeZoneIndices[result];
std::vector<size_t> resultIndexSet;

// First time with selectManyZones = true for this window?
if (finalIndexIt == m_windowFinalIndex.end())
{
// Already zoned?
if (oldZones.size())
{
m_windowInitialIndexSet[window] = oldZones;
m_windowFinalIndex[window] = targetZone;
resultIndexSet = GetCombinedZoneRange(oldZones, { targetZone });
}
else
{
m_windowInitialIndexSet[window] = { targetZone };
m_windowFinalIndex[window] = targetZone;
resultIndexSet = { targetZone };
}
}
else
{
auto deletethis = m_windowInitialIndexSet[window];
m_windowFinalIndex[window] = targetZone;
resultIndexSet = GetCombinedZoneRange(m_windowInitialIndexSet[window], { targetZone });
}

m_inExtendWindow = true;
MoveWindowIntoZoneByIndexSet(window, windowZone, resultIndexSet);
m_inExtendWindow = false;
return true;
}
}

return false;
}

IFACEMETHODIMP_(void)
ZoneSet::MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept
{
Expand Down Expand Up @@ -779,6 +886,48 @@ void ZoneSet::StampWindow(HWND window, size_t bitmask) noexcept
SetProp(window, ZonedWindowProperties::PropertyMultipleZoneID, reinterpret_cast<HANDLE>(bitmask));
}

std::vector<size_t> ZoneSet::GetCombinedZoneRange(const std::vector<size_t>& initialZones, const std::vector<size_t>& finalZones) noexcept
{
std::vector<size_t> combinedZones, result;
std::set_union(begin(initialZones), end(initialZones), begin(finalZones), end(finalZones), std::back_inserter(combinedZones));

RECT boundingRect;
bool boundingRectEmpty = true;
auto zones = GetZones();

for (size_t zoneId : combinedZones)
{
RECT rect = zones[zoneId]->GetZoneRect();
if (boundingRectEmpty)
{
boundingRect = rect;
boundingRectEmpty = false;
}
else
{
boundingRect.left = min(boundingRect.left, rect.left);
boundingRect.top = min(boundingRect.top, rect.top);
boundingRect.right = max(boundingRect.right, rect.right);
boundingRect.bottom = max(boundingRect.bottom, rect.bottom);
}
}

if (!boundingRectEmpty)
{
for (size_t zoneId = 0; zoneId < zones.size(); zoneId++)
{
RECT rect = zones[zoneId]->GetZoneRect();
if (boundingRect.left <= rect.left && rect.right <= boundingRect.right &&
boundingRect.top <= rect.top && rect.bottom <= boundingRect.bottom)
{
result.push_back(zoneId);
}
}
}

return result;
}

winrt::com_ptr<IZoneSet> MakeZoneSet(ZoneSetConfig const& config) noexcept
{
return winrt::make_self<ZoneSet>(config);
Expand Down
22 changes: 22 additions & 0 deletions src/modules/fancyzones/lib/ZoneSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
* zones left in the zone layout in which window can move.
*/
IFACEMETHOD_(bool, MoveWindowIntoZoneByDirectionAndPosition)(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) = 0;
/**
* Extend or shrink the window to an adjacent zone based on direction (using CTRL+WIN+ALT + LEFT/RIGHT/UP/DOWN arrow), based on
* their on-screen position.
*
* @param window Handle of window which should be assigned to zone.
* @param zoneWindow The m_window of a ZoneWindow, it's a hidden window representing the
* current monitor desktop work area.
* @param vkCode Pressed arrow key.
*
* @returns Boolean indicating whether the window was rezoned. False could be returned when there are no more
* zones available in the given direction.
*/
IFACEMETHOD_(bool, ExtendWindowByDirectionAndPosition)(HWND window, HWND zoneWindow, DWORD vkCode) = 0;
/**
* Assign window to the zone based on cursor coordinates.
*
Expand Down Expand Up @@ -119,6 +132,15 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
* @returns Boolean indicating whether the zone is empty.
*/
IFACEMETHOD_(bool, IsZoneEmpty)(int zoneIndex) = 0;
/**
* Returns all zones spanned by the minimum bounding rectangle containing the two given zone index sets.
*
* @param initialZones The indices of the first chosen zone (the anchor).
* @param finalZones The indices of the last chosen zone (the current window position).
*
* @returns A vector indicating describing the chosen zone index set.
*/
IFACEMETHOD_(std::vector<size_t>, GetCombinedZoneRange)(const std::vector<size_t>& initialZones, const std::vector<size_t>& finalZones) = 0;
};

struct ZoneSetConfig
Expand Down

0 comments on commit 0e32edb

Please sign in to comment.