#include "pch.h" #include "WindowBorder.h" #include #include "winrt/Windows.Foundation.h" #include #include // Non-Localizable strings namespace NonLocalizable { const wchar_t ToolWindowClassName[] = L"AlwaysOnTop_Border"; } std::optional GetFrameRect(HWND window) { RECT rect; if (!SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect)))) { return std::nullopt; } int border = AlwaysOnTopSettings::settings().frameThickness; rect.top -= border; rect.left -= border; rect.right += border; rect.bottom += border; return rect; } WindowBorder::WindowBorder(HWND window) : SettingsObserver({ SettingId::FrameColor, SettingId::FrameThickness, SettingId::FrameAccentColor }), m_window(nullptr), m_trackingWindow(window), m_frameDrawer(nullptr) { } WindowBorder::~WindowBorder() { if (m_frameDrawer) { m_frameDrawer->Hide(); m_frameDrawer = nullptr; } if (m_window) { SetWindowLongPtrW(m_window, GWLP_USERDATA, 0); ShowWindow(m_window, SW_HIDE); } } std::unique_ptr WindowBorder::Create(HWND window, HINSTANCE hinstance) { auto self = std::unique_ptr(new WindowBorder(window)); if (self->Init(hinstance)) { return self; } 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) { return false; } auto windowRectOpt = GetFrameRect(m_trackingWindow); if (!windowRectOpt.has_value()) { return false; } RECT windowRect = windowRectOpt.value(); 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); m_window = CreateWindowExW(WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW , NonLocalizable::ToolWindowClassName , L"" , WS_POPUP | WS_DISABLED , windowRect.left , windowRect.top , windowRect.right - windowRect.left , windowRect.bottom - windowRect.top , nullptr , nullptr , hinstance , this); if (!m_window) { return false; } if (!SetLayeredWindowAttributes(m_window, RGB(0, 0, 0), 0, LWA_COLORKEY)) { return false; } // set position of the border-window behind the tracking window // helps to prevent border overlapping (happens after turning borders off and on) SetWindowPos(m_trackingWindow , m_window , windowRect.left , windowRect.top , windowRect.right - windowRect.left , windowRect.bottom - windowRect.top , SWP_NOMOVE | SWP_NOSIZE); m_frameDrawer = FrameDrawer::Create(m_window); if (!m_frameDrawer) { return false; } UpdateBorderProperties(); m_frameDrawer->Show(); m_timer_id = SetTimer(m_window, REFRESH_BORDER_TIMER_ID, REFRESH_BORDER_INTERVAL, nullptr); return true; } void WindowBorder::UpdateBorderPosition() const { if (!m_trackingWindow) { return; } auto rectOpt = GetFrameRect(m_trackingWindow); if (!rectOpt.has_value()) { m_frameDrawer->Hide(); return; } RECT rect = rectOpt.value(); 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 { if (!m_trackingWindow || !m_frameDrawer) { return; } auto windowRectOpt = GetFrameRect(m_trackingWindow); if (!windowRectOpt.has_value()) { return; } 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; if (AlwaysOnTopSettings::settings().frameAccentColor) { winrt::Windows::UI::ViewManagement::UISettings settings; auto accentValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Accent); color = RGB(accentValue.R, accentValue.G, accentValue.B); } else { color = AlwaysOnTopSettings::settings().frameColor; } m_frameDrawer->SetBorderRect(frameRect, color, AlwaysOnTopSettings::settings().frameThickness); } LRESULT WindowBorder::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept { 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); } break; case WM_ERASEBKGND: return TRUE; // prevent from beeping if the border was clicked case WM_SETCURSOR: return TRUE; default: { return DefWindowProc(m_window, message, wparam, lparam); } } return FALSE; } void WindowBorder::SettingsUpdate(SettingId id) { if (!AlwaysOnTopSettings::settings().enableFrame) { return; } auto windowRectOpt = GetFrameRect(m_trackingWindow); if (!windowRectOpt.has_value()) { return; } switch (id) { case SettingId::FrameThickness: { UpdateBorderPosition(); UpdateBorderProperties(); } break; case SettingId::FrameColor: { UpdateBorderProperties(); } break; case SettingId::FrameAccentColor: { UpdateBorderProperties(); } break; default: break; } }