diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 073a291b7f..d8b547c7d6 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -488,6 +488,7 @@ DQTYPE DRAWFRAME drawingcolor dreamsofameaningfullife +DRect drivedetectionwarning dshow dst @@ -1194,6 +1195,7 @@ mdpreviewhandler MEDIASUBTYPE mediatype Melman +memcmp memcpy memset MENUITEMINFO @@ -2103,6 +2105,7 @@ uap udit Udp uefi +UHash UIA Uid uint diff --git a/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.cpp b/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.cpp index 3c369799e5..511dc85f24 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.cpp +++ b/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.cpp @@ -1,6 +1,18 @@ #include "pch.h" + #include "FrameDrawer.h" +namespace +{ + size_t D2DRectUHash(D2D1_SIZE_U rect) + { + using pod_repr_t = uint64_t; + static_assert(sizeof(D2D1_SIZE_U) == sizeof(pod_repr_t)); + std::hash hasher{}; + return hasher(*reinterpret_cast(&rect)); + } +} + std::unique_ptr FrameDrawer::Create(HWND window) { auto self = std::make_unique(window); @@ -12,63 +24,55 @@ std::unique_ptr FrameDrawer::Create(HWND window) return nullptr; } -FrameDrawer::FrameDrawer(FrameDrawer&& other) : - m_window(other.m_window), - m_renderTarget(std::move(other.m_renderTarget)), - m_sceneRect(std::move(other.m_sceneRect)), - m_renderThread(std::move(m_renderThread)) -{ -} - FrameDrawer::FrameDrawer(HWND window) : - m_window(window), m_renderTarget(nullptr) + m_window(window) { } -FrameDrawer::~FrameDrawer() +bool FrameDrawer::CreateRenderTargets(const RECT& clientRect) { - m_abortThread = true; - m_renderThread.join(); + HRESULT hr; - if (m_renderTarget) + constexpr float DPI = 96.f; // Always using the default in DPI-aware mode + const auto renderTargetProperties = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), + DPI, + DPI); + + const auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); + const auto rectHash = D2DRectUHash(renderTargetSize); + if (m_renderTarget && rectHash == m_renderTargetSizeHash) { - m_renderTarget->Release(); + // Already at the desired size -> do nothing + return true; } + + m_renderTarget = nullptr; + + const auto hwndRenderTargetProperties = D2D1::HwndRenderTargetProperties(m_window, renderTargetSize, D2D1_PRESENT_OPTIONS_NONE); + + hr = GetD2DFactory()->CreateHwndRenderTarget(renderTargetProperties, hwndRenderTargetProperties, m_renderTarget.put()); + + if (!SUCCEEDED(hr) || !m_renderTarget) + { + return false; + } + m_renderTargetSizeHash = rectHash; + + return true; } bool FrameDrawer::Init() { RECT clientRect; - // Obtain the size of the drawing area. if (!GetClientRect(m_window, &clientRect)) { return false; } - HRESULT hr; - - // Create a Direct2D render target - // We should always use the DPI value of 96 since we're running in DPI aware mode - auto renderTargetProperties = D2D1::RenderTargetProperties( - D2D1_RENDER_TARGET_TYPE_DEFAULT, - D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), - 96.f, - 96.f); - - auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); - auto hwndRenderTargetProperties = D2D1::HwndRenderTargetProperties(m_window, renderTargetSize); - - hr = GetD2DFactory()->CreateHwndRenderTarget(renderTargetProperties, hwndRenderTargetProperties, &m_renderTarget); - - if (!SUCCEEDED(hr)) - { - return false; - } - - m_renderThread = std::thread([this]() { RenderLoop(); }); - - return true; + return CreateRenderTargets(clientRect); } void FrameDrawer::Hide() @@ -79,19 +83,64 @@ void FrameDrawer::Hide() void FrameDrawer::Show() { ShowWindow(m_window, SW_SHOWNA); + Render(); } void FrameDrawer::SetBorderRect(RECT windowRect, COLORREF color, float thickness) { - std::unique_lock lock(m_mutex); - - auto borderColor = ConvertColor(color); - - m_sceneRect = DrawableRect{ + const auto newSceneRect = DrawableRect{ .rect = ConvertRect(windowRect), - .borderColor = borderColor, + .borderColor = ConvertColor(color), .thickness = thickness }; + + const bool colorUpdated = std::memcmp(&m_sceneRect.borderColor, &newSceneRect.borderColor, sizeof(newSceneRect.borderColor)); + const bool thicknessUpdated = m_sceneRect.thickness != newSceneRect.thickness; + const bool needsRedraw = colorUpdated || thicknessUpdated; + + RECT clientRect; + + if (!GetClientRect(m_window, &clientRect)) + { + return; + } + + m_sceneRect = newSceneRect; + + const auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); + + const auto rectHash = D2DRectUHash(renderTargetSize); + + const bool atTheDesiredSize = (rectHash == m_renderTargetSizeHash) && m_renderTarget; + if (!atTheDesiredSize) + { + const bool resizeOk = m_renderTarget && SUCCEEDED(m_renderTarget->Resize(renderTargetSize)); + if (!resizeOk) + { + if (!CreateRenderTargets(clientRect)) + { + Logger::error(L"Failed to create render targets"); + } + } + else + { + m_renderTargetSizeHash = rectHash; + } + } + + if (colorUpdated) + { + m_borderBrush = nullptr; + if (m_renderTarget) + { + m_renderTarget->CreateSolidColorBrush(m_sceneRect.borderColor, m_borderBrush.put()); + } + } + + if (!atTheDesiredSize || needsRedraw) + { + Render(); + } } ID2D1Factory* FrameDrawer::GetD2DFactory() @@ -127,46 +176,18 @@ D2D1_RECT_F FrameDrawer::ConvertRect(RECT rect) return D2D1::RectF((float)rect.left, (float)rect.top, (float)rect.right, (float)rect.bottom); } -FrameDrawer::RenderResult FrameDrawer::Render() +void FrameDrawer::Render() { - std::unique_lock lock(m_mutex); - if (!m_renderTarget) - { - return RenderResult::Failed; - } - + return; m_renderTarget->BeginDraw(); - // Draw backdrop m_renderTarget->Clear(D2D1::ColorF(0.f, 0.f, 0.f, 0.f)); - ID2D1SolidColorBrush* borderBrush = nullptr; - m_renderTarget->CreateSolidColorBrush(m_sceneRect.borderColor, &borderBrush); - - if (borderBrush) + if (m_borderBrush) { - m_renderTarget->DrawRectangle(m_sceneRect.rect, borderBrush, m_sceneRect.thickness); - borderBrush->Release(); + m_renderTarget->DrawRectangle(m_sceneRect.rect, m_borderBrush.get(), m_sceneRect.thickness); } - // The lock must be released here, as EndDraw() will wait for vertical sync - lock.unlock(); - m_renderTarget->EndDraw(); - return RenderResult::Ok; -} - -void FrameDrawer::RenderLoop() -{ - while (!m_abortThread) - { - auto result = Render(); - if (result == RenderResult::Failed) - { - Logger::error("Render failed"); - Hide(); - m_abortThread = true; - } - } -} +} \ No newline at end of file diff --git a/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.h b/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.h index 47e2678fb7..b396938b67 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.h +++ b/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.h @@ -1,7 +1,9 @@ #pragma once #include + #include +#include #include class FrameDrawer @@ -10,8 +12,7 @@ public: static std::unique_ptr Create(HWND window); FrameDrawer(HWND window); - FrameDrawer(FrameDrawer&& other); - ~FrameDrawer(); + FrameDrawer(FrameDrawer&& other) = default; bool Init(); @@ -20,6 +21,8 @@ public: void SetBorderRect(RECT windowRect, COLORREF color, float thickness); private: + bool CreateRenderTargets(const RECT& clientRect); + struct DrawableRect { D2D1_RECT_F rect; @@ -27,25 +30,15 @@ private: float thickness; }; - enum struct RenderResult - { - Ok, - Failed, - }; - static ID2D1Factory* GetD2DFactory(); static IDWriteFactory* GetWriteFactory(); static D2D1_COLOR_F ConvertColor(COLORREF color); static D2D1_RECT_F ConvertRect(RECT rect); - RenderResult Render(); - void RenderLoop(); + void Render(); HWND m_window = nullptr; - ID2D1HwndRenderTarget* m_renderTarget = nullptr; - - std::mutex m_mutex; - DrawableRect m_sceneRect; - - std::atomic m_abortThread = false; - std::thread m_renderThread; + size_t m_renderTargetSizeHash = {}; + winrt::com_ptr m_renderTarget; + winrt::com_ptr m_borderBrush; + DrawableRect m_sceneRect = {}; }; \ No newline at end of file diff --git a/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.cpp b/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.cpp index 9f5a7036fe..537b52ce1d 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.cpp +++ b/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.cpp @@ -31,18 +31,10 @@ std::optional GetFrameRect(HWND window) } WindowBorder::WindowBorder(HWND window) : - SettingsObserver({SettingId::FrameColor, SettingId::FrameThickness, SettingId::FrameAccentColor }), - m_window(nullptr), - m_trackingWindow(window), - m_frameDrawer(nullptr) -{ -} - -WindowBorder::WindowBorder(WindowBorder&& other) : SettingsObserver({ SettingId::FrameColor, SettingId::FrameThickness, SettingId::FrameAccentColor }), - m_window(other.m_window), - m_trackingWindow(other.m_trackingWindow), - m_frameDrawer(std::move(other.m_frameDrawer)) + m_window(nullptr), + m_trackingWindow(window), + m_frameDrawer(nullptr) { } @@ -72,6 +64,12 @@ std::unique_ptr WindowBorder::Create(HWND window, HINSTANCE hinsta return nullptr; } +namespace +{ + constexpr uint32_t REFRESH_BORDER_TIMER_ID = 123; + constexpr uint32_t REFRESH_BORDER_INTERVAL = 100; +} + bool WindowBorder::Init(HINSTANCE hinstance) { if (!m_trackingWindow) @@ -136,6 +134,8 @@ bool WindowBorder::Init(HINSTANCE hinstance) UpdateBorderProperties(); m_frameDrawer->Show(); + m_timer_id = SetTimer(m_window, REFRESH_BORDER_TIMER_ID, REFRESH_BORDER_INTERVAL, nullptr); + return true; } @@ -154,7 +154,7 @@ void WindowBorder::UpdateBorderPosition() const } RECT rect = rectOpt.value(); - SetWindowPos(m_window, m_trackingWindow, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOREDRAW); + SetWindowPos(m_window, m_trackingWindow, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOREDRAW | SWP_NOACTIVATE); } void WindowBorder::UpdateBorderProperties() const @@ -170,7 +170,9 @@ void WindowBorder::UpdateBorderProperties() const return; } - RECT windowRect = windowRectOpt.value(); + const RECT windowRect = windowRectOpt.value(); + SetWindowPos(m_window, m_trackingWindow, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, SWP_NOREDRAW | SWP_NOACTIVATE); + RECT frameRect{ 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top }; COLORREF color; @@ -192,8 +194,22 @@ LRESULT WindowBorder::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexce { switch (message) { + case WM_TIMER: + { + switch (wparam) + { + case REFRESH_BORDER_TIMER_ID: + KillTimer(m_window, m_timer_id); + m_timer_id = SetTimer(m_window, REFRESH_BORDER_TIMER_ID, REFRESH_BORDER_INTERVAL, nullptr); + UpdateBorderPosition(); + UpdateBorderProperties(); + break; + } + break; + } case WM_NCDESTROY: { + KillTimer(m_window, m_timer_id); ::DefWindowProc(m_window, message, wparam, lparam); SetWindowLongPtr(m_window, GWLP_USERDATA, 0); } @@ -231,7 +247,7 @@ void WindowBorder::SettingsUpdate(SettingId id) UpdateBorderProperties(); } break; - + case SettingId::FrameColor: { UpdateBorderProperties(); @@ -246,5 +262,4 @@ void WindowBorder::SettingsUpdate(SettingId id) default: break; } - } diff --git a/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.h b/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.h index 1f1d3de1d5..50c4d15ee1 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.h +++ b/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.h @@ -7,8 +7,8 @@ class FrameDrawer; class WindowBorder : public SettingsObserver { WindowBorder(HWND window); - WindowBorder(WindowBorder&& other); - + WindowBorder(WindowBorder&& other) = default; + public: static std::unique_ptr Create(HWND window, HINSTANCE hinstance); ~WindowBorder(); @@ -32,8 +32,9 @@ protected: } private: - HWND m_window; - HWND m_trackingWindow; + UINT_PTR m_timer_id = {}; + HWND m_window = {}; + HWND m_trackingWindow = {}; std::unique_ptr m_frameDrawer; LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept; diff --git a/src/modules/alwaysontop/AlwaysOnTop/pch.h b/src/modules/alwaysontop/AlwaysOnTop/pch.h index b3e44606f6..1a3b9fd1fd 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/pch.h +++ b/src/modules/alwaysontop/AlwaysOnTop/pch.h @@ -4,4 +4,7 @@ #include #include #include -#include \ No newline at end of file +#include + +#include +#include \ No newline at end of file