#include "pch.h" #include #include "FancyZonesData.h" #include "FancyZonesDataTypes.h" #include "ZoneWindow.h" #include "ZoneWindowDrawing.h" #include "trace.h" #include "util.h" #include "on_thread_executor.h" #include "Settings.h" #include "CallTracer.h" #include #include #include #include // Non-Localizable strings namespace NonLocalizable { const wchar_t ToolWindowClassName[] = L"SuperFancyZones_ZoneWindow"; } using namespace FancyZonesUtils; struct ZoneWindow; namespace { // The reason for using this class is the need to call ShowWindow(window, SW_SHOWNORMAL); on each // newly created window for it to be displayed properly. The call sometimes has side effects when // a fullscreen app is running, and happens when the resolution change event is triggered // (e.g. when running some games). // This class will serve as a pool of windows for which this call was already done. class WindowPool { std::vector m_pool; std::mutex m_mutex; HWND ExtractWindow() { _TRACER_; std::unique_lock lock(m_mutex); if (m_pool.empty()) { return NULL; } HWND window = m_pool.back(); m_pool.pop_back(); return window; } public: HWND NewZoneWindow(Rect position, HINSTANCE hinstance, ZoneWindow* owner) { HWND windowFromPool = ExtractWindow(); if (windowFromPool == NULL) { HWND window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, L"", WS_POPUP, position.left(), position.top(), position.width(), position.height(), nullptr, nullptr, hinstance, owner); Logger::info("Creating new zone window, hWnd = {}", (void*)window); MakeWindowTransparent(window); // According to ShowWindow docs, we must call it with SW_SHOWNORMAL the first time ShowWindow(window, SW_SHOWNORMAL); ShowWindow(window, SW_HIDE); return window; } else { Logger::info("Reusing zone window from pool, hWnd = {}", (void*)windowFromPool); SetWindowLongPtrW(windowFromPool, GWLP_USERDATA, reinterpret_cast(owner)); MoveWindow(windowFromPool, position.left(), position.top(), position.width(), position.height(), TRUE); return windowFromPool; } } void FreeZoneWindow(HWND window) { _TRACER_; Logger::info("Freeing zone window, hWnd = {}", (void*)window); SetWindowLongPtrW(window, GWLP_USERDATA, 0); ShowWindow(window, SW_HIDE); std::unique_lock lock(m_mutex); m_pool.push_back(window); } ~WindowPool() { for (HWND window : m_pool) { DestroyWindow(window); } } }; WindowPool windowPool; } struct ZoneWindow : public winrt::implements { public: ZoneWindow(HINSTANCE hinstance); ~ZoneWindow(); bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId); IFACEMETHODIMP MoveSizeEnter(HWND window) noexcept; IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept; IFACEMETHODIMP MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept; IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndex(HWND window, size_t index) noexcept; IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndexSet(HWND window, const std::vector& indexSet) noexcept; IFACEMETHODIMP_(bool) MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept; IFACEMETHODIMP_(bool) MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) noexcept; IFACEMETHODIMP_(bool) ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept; IFACEMETHODIMP_(std::wstring) UniqueId() noexcept { return { m_uniqueId }; } IFACEMETHODIMP_(void) SaveWindowProcessToZoneIndex(HWND window) noexcept; IFACEMETHODIMP_(IZoneSet*) ActiveZoneSet() noexcept { return m_activeZoneSet.get(); } IFACEMETHODIMP_(void) ShowZoneWindow() noexcept; IFACEMETHODIMP_(void) HideZoneWindow() noexcept; IFACEMETHODIMP_(void) UpdateActiveZoneSet() noexcept; IFACEMETHODIMP_(void) ClearSelectedZones() noexcept; IFACEMETHODIMP_(void) FlashZones() noexcept; protected: static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept; private: void InitializeZoneSets(const std::wstring& parentUniqueId) noexcept; void CalculateZoneSet() noexcept; void UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept; LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept; std::vector ZonesFromPoint(POINT pt) noexcept; void SetAsTopmostWindow() noexcept; winrt::com_ptr m_host; HMONITOR m_monitor{}; std::wstring m_uniqueId; // Parsed deviceId + resolution + virtualDesktopId HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area. HWND m_windowMoveSize{}; winrt::com_ptr m_activeZoneSet; std::vector> m_zoneSets; std::vector m_initialHighlightZone; std::vector m_highlightZone; WPARAM m_keyLast{}; size_t m_keyCycle{}; std::unique_ptr m_zoneWindowDrawing; }; ZoneWindow::ZoneWindow(HINSTANCE hinstance) { WNDCLASSEXW wcex{}; wcex.cbSize = sizeof(WNDCLASSEX); wcex.lpfnWndProc = s_WndProc; wcex.hInstance = hinstance; wcex.lpszClassName = NonLocalizable::ToolWindowClassName; wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); RegisterClassExW(&wcex); } ZoneWindow::~ZoneWindow() { windowPool.FreeZoneWindow(m_window); } bool ZoneWindow::Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId) { m_host.copy_from(host); Rect workAreaRect; m_monitor = monitor; if (monitor) { MONITORINFO mi{}; mi.cbSize = sizeof(mi); if (!GetMonitorInfoW(monitor, &mi)) { return false; } workAreaRect = Rect(mi.rcWork); } else { workAreaRect = GetAllMonitorsCombinedRect<&MONITORINFO::rcWork>(); } m_uniqueId = uniqueId; InitializeZoneSets(parentUniqueId); m_window = windowPool.NewZoneWindow(workAreaRect, hinstance, this); if (!m_window) { return false; } m_zoneWindowDrawing = std::make_unique(m_window); return true; } IFACEMETHODIMP ZoneWindow::MoveSizeEnter(HWND window) noexcept { m_windowMoveSize = window; m_highlightZone = {}; m_initialHighlightZone = {}; ShowZoneWindow(); return S_OK; } IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept { bool redraw = false; POINT ptClient = ptScreen; MapWindowPoints(nullptr, m_window, &ptClient, 1); if (dragEnabled) { auto highlightZone = ZonesFromPoint(ptClient); if (selectManyZones) { if (m_initialHighlightZone.empty()) { // first time m_initialHighlightZone = highlightZone; } else { highlightZone = m_activeZoneSet->GetCombinedZoneRange(m_initialHighlightZone, highlightZone); } } else { m_initialHighlightZone = {}; } redraw = (highlightZone != m_highlightZone); m_highlightZone = std::move(highlightZone); } else if (m_highlightZone.size()) { m_highlightZone = {}; redraw = true; } if (redraw) { m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); } return S_OK; } IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept { if (m_windowMoveSize != window) { return E_INVALIDARG; } if (m_activeZoneSet) { POINT ptClient = ptScreen; MapWindowPoints(nullptr, m_window, &ptClient, 1); m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone); if (FancyZonesUtils::HasNoVisibleOwner(window)) { SaveWindowProcessToZoneIndex(window); } } Trace::ZoneWindow::MoveSizeEnd(m_activeZoneSet); HideZoneWindow(); m_windowMoveSize = nullptr; return S_OK; } IFACEMETHODIMP_(void) ZoneWindow::MoveWindowIntoZoneByIndex(HWND window, size_t index) noexcept { MoveWindowIntoZoneByIndexSet(window, { index }); } IFACEMETHODIMP_(void) ZoneWindow::MoveWindowIntoZoneByIndexSet(HWND window, const std::vector& indexSet) noexcept { if (m_activeZoneSet) { m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet); } } IFACEMETHODIMP_(bool) ZoneWindow::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept { if (m_activeZoneSet) { if (m_activeZoneSet->MoveWindowIntoZoneByDirectionAndIndex(window, m_window, vkCode, cycle)) { if (FancyZonesUtils::HasNoVisibleOwner(window)) { SaveWindowProcessToZoneIndex(window); } return true; } } return false; } IFACEMETHODIMP_(bool) ZoneWindow::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) noexcept { if (m_activeZoneSet) { if (m_activeZoneSet->MoveWindowIntoZoneByDirectionAndPosition(window, m_window, vkCode, cycle)) { SaveWindowProcessToZoneIndex(window); return true; } } return false; } IFACEMETHODIMP_(bool) ZoneWindow::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept { if (m_activeZoneSet) { if (m_activeZoneSet->ExtendWindowByDirectionAndPosition(window, m_window, vkCode)) { SaveWindowProcessToZoneIndex(window); return true; } } return false; } IFACEMETHODIMP_(void) ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept { if (m_activeZoneSet) { auto zoneIndexSet = m_activeZoneSet->GetZoneIndexSetFromWindow(window); if (zoneIndexSet.size()) { OLECHAR* guidString; if (StringFromCLSID(m_activeZoneSet->Id(), &guidString) == S_OK) { FancyZonesDataInstance().SetAppLastZones(window, m_uniqueId, guidString, zoneIndexSet); } CoTaskMemFree(guidString); } } } IFACEMETHODIMP_(void) ZoneWindow::ShowZoneWindow() noexcept { if (m_window) { SetAsTopmostWindow(); m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); m_zoneWindowDrawing->Show(); } } IFACEMETHODIMP_(void) ZoneWindow::HideZoneWindow() noexcept { if (m_window) { m_zoneWindowDrawing->Hide(); m_keyLast = 0; m_windowMoveSize = nullptr; m_highlightZone = {}; } } IFACEMETHODIMP_(void) ZoneWindow::UpdateActiveZoneSet() noexcept { CalculateZoneSet(); if (m_window) { m_highlightZone.clear(); m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); } } IFACEMETHODIMP_(void) ZoneWindow::ClearSelectedZones() noexcept { if (m_highlightZone.size()) { m_highlightZone.clear(); m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); } } IFACEMETHODIMP_(void) ZoneWindow::FlashZones() noexcept { if (m_window) { SetAsTopmostWindow(); m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), {}, m_host); m_zoneWindowDrawing->Flash(); } } #pragma region private void ZoneWindow::InitializeZoneSets(const std::wstring& parentUniqueId) noexcept { bool deviceAdded = FancyZonesDataInstance().AddDevice(m_uniqueId); // If the device has been added, check if it should inherit the parent's layout if (deviceAdded && !parentUniqueId.empty()) { FancyZonesDataInstance().CloneDeviceInfo(parentUniqueId, m_uniqueId); } CalculateZoneSet(); } void ZoneWindow::CalculateZoneSet() noexcept { const auto& fancyZonesData = FancyZonesDataInstance(); const auto deviceInfoData = fancyZonesData.FindDeviceInfo(m_uniqueId); if (!deviceInfoData.has_value()) { return; } const auto& activeZoneSet = deviceInfoData->activeZoneSet; if (activeZoneSet.uuid.empty()) { return; } GUID zoneSetId; if (SUCCEEDED_LOG(CLSIDFromString(activeZoneSet.uuid.c_str(), &zoneSetId))) { int sensitivityRadius = deviceInfoData->sensitivityRadius; auto zoneSet = MakeZoneSet(ZoneSetConfig( zoneSetId, activeZoneSet.type, m_monitor, sensitivityRadius, m_host->GetOverlappingZonesAlgorithm())); RECT workArea; if (m_monitor) { MONITORINFO monitorInfo{}; monitorInfo.cbSize = sizeof(monitorInfo); if (GetMonitorInfoW(m_monitor, &monitorInfo)) { workArea = monitorInfo.rcWork; } else { return; } } else { workArea = GetAllMonitorsCombinedRect<&MONITORINFO::rcWork>(); } bool showSpacing = deviceInfoData->showSpacing; int spacing = showSpacing ? deviceInfoData->spacing : 0; int zoneCount = deviceInfoData->zoneCount; zoneSet->CalculateZones(workArea, zoneCount, spacing); UpdateActiveZoneSet(zoneSet.get()); } } void ZoneWindow::UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept { m_activeZoneSet.copy_from(zoneSet); if (m_activeZoneSet) { wil::unique_cotaskmem_string zoneSetId; if (SUCCEEDED_LOG(StringFromCLSID(m_activeZoneSet->Id(), &zoneSetId))) { FancyZonesDataTypes::ZoneSetData data{ .uuid = zoneSetId.get(), .type = m_activeZoneSet->LayoutType() }; FancyZonesDataInstance().SetActiveZoneSet(m_uniqueId, data); } } } LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept { switch (message) { case WM_NCDESTROY: { ::DefWindowProc(m_window, message, wparam, lparam); SetWindowLongPtr(m_window, GWLP_USERDATA, 0); } break; case WM_ERASEBKGND: return 1; default: { return DefWindowProc(m_window, message, wparam, lparam); } } return 0; } std::vector ZoneWindow::ZonesFromPoint(POINT pt) noexcept { if (m_activeZoneSet) { return m_activeZoneSet->ZonesFromPoint(pt); } return {}; } void ZoneWindow::SetAsTopmostWindow() noexcept { if (!m_window) { return; } UINT flags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE; HWND windowInsertAfter = m_windowMoveSize; if (windowInsertAfter == nullptr) { windowInsertAfter = HWND_TOPMOST; } SetWindowPos(m_window, windowInsertAfter, 0, 0, 0, 0, flags); } #pragma endregion LRESULT CALLBACK ZoneWindow::s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept { auto thisRef = reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); if ((thisRef == nullptr) && (message == WM_CREATE)) { auto createStruct = reinterpret_cast(lparam); thisRef = reinterpret_cast(createStruct->lpCreateParams); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(thisRef)); } return (thisRef != nullptr) ? thisRef->WndProc(message, wparam, lparam) : DefWindowProc(window, message, wparam, lparam); } winrt::com_ptr MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId) noexcept { auto self = winrt::make_self(hinstance); if (self->Init(host, hinstance, monitor, uniqueId, parentUniqueId)) { return self; } return nullptr; }