[FancyZones] Hold Ctrl to select any number of zones (#4850)

* Started work

* Did something, not yet sure that it works

* Sort of works

* Cleari highlighted zones when using Ctrl after leaving a monitor

* Remove unnecessary line

* Enhanced UX. Maybe refactor?

* Changed the logic behind zone selection when dragging

* Various fixups
This commit is contained in:
Ivan Stošić 2020-07-10 11:06:01 +02:00 committed by GitHub
parent b1d662a5b1
commit 412d80efe3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 203 additions and 109 deletions

View File

@ -18,7 +18,7 @@
#include <interface/win_hook_event_data.h>
#include <lib/SecondaryMouseButtonsHook.h>
#include <lib/ShiftKeyHook.h>
#include <lib/GenericKeyHook.h>
enum class DisplayChangeType
{
@ -41,7 +41,8 @@ public:
m_settings(settings),
m_mouseHook(std::bind(&FancyZones::OnMouseDown, this)),
m_shiftHook(std::bind(&FancyZones::OnShiftChangeState, this, std::placeholders::_1)),
m_windowMoveHandler(settings, &m_mouseHook, &m_shiftHook)
m_ctrlHook(std::bind(&FancyZones::OnCtrlChangeState, this, std::placeholders::_1)),
m_windowMoveHandler(settings, &m_mouseHook, &m_shiftHook, &m_ctrlHook)
{
m_settings->SetCallback(this);
}
@ -66,6 +67,13 @@ public:
PostMessageW(m_window, WM_PRIV_LOCATIONCHANGE, NULL, NULL);
}
void OnCtrlChangeState(bool state) noexcept
{
m_windowMoveHandler.OnCtrlChangeState(state);
PostMessageW(m_window, WM_PRIV_LOCATIONCHANGE, NULL, NULL);
}
void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen) noexcept
{
std::unique_lock writeLock(m_lock);
@ -244,6 +252,7 @@ private:
MonitorWorkAreaHandler m_workAreaHandler;
SecondaryMouseButtonsHook m_mouseHook;
ShiftKeyHook m_shiftHook;
CtrlKeyHook m_ctrlHook;
winrt::com_ptr<IFancyZonesSettings> m_settings{};
GUID m_previousDesktopId{}; // UUID of previously active virtual desktop.

View File

@ -100,13 +100,13 @@
<ItemGroup>
<ClInclude Include="FancyZones.h" />
<ClInclude Include="FancyZonesWinHookEventIDs.h" />
<ClInclude Include="GenericKeyHook.h" />
<ClInclude Include="JsonHelpers.h" />
<ClInclude Include="MonitorWorkAreaHandler.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="SecondaryMouseButtonsHook.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="ShiftKeyHook.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="util.h" />
<ClInclude Include="VirtualDesktopUtils.h" />
@ -118,6 +118,7 @@
<ItemGroup>
<ClCompile Include="FancyZones.cpp" />
<ClCompile Include="FancyZonesWinHookEventIDs.cpp" />
<ClCompile Include="GenericKeyHook.cpp" />
<ClCompile Include="JsonHelpers.cpp" />
<ClCompile Include="MonitorWorkAreaHandler.cpp" />
<ClCompile Include="pch.cpp">
@ -125,7 +126,6 @@
</ClCompile>
<ClCompile Include="SecondaryMouseButtonsHook.cpp" />
<ClCompile Include="Settings.cpp" />
<ClCompile Include="ShiftKeyHook.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="util.cpp" />
<ClCompile Include="VirtualDesktopUtils.cpp" />

View File

@ -60,7 +60,7 @@
<ClInclude Include="MonitorWorkAreaHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ShiftKeyHook.h">
<ClInclude Include="GenericKeyHook.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
@ -107,7 +107,7 @@
<ClCompile Include="MonitorWorkAreaHandler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ShiftKeyHook.cpp">
<ClCompile Include="GenericKeyHook.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>

View File

@ -0,0 +1,8 @@
#include "pch.h"
#include "GenericKeyHook.h"
HHOOK ShiftKeyHook::hHook{};
std::function<void(bool)> ShiftKeyHook::callback{};
HHOOK CtrlKeyHook::hHook{};
std::function<void(bool)> CtrlKeyHook::callback{};

View File

@ -0,0 +1,62 @@
#pragma once
#include <functional>
#include "pch.h"
template<int... keys>
class GenericKeyHook
{
public:
GenericKeyHook(std::function<void(bool)> extCallback)
{
callback = std::move(extCallback);
}
void enable()
{
#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED)
if (IsDebuggerPresent())
{
return;
}
#endif
if (!hHook)
{
hHook = SetWindowsHookEx(WH_KEYBOARD_LL, GenericKeyHookProc, GetModuleHandle(NULL), 0);
}
}
void disable()
{
if (hHook)
{
UnhookWindowsHookEx(hHook);
hHook = NULL;
callback(false);
}
}
private:
static HHOOK hHook;
static std::function<void(bool)> callback;
static LRESULT CALLBACK GenericKeyHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION)
{
if (wParam == WM_KEYDOWN || wParam == WM_KEYUP)
{
PKBDLLHOOKSTRUCT kbdHookStruct = (PKBDLLHOOKSTRUCT)lParam;
if (((kbdHookStruct->vkCode == keys) || ...))
{
callback(wParam == WM_KEYDOWN);
}
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
};
typedef GenericKeyHook<VK_LSHIFT, VK_RSHIFT> ShiftKeyHook;
typedef GenericKeyHook<VK_LCONTROL, VK_RCONTROL> CtrlKeyHook;

View File

@ -1,68 +0,0 @@
#include "pch.h"
#include "ShiftKeyHook.h"
#include <common/debug_control.h>
#pragma region public
HHOOK ShiftKeyHook::hHook = {};
std::function<void(bool)> ShiftKeyHook::callback = {};
ShiftKeyHook::ShiftKeyHook(std::function<void(bool)> extCallback)
{
callback = std::move(extCallback);
}
void ShiftKeyHook::enable()
{
#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED)
if (IsDebuggerPresent())
{
return;
}
#endif
if (!hHook)
{
hHook = SetWindowsHookEx(WH_KEYBOARD_LL, ShiftKeyHookProc, GetModuleHandle(NULL), 0);
}
}
void ShiftKeyHook::disable()
{
if (hHook)
{
UnhookWindowsHookEx(hHook);
hHook = NULL;
callback(false);
}
}
#pragma endregion
#pragma region private
LRESULT CALLBACK ShiftKeyHook::ShiftKeyHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION)
{
if (wParam == WM_KEYDOWN)
{
PKBDLLHOOKSTRUCT kbdHookStruct = (PKBDLLHOOKSTRUCT)lParam;
if (kbdHookStruct->vkCode == VK_LSHIFT || kbdHookStruct->vkCode == VK_RSHIFT)
{
callback(true);
}
}
else if (wParam == WM_KEYUP)
{
PKBDLLHOOKSTRUCT kbdHookStruct = (PKBDLLHOOKSTRUCT)lParam;
if (kbdHookStruct->vkCode == VK_LSHIFT || kbdHookStruct->vkCode == VK_RSHIFT)
{
callback(false);
}
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
#pragma endregion

View File

@ -1,16 +0,0 @@
#pragma once
#include <functional>
class ShiftKeyHook
{
public:
ShiftKeyHook(std::function<void(bool)>);
void enable();
void disable();
private:
static HHOOK hHook;
static std::function<void(bool)> callback;
static LRESULT CALLBACK ShiftKeyHookProc(int, WPARAM, LPARAM);
};

View File

@ -11,7 +11,7 @@
#include "lib/util.h"
#include "VirtualDesktopUtils.h"
#include "lib/SecondaryMouseButtonsHook.h"
#include <lib/ShiftKeyHook.h>
#include "lib/GenericKeyHook.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
@ -48,10 +48,13 @@ namespace WindowMoveHandlerUtils
class WindowMoveHandlerPrivate
{
public:
WindowMoveHandlerPrivate(const winrt::com_ptr<IFancyZonesSettings>& settings, SecondaryMouseButtonsHook* mouseHook, ShiftKeyHook* shiftHook) :
WindowMoveHandlerPrivate(const winrt::com_ptr<IFancyZonesSettings>& settings, SecondaryMouseButtonsHook* mouseHook, ShiftKeyHook* shiftHook, CtrlKeyHook* ctrlHook) :
m_settings(settings),
m_mouseHook(mouseHook),
m_shiftHook(shiftHook){};
m_shiftHook(shiftHook),
m_ctrlHook(ctrlHook)
{
}
bool IsDragEnabled() const noexcept
{
@ -65,6 +68,7 @@ public:
void OnMouseDown() noexcept;
void OnShiftChangeState(bool state) noexcept;
void OnCtrlChangeState(bool state) noexcept;
void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept;
void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept;
@ -80,6 +84,7 @@ private:
winrt::com_ptr<IFancyZonesSettings> m_settings{};
SecondaryMouseButtonsHook* m_mouseHook{};
ShiftKeyHook* m_shiftHook{};
CtrlKeyHook* m_ctrlHook{};
HWND m_windowMoveSize{}; // The window that is being moved/sized
bool m_inMoveSize{}; // Whether or not a move/size operation is currently active
@ -87,10 +92,11 @@ private:
bool m_dragEnabled{}; // True if we should be showing zone hints while dragging
bool m_secondaryMouseButtonState{}; // True when secondary mouse button was clicked after window was moved
bool m_shiftKeyState{}; // True when shift key was pressed after window was moved
bool m_ctrlKeyState{}; // True when ctrl key was pressed after window was moved
};
WindowMoveHandler::WindowMoveHandler(const winrt::com_ptr<IFancyZonesSettings>& settings, SecondaryMouseButtonsHook* mouseHook, ShiftKeyHook* shiftHook) :
pimpl(new WindowMoveHandlerPrivate(settings, mouseHook, shiftHook)) {}
WindowMoveHandler::WindowMoveHandler(const winrt::com_ptr<IFancyZonesSettings>& settings, SecondaryMouseButtonsHook* mouseHook, ShiftKeyHook* shiftHook, CtrlKeyHook* ctrlHook) :
pimpl(new WindowMoveHandlerPrivate(settings, mouseHook, shiftHook, ctrlHook)) {}
WindowMoveHandler::~WindowMoveHandler()
{
@ -117,6 +123,11 @@ void WindowMoveHandler::OnShiftChangeState(bool state) noexcept
pimpl->OnShiftChangeState(state);
}
void WindowMoveHandler::OnCtrlChangeState(bool state) noexcept
{
pimpl->OnCtrlChangeState(state);
}
void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept
{
pimpl->MoveSizeStart(window, monitor, ptScreen, zoneWindowMap);
@ -152,6 +163,11 @@ void WindowMoveHandlerPrivate::OnShiftChangeState(bool state) noexcept
m_shiftKeyState = state;
}
void WindowMoveHandlerPrivate::OnCtrlChangeState(bool state) noexcept
{
m_ctrlKeyState = state;
}
void WindowMoveHandlerPrivate::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept
{
if (!IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent())
@ -175,6 +191,7 @@ void WindowMoveHandlerPrivate::MoveSizeStart(HWND window, HMONITOR monitor, POIN
}
m_shiftHook->enable();
m_ctrlHook->enable();
// This updates m_dragEnabled depending on if the shift key is being held down.
UpdateDragState(window);
@ -247,6 +264,7 @@ void WindowMoveHandlerPrivate::MoveSizeUpdate(HMONITOR monitor, POINT const& ptS
{
// The drag has moved to a different monitor.
m_zoneWindowMoveSize->RestoreOriginalTransparency();
m_zoneWindowMoveSize->ClearSelectedZones();
if (!m_settings->GetSettings()->showZonesOnAllMonitors)
{
@ -258,7 +276,7 @@ void WindowMoveHandlerPrivate::MoveSizeUpdate(HMONITOR monitor, POINT const& ptS
for (auto [keyMonitor, zoneWindow] : zoneWindowMap)
{
zoneWindow->MoveSizeUpdate(ptScreen, m_dragEnabled);
zoneWindow->MoveSizeUpdate(ptScreen, m_dragEnabled, m_ctrlKeyState);
}
}
}
@ -281,6 +299,7 @@ void WindowMoveHandlerPrivate::MoveSizeEnd(HWND window, POINT const& ptScreen, c
m_mouseHook->disable();
m_shiftHook->disable();
m_ctrlHook->disable();
m_inMoveSize = false;
m_dragEnabled = false;

View File

@ -1,14 +1,15 @@
#pragma once
#include "SecondaryMouseButtonsHook.h"
#include "GenericKeyHook.h"
interface IFancyZonesSettings;
interface IZoneWindow;
class SecondaryMouseButtonsHook;
class ShiftKeyHook;
class WindowMoveHandler
{
public:
WindowMoveHandler(const winrt::com_ptr<IFancyZonesSettings>& settings, SecondaryMouseButtonsHook* mouseHook, ShiftKeyHook* shiftHook);
WindowMoveHandler(const winrt::com_ptr<IFancyZonesSettings>& settings, SecondaryMouseButtonsHook* mouseHook, ShiftKeyHook* shiftHook, CtrlKeyHook* ctrlHook);
~WindowMoveHandler();
bool InMoveSize() const noexcept;
@ -16,6 +17,7 @@ public:
void OnMouseDown() noexcept;
void OnShiftChangeState(bool state) noexcept; //True for shift down event false for shift up
void OnCtrlChangeState(bool state) noexcept; //True for ctrl down event false for ctrl up
void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept;
void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept;

View File

@ -212,7 +212,7 @@ public:
bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId, bool flashZones);
IFACEMETHODIMP MoveSizeEnter(HWND window) noexcept;
IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept;
IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept;
IFACEMETHODIMP MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept;
IFACEMETHODIMP_(void)
RestoreOriginalTransparency() noexcept;
@ -238,6 +238,8 @@ public:
HideZoneWindow() noexcept;
IFACEMETHODIMP_(void)
UpdateActiveZoneSet() noexcept;
IFACEMETHODIMP_(void)
ClearSelectedZones() noexcept;
protected:
static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
@ -263,6 +265,7 @@ private:
bool m_flashMode{};
winrt::com_ptr<IZoneSet> m_activeZoneSet;
std::vector<winrt::com_ptr<IZoneSet>> m_zoneSets;
std::vector<int> m_initialHighlightZone;
std::vector<int> m_highlightZone;
WPARAM m_keyLast{};
size_t m_keyCycle{};
@ -368,11 +371,12 @@ IFACEMETHODIMP ZoneWindow::MoveSizeEnter(HWND window) noexcept
m_windowMoveSize = window;
m_drawHints = true;
m_highlightZone = {};
m_initialHighlightZone = {};
ShowZoneWindow();
return S_OK;
}
IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept
IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept
{
bool redraw = false;
POINT ptClient = ptScreen;
@ -381,6 +385,61 @@ IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnable
if (dragEnabled)
{
auto highlightZone = ZonesFromPoint(ptClient);
if (selectManyZones)
{
if (m_initialHighlightZone.empty())
{
// first time
m_initialHighlightZone = highlightZone;
}
else
{
std::vector<int> newHighlightZone;
std::set_union(begin(highlightZone), end(highlightZone), begin(m_initialHighlightZone), end(m_initialHighlightZone), std::back_inserter(newHighlightZone));
RECT boundingRect;
bool boundingRectEmpty = true;
auto zones = m_activeZoneSet->GetZones();
for (int zoneId : newHighlightZone)
{
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);
}
}
highlightZone.clear();
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)
{
highlightZone.push_back(static_cast<int>(zoneId));
}
}
}
}
}
else
{
m_initialHighlightZone = {};
}
redraw = (highlightZone != m_highlightZone);
m_highlightZone = std::move(highlightZone);
}
@ -410,7 +469,7 @@ IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexc
{
POINT ptClient = ptScreen;
MapWindowPoints(nullptr, m_window.get(), &ptClient, 1);
m_activeZoneSet->MoveWindowIntoZoneByPoint(window, m_window.get(), ptClient);
m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window.get(), m_highlightZone);
SaveWindowProcessToZoneIndex(window);
}
@ -545,6 +604,16 @@ ZoneWindow::UpdateActiveZoneSet() noexcept
CalculateZoneSet();
}
IFACEMETHODIMP_(void)
ZoneWindow::ClearSelectedZones() noexcept
{
if (m_highlightZone.size())
{
m_highlightZone.clear();
InvalidateRect(m_window.get(), nullptr, true);
}
}
#pragma region private
void ZoneWindow::InitializeZoneSets(const std::wstring& parentUniqueId) noexcept

View File

@ -29,11 +29,16 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
* A window has changed location, shape, or size. Track down window position and give zone layout
* hints if dragging functionality is enabled.
*
* @param ptScreen Cursor coordinates.
* @param dragEnabled Boolean indicating is giving hints about active zone layout enabled.
* Hints are given while dragging window while holding SHIFT key.
* @param ptScreen Cursor coordinates.
* @param dragEnabled Boolean indicating is giving hints about active zone layout enabled.
* Hints are given while dragging window while holding SHIFT key.
* @param selectManyZones When this parameter is true, the set of highlighted zones is computed
by finding the minimum bounding rectangle of the zone(s) from which the
user started dragging and the zone(s) above which the user is hovering
at the moment this function is called. Otherwise, highlight only the zone(s)
above which the user is currently hovering.
*/
IFACEMETHOD(MoveSizeUpdate)(POINT const& ptScreen, bool dragEnabled) = 0;
IFACEMETHOD(MoveSizeUpdate)(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) = 0;
/**
* The movement or resizing of a window has finished. Assign window to the zone of it
* is dropped within zone borders.
@ -102,6 +107,10 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
* Update currently active zone layout for this work area.
*/
IFACEMETHOD_(void, UpdateActiveZoneSet)() = 0;
/**
* Clear the selected zones when this ZoneWindow loses focus.
*/
IFACEMETHOD_(void, ClearSelectedZones)() = 0;
};
winrt::com_ptr<IZoneWindow> MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor,

View File

@ -467,7 +467,7 @@ namespace FancyZonesUnitTests
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), {}, false);
const auto expected = S_OK;
const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ 0, 0 }, true);
const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ 0, 0 }, true, false);
Assert::AreEqual(expected, actual);
}
@ -477,7 +477,7 @@ namespace FancyZonesUnitTests
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), {}, false);
const auto expected = S_OK;
const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ -10, -10 }, true);
const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ -10, -10 }, true, false);
Assert::AreEqual(expected, actual);
}
@ -487,7 +487,7 @@ namespace FancyZonesUnitTests
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), {}, false);
const auto expected = S_OK;
const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ m_monitorInfo.rcMonitor.right + 1, m_monitorInfo.rcMonitor.bottom + 1 }, true);
const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ m_monitorInfo.rcMonitor.right + 1, m_monitorInfo.rcMonitor.bottom + 1 }, true, false);
Assert::AreEqual(expected, actual);
}