WindowBorder.cpp
1 #include "pch.h" 2 #include "WindowBorder.h" 3 4 #include <dwmapi.h> 5 #include "winrt/Windows.Foundation.h" 6 7 #include <FrameDrawer.h> 8 #include <ScalingUtils.h> 9 #include <Settings.h> 10 #include <WindowCornersUtil.h> 11 12 // Non-Localizable strings 13 namespace NonLocalizable 14 { 15 const wchar_t ToolWindowClassName[] = L"AlwaysOnTop_Border"; 16 } 17 18 std::optional<RECT> GetFrameRect(HWND window) 19 { 20 RECT rect; 21 if (!SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect)))) 22 { 23 return std::nullopt; 24 } 25 26 int border = AlwaysOnTopSettings::settings().frameThickness; 27 rect.top -= border; 28 rect.left -= border; 29 rect.right += border; 30 rect.bottom += border; 31 32 return rect; 33 } 34 35 WindowBorder::WindowBorder(HWND window) : 36 SettingsObserver({ SettingId::FrameColor, SettingId::FrameThickness, SettingId::FrameAccentColor, SettingId::FrameOpacity, SettingId::RoundCornersEnabled }), 37 m_window(nullptr), 38 m_trackingWindow(window), 39 m_frameDrawer(nullptr) 40 { 41 } 42 43 WindowBorder::~WindowBorder() 44 { 45 if (m_frameDrawer) 46 { 47 m_frameDrawer->Hide(); 48 m_frameDrawer = nullptr; 49 } 50 51 if (m_window) 52 { 53 SetWindowLongPtrW(m_window, GWLP_USERDATA, 0); 54 DestroyWindow(m_window); 55 } 56 } 57 58 std::unique_ptr<WindowBorder> WindowBorder::Create(HWND window, HINSTANCE hinstance) 59 { 60 auto self = std::unique_ptr<WindowBorder>(new WindowBorder(window)); 61 if (self->Init(hinstance)) 62 { 63 return self; 64 } 65 66 return nullptr; 67 } 68 69 namespace 70 { 71 constexpr uint32_t REFRESH_BORDER_TIMER_ID = 123; 72 constexpr uint32_t REFRESH_BORDER_INTERVAL = 100; 73 } 74 75 bool WindowBorder::Init(HINSTANCE hinstance) 76 { 77 if (!m_trackingWindow) 78 { 79 return false; 80 } 81 82 auto windowRectOpt = GetFrameRect(m_trackingWindow); 83 if (!windowRectOpt.has_value()) 84 { 85 return false; 86 } 87 88 RECT windowRect = windowRectOpt.value(); 89 90 WNDCLASSEXW wcex{}; 91 wcex.cbSize = sizeof(WNDCLASSEX); 92 wcex.lpfnWndProc = s_WndProc; 93 wcex.hInstance = hinstance; 94 wcex.lpszClassName = NonLocalizable::ToolWindowClassName; 95 wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); 96 RegisterClassExW(&wcex); 97 98 m_window = CreateWindowExW(WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW 99 , NonLocalizable::ToolWindowClassName 100 , L"" 101 , WS_POPUP | WS_DISABLED 102 , windowRect.left 103 , windowRect.top 104 , windowRect.right - windowRect.left 105 , windowRect.bottom - windowRect.top 106 , nullptr 107 , nullptr 108 , hinstance 109 , this); 110 111 if (!m_window) 112 { 113 return false; 114 } 115 116 // make window transparent 117 int const pos = -GetSystemMetrics(SM_CXVIRTUALSCREEN) - 8; 118 if (wil::unique_hrgn hrgn{ CreateRectRgn(pos, 0, (pos + 1), 1) }) 119 { 120 DWM_BLURBEHIND bh = { DWM_BB_ENABLE | DWM_BB_BLURREGION, TRUE, hrgn.get(), FALSE }; 121 DwmEnableBlurBehindWindow(m_window, &bh); 122 } 123 124 if (!SetLayeredWindowAttributes(m_window, RGB(0, 0, 0), 0, LWA_COLORKEY)) 125 { 126 return false; 127 } 128 129 if (!SetLayeredWindowAttributes(m_window, 0, 255, LWA_ALPHA)) 130 { 131 return false; 132 } 133 134 // set position of the border-window behind the tracking window 135 // helps to prevent border overlapping (happens after turning borders off and on) 136 SetWindowPos(m_trackingWindow 137 , m_window 138 , windowRect.left 139 , windowRect.top 140 , windowRect.right - windowRect.left 141 , windowRect.bottom - windowRect.top 142 , SWP_NOMOVE | SWP_NOSIZE); 143 144 BOOL val = TRUE; 145 DwmSetWindowAttribute(m_window, DWMWA_EXCLUDED_FROM_PEEK, &val, sizeof(val)); 146 147 m_frameDrawer = FrameDrawer::Create(m_window); 148 if (!m_frameDrawer) 149 { 150 return false; 151 } 152 153 m_frameDrawer->Show(); 154 UpdateBorderPosition(); 155 UpdateBorderProperties(); 156 m_timer_id = SetTimer(m_window, REFRESH_BORDER_TIMER_ID, REFRESH_BORDER_INTERVAL, nullptr); 157 158 return true; 159 } 160 161 void WindowBorder::UpdateBorderPosition() const 162 { 163 if (!m_trackingWindow) 164 { 165 return; 166 } 167 168 auto rectOpt = GetFrameRect(m_trackingWindow); 169 if (!rectOpt.has_value()) 170 { 171 m_frameDrawer->Hide(); 172 return; 173 } 174 175 RECT rect = rectOpt.value(); 176 SetWindowPos(m_window, m_trackingWindow, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOREDRAW | SWP_NOACTIVATE); 177 } 178 179 void WindowBorder::UpdateBorderProperties() const 180 { 181 if (!m_trackingWindow || !m_frameDrawer) 182 { 183 return; 184 } 185 186 auto windowRectOpt = GetFrameRect(m_trackingWindow); 187 if (!windowRectOpt.has_value()) 188 { 189 return; 190 } 191 192 const RECT windowRect = windowRectOpt.value(); 193 SetWindowPos(m_window, m_trackingWindow, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, SWP_NOREDRAW | SWP_NOACTIVATE); 194 195 RECT frameRect{ 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top }; 196 197 COLORREF color; 198 if (AlwaysOnTopSettings::settings().frameAccentColor) 199 { 200 winrt::Windows::UI::ViewManagement::UISettings settings; 201 auto accentValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Accent); 202 color = RGB(accentValue.R, accentValue.G, accentValue.B); 203 } 204 else 205 { 206 color = AlwaysOnTopSettings::settings().frameColor; 207 } 208 209 float opacity = AlwaysOnTopSettings::settings().frameOpacity / 100.0f; 210 float scalingFactor = ScalingUtils::ScalingFactor(m_trackingWindow); 211 float thickness = AlwaysOnTopSettings::settings().frameThickness * scalingFactor; 212 float cornerRadius = 0.0; 213 if (AlwaysOnTopSettings::settings().roundCornersEnabled) 214 { 215 cornerRadius = WindowCornerUtils::CornersRadius(m_trackingWindow) * scalingFactor; 216 } 217 218 m_frameDrawer->SetBorderRect(frameRect, color, opacity, static_cast<int>(thickness), cornerRadius); 219 } 220 221 LRESULT WindowBorder::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept 222 { 223 switch (message) 224 { 225 case WM_TIMER: 226 { 227 switch (wparam) 228 { 229 case REFRESH_BORDER_TIMER_ID: 230 KillTimer(m_window, m_timer_id); 231 m_timer_id = SetTimer(m_window, REFRESH_BORDER_TIMER_ID, REFRESH_BORDER_INTERVAL, nullptr); 232 UpdateBorderPosition(); 233 UpdateBorderProperties(); 234 break; 235 } 236 break; 237 } 238 case WM_NCDESTROY: 239 { 240 KillTimer(m_window, m_timer_id); 241 ::DefWindowProc(m_window, message, wparam, lparam); 242 SetWindowLongPtr(m_window, GWLP_USERDATA, 0); 243 } 244 break; 245 246 case WM_ERASEBKGND: 247 return TRUE; 248 249 // Prevent from beeping if the border was clicked 250 case WM_SETCURSOR: 251 { 252 HCURSOR hCursor = LoadCursorW(nullptr, IDC_ARROW); 253 if (hCursor) 254 { 255 SetCursor(hCursor); 256 } 257 258 return TRUE; 259 } 260 261 default: 262 { 263 return DefWindowProc(m_window, message, wparam, lparam); 264 } 265 } 266 return FALSE; 267 } 268 269 void WindowBorder::SettingsUpdate(SettingId id) 270 { 271 if (!AlwaysOnTopSettings::settings().enableFrame) 272 { 273 return; 274 } 275 276 auto windowRectOpt = GetFrameRect(m_trackingWindow); 277 if (!windowRectOpt.has_value()) 278 { 279 return; 280 } 281 282 switch (id) 283 { 284 case SettingId::FrameThickness: 285 { 286 UpdateBorderPosition(); 287 UpdateBorderProperties(); 288 } 289 break; 290 291 case SettingId::FrameColor: 292 case SettingId::FrameAccentColor: 293 case SettingId::FrameOpacity: 294 case SettingId::RoundCornersEnabled: 295 { 296 UpdateBorderProperties(); 297 } 298 break; 299 300 default: 301 break; 302 } 303 }