AlwaysOnTop.cpp
1 #include "pch.h" 2 #include "AlwaysOnTop.h" 3 4 #include <common/display/dpi_aware.h> 5 #include <common/utils/game_mode.h> 6 #include <common/utils/excluded_apps.h> 7 #include <common/utils/resources.h> 8 #include <common/utils/winapi_error.h> 9 #include <common/utils/process_path.h> 10 11 #include <common/utils/elevation.h> 12 #include <Generated Files/resource.h> 13 14 #include <interop/shared_constants.h> 15 16 #include <trace.h> 17 #include <WinHookEventIDs.h> 18 19 20 namespace NonLocalizable 21 { 22 const static wchar_t* TOOL_WINDOW_CLASS_NAME = L"AlwaysOnTopWindow"; 23 const static wchar_t* WINDOW_IS_PINNED_PROP = L"AlwaysOnTop_Pinned"; 24 } 25 26 bool isExcluded(HWND window) 27 { 28 auto processPath = get_process_path(window); 29 CharUpperBuffW(processPath.data(), static_cast<DWORD>(processPath.length())); 30 31 return check_excluded_app(window, processPath, AlwaysOnTopSettings::settings().excludedApps); 32 } 33 34 AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) : 35 SettingsObserver({SettingId::FrameEnabled, SettingId::Hotkey, SettingId::ExcludeApps}), 36 m_hinstance(reinterpret_cast<HINSTANCE>(&__ImageBase)), 37 m_useCentralizedLLKH(useLLKH), 38 m_mainThreadId(mainThreadId), 39 m_notificationUtil(std::make_unique<notifications::NotificationUtil>()) 40 { 41 s_instance = this; 42 DPIAware::EnableDPIAwarenessForThisProcess(); 43 44 if (InitMainWindow()) 45 { 46 InitializeWinhookEventIds(); 47 48 AlwaysOnTopSettings::instance().InitFileWatcher(); 49 AlwaysOnTopSettings::instance().LoadSettings(); 50 51 RegisterHotkey(); 52 RegisterLLKH(); 53 54 SubscribeToEvents(); 55 StartTrackingTopmostWindows(); 56 } 57 else 58 { 59 Logger::error("Failed to init AlwaysOnTop module"); 60 // TODO: show localized message 61 } 62 } 63 64 AlwaysOnTop::~AlwaysOnTop() 65 { 66 m_running = false; 67 m_notificationUtil.reset(); 68 69 if (m_hPinEvent) 70 { 71 // Needed to unblock MsgWaitForMultipleObjects one last time 72 SetEvent(m_hPinEvent); 73 CloseHandle(m_hPinEvent); 74 } 75 m_thread.join(); 76 77 CleanUp(); 78 } 79 80 bool AlwaysOnTop::InitMainWindow() 81 { 82 WNDCLASSEXW wcex{}; 83 wcex.cbSize = sizeof(WNDCLASSEX); 84 wcex.lpfnWndProc = WndProc_Helper; 85 wcex.hInstance = m_hinstance; 86 wcex.lpszClassName = NonLocalizable::TOOL_WINDOW_CLASS_NAME; 87 RegisterClassExW(&wcex); 88 89 m_window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::TOOL_WINDOW_CLASS_NAME, L"", WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, m_hinstance, this); 90 if (!m_window) 91 { 92 Logger::error(L"Failed to create AlwaysOnTop window: {}", get_last_error_or_default(GetLastError())); 93 return false; 94 } 95 96 return true; 97 } 98 99 void AlwaysOnTop::SettingsUpdate(SettingId id) 100 { 101 switch (id) 102 { 103 case SettingId::Hotkey: 104 { 105 RegisterHotkey(); 106 } 107 break; 108 case SettingId::FrameEnabled: 109 { 110 if (AlwaysOnTopSettings::settings().enableFrame) 111 { 112 for (auto& iter : m_topmostWindows) 113 { 114 if (!iter.second) 115 { 116 AssignBorder(iter.first); 117 } 118 } 119 } 120 else 121 { 122 for (auto& iter : m_topmostWindows) 123 { 124 iter.second = nullptr; 125 } 126 } 127 } 128 break; 129 case SettingId::ExcludeApps: 130 { 131 std::vector<HWND> toErase{}; 132 for (const auto& [window, border] : m_topmostWindows) 133 { 134 if (isExcluded(window)) 135 { 136 UnpinTopmostWindow(window); 137 toErase.push_back(window); 138 } 139 } 140 141 for (const auto window: toErase) 142 { 143 m_topmostWindows.erase(window); 144 } 145 } 146 break; 147 default: 148 break; 149 } 150 } 151 152 LRESULT AlwaysOnTop::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept 153 { 154 if (message == WM_HOTKEY) 155 { 156 int hotkeyId = static_cast<int>(wparam); 157 if (HWND fw{ GetForegroundWindow() }) 158 { 159 if (hotkeyId == static_cast<int>(HotkeyId::Pin)) 160 { 161 ProcessCommand(fw); 162 } 163 else if (hotkeyId == static_cast<int>(HotkeyId::IncreaseOpacity)) 164 { 165 StepWindowTransparency(fw, Settings::transparencyStep); 166 } 167 else if (hotkeyId == static_cast<int>(HotkeyId::DecreaseOpacity)) 168 { 169 StepWindowTransparency(fw, -Settings::transparencyStep); 170 } 171 } 172 } 173 else if (message == WM_PRIV_SETTINGS_CHANGED) 174 { 175 AlwaysOnTopSettings::instance().LoadSettings(); 176 } 177 178 return 0; 179 } 180 181 void AlwaysOnTop::ProcessCommand(HWND window) 182 { 183 bool gameMode = detect_game_mode(); 184 if (AlwaysOnTopSettings::settings().blockInGameMode && gameMode) 185 { 186 return; 187 } 188 189 if (isExcluded(window)) 190 { 191 return; 192 } 193 194 Sound::Type soundType = Sound::Type::Off; 195 bool topmost = IsTopmost(window); 196 if (topmost) 197 { 198 if (UnpinTopmostWindow(window)) 199 { 200 auto iter = m_topmostWindows.find(window); 201 if (iter != m_topmostWindows.end()) 202 { 203 m_topmostWindows.erase(iter); 204 } 205 206 // Restore transparency when unpinning 207 RestoreWindowAlpha(window); 208 m_windowOriginalLayeredState.erase(window); 209 210 Trace::AlwaysOnTop::UnpinWindow(); 211 } 212 } 213 else 214 { 215 if (PinTopmostWindow(window)) 216 { 217 soundType = Sound::Type::On; 218 AssignBorder(window); 219 220 Trace::AlwaysOnTop::PinWindow(); 221 } 222 } 223 224 if (AlwaysOnTopSettings::settings().enableSound) 225 { 226 m_sound.Play(soundType); 227 } 228 } 229 230 void AlwaysOnTop::StartTrackingTopmostWindows() 231 { 232 using result_t = std::vector<HWND>; 233 result_t result; 234 235 auto enumWindows = [](HWND hwnd, LPARAM param) -> BOOL { 236 if (!IsWindowVisible(hwnd)) 237 { 238 return TRUE; 239 } 240 241 if (isExcluded(hwnd)) 242 { 243 return TRUE; 244 } 245 246 auto windowName = GetWindowTextLength(hwnd); 247 if (windowName > 0) 248 { 249 result_t& result = *reinterpret_cast<result_t*>(param); 250 result.push_back(hwnd); 251 } 252 253 return TRUE; 254 }; 255 256 EnumWindows(enumWindows, reinterpret_cast<LPARAM>(&result)); 257 258 for (HWND window : result) 259 { 260 if (IsPinned(window)) 261 { 262 AssignBorder(window); 263 } 264 } 265 } 266 267 bool AlwaysOnTop::AssignBorder(HWND window) 268 { 269 if (m_virtualDesktopUtils.IsWindowOnCurrentDesktop(window) && AlwaysOnTopSettings::settings().enableFrame) 270 { 271 auto border = WindowBorder::Create(window, m_hinstance); 272 if (border) 273 { 274 m_topmostWindows[window] = std::move(border); 275 } 276 } 277 else 278 { 279 m_topmostWindows[window] = nullptr; 280 } 281 282 return true; 283 } 284 285 void AlwaysOnTop::RegisterHotkey() const 286 { 287 if (m_useCentralizedLLKH) 288 { 289 // All hotkeys are handled by centralized LLKH 290 return; 291 } 292 293 // Register hotkeys only when not using centralized LLKH 294 UnregisterHotKey(m_window, static_cast<int>(HotkeyId::Pin)); 295 UnregisterHotKey(m_window, static_cast<int>(HotkeyId::IncreaseOpacity)); 296 UnregisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity)); 297 298 // Register pin hotkey 299 RegisterHotKey(m_window, static_cast<int>(HotkeyId::Pin), AlwaysOnTopSettings::settings().hotkey.get_modifiers(), AlwaysOnTopSettings::settings().hotkey.get_code()); 300 301 // Register transparency hotkeys using the same modifiers as the pin hotkey 302 UINT modifiers = AlwaysOnTopSettings::settings().hotkey.get_modifiers(); 303 RegisterHotKey(m_window, static_cast<int>(HotkeyId::IncreaseOpacity), modifiers, VK_OEM_PLUS); 304 RegisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity), modifiers, VK_OEM_MINUS); 305 } 306 307 void AlwaysOnTop::RegisterLLKH() 308 { 309 if (!m_useCentralizedLLKH) 310 { 311 return; 312 } 313 314 m_hPinEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT); 315 m_hTerminateEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_TERMINATE_EVENT); 316 m_hIncreaseOpacityEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_INCREASE_OPACITY_EVENT); 317 m_hDecreaseOpacityEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_DECREASE_OPACITY_EVENT); 318 319 if (!m_hPinEvent) 320 { 321 Logger::warn(L"Failed to create pinEvent. {}", get_last_error_or_default(GetLastError())); 322 return; 323 } 324 325 if (!m_hTerminateEvent) 326 { 327 Logger::warn(L"Failed to create terminateEvent. {}", get_last_error_or_default(GetLastError())); 328 return; 329 } 330 331 if (!m_hIncreaseOpacityEvent) 332 { 333 Logger::warn(L"Failed to create increaseOpacityEvent. {}", get_last_error_or_default(GetLastError())); 334 } 335 336 if (!m_hDecreaseOpacityEvent) 337 { 338 Logger::warn(L"Failed to create decreaseOpacityEvent. {}", get_last_error_or_default(GetLastError())); 339 } 340 341 HANDLE handles[4] = { m_hPinEvent, 342 m_hTerminateEvent, 343 m_hIncreaseOpacityEvent, 344 m_hDecreaseOpacityEvent }; 345 346 m_thread = std::thread([this, handles]() { 347 MSG msg; 348 while (m_running) 349 { 350 DWORD dwEvt = MsgWaitForMultipleObjects(4, handles, false, INFINITE, QS_ALLINPUT); 351 if (!m_running) 352 { 353 break; 354 } 355 switch (dwEvt) 356 { 357 case WAIT_OBJECT_0: // Pin event 358 if (HWND fw{ GetForegroundWindow() }) 359 { 360 ProcessCommand(fw); 361 } 362 break; 363 case WAIT_OBJECT_0 + 1: // Terminate event 364 PostThreadMessage(m_mainThreadId, WM_QUIT, 0, 0); 365 break; 366 case WAIT_OBJECT_0 + 2: // Increase opacity event 367 if (HWND fw{ GetForegroundWindow() }) 368 { 369 StepWindowTransparency(fw, Settings::transparencyStep); 370 } 371 break; 372 case WAIT_OBJECT_0 + 3: // Decrease opacity event 373 if (HWND fw{ GetForegroundWindow() }) 374 { 375 StepWindowTransparency(fw, -Settings::transparencyStep); 376 } 377 break; 378 case WAIT_OBJECT_0 + 4: // Message queue 379 if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) 380 { 381 TranslateMessage(&msg); 382 DispatchMessageW(&msg); 383 } 384 break; 385 default: 386 break; 387 } 388 } 389 }); 390 } 391 392 void AlwaysOnTop::SubscribeToEvents() 393 { 394 // subscribe to windows events 395 std::array<DWORD, 7> events_to_subscribe = { 396 EVENT_OBJECT_LOCATIONCHANGE, 397 EVENT_SYSTEM_MINIMIZESTART, 398 EVENT_SYSTEM_MINIMIZEEND, 399 EVENT_SYSTEM_MOVESIZEEND, 400 EVENT_SYSTEM_FOREGROUND, 401 EVENT_OBJECT_DESTROY, 402 EVENT_OBJECT_FOCUS, 403 }; 404 405 for (const auto event : events_to_subscribe) 406 { 407 auto hook = SetWinEventHook(event, event, nullptr, WinHookProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); 408 if (hook) 409 { 410 m_staticWinEventHooks.emplace_back(hook); 411 } 412 else 413 { 414 Logger::error(L"Failed to set win event hook"); 415 } 416 } 417 } 418 419 void AlwaysOnTop::UnpinAll() 420 { 421 for (const auto& [topWindow, border] : m_topmostWindows) 422 { 423 if (!UnpinTopmostWindow(topWindow)) 424 { 425 Logger::error(L"Unpinning topmost window failed"); 426 } 427 // Restore transparency when unpinning all 428 RestoreWindowAlpha(topWindow); 429 } 430 431 m_topmostWindows.clear(); 432 m_windowOriginalLayeredState.clear(); 433 } 434 435 void AlwaysOnTop::CleanUp() 436 { 437 UnpinAll(); 438 if (m_window) 439 { 440 DestroyWindow(m_window); 441 m_window = nullptr; 442 } 443 444 UnregisterClass(NonLocalizable::TOOL_WINDOW_CLASS_NAME, reinterpret_cast<HINSTANCE>(&__ImageBase)); 445 } 446 447 bool AlwaysOnTop::IsTopmost(HWND window) const noexcept 448 { 449 int exStyle = GetWindowLong(window, GWL_EXSTYLE); 450 return (exStyle & WS_EX_TOPMOST) == WS_EX_TOPMOST; 451 } 452 453 bool AlwaysOnTop::IsPinned(HWND window) const noexcept 454 { 455 auto handle = GetProp(window, NonLocalizable::WINDOW_IS_PINNED_PROP); 456 return (handle != NULL); 457 } 458 459 bool AlwaysOnTop::PinTopmostWindow(HWND window) const noexcept 460 { 461 if (!SetProp(window, NonLocalizable::WINDOW_IS_PINNED_PROP, reinterpret_cast<HANDLE>(1))) 462 { 463 Logger::error(L"SetProp failed, {}", get_last_error_or_default(GetLastError())); 464 } 465 466 auto res = SetWindowPos(window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); 467 if (!res) 468 { 469 Logger::error(L"Failed to pin window, {}", get_last_error_or_default(GetLastError())); 470 } 471 472 return res; 473 } 474 475 bool AlwaysOnTop::UnpinTopmostWindow(HWND window) const noexcept 476 { 477 RemoveProp(window, NonLocalizable::WINDOW_IS_PINNED_PROP); 478 auto res = SetWindowPos(window, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); 479 if (!res) 480 { 481 Logger::error(L"Failed to unpin window, {}", get_last_error_or_default(GetLastError())); 482 } 483 484 return res; 485 } 486 487 bool AlwaysOnTop::IsTracked(HWND window) const noexcept 488 { 489 auto iter = m_topmostWindows.find(window); 490 return (iter != m_topmostWindows.end()); 491 } 492 493 void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept 494 { 495 if (!AlwaysOnTopSettings::settings().enableFrame || !data->hwnd) 496 { 497 return; 498 } 499 500 std::vector<HWND> toErase{}; 501 for (const auto& [window, border] : m_topmostWindows) 502 { 503 // check if the window was closed, since for some EVENT_OBJECT_DESTROY doesn't work 504 // fixes https://github.com/microsoft/PowerToys/issues/15300 505 bool visible = IsWindowVisible(window); 506 if (!visible) 507 { 508 UnpinTopmostWindow(window); 509 toErase.push_back(window); 510 } 511 } 512 513 for (const auto window : toErase) 514 { 515 m_topmostWindows.erase(window); 516 m_windowOriginalLayeredState.erase(window); 517 } 518 519 switch (data->event) 520 { 521 case EVENT_OBJECT_LOCATIONCHANGE: 522 { 523 auto iter = m_topmostWindows.find(data->hwnd); 524 if (iter != m_topmostWindows.end()) 525 { 526 const auto& border = iter->second; 527 if (border) 528 { 529 border->UpdateBorderPosition(); 530 } 531 } 532 } 533 break; 534 case EVENT_SYSTEM_MINIMIZESTART: 535 { 536 auto iter = m_topmostWindows.find(data->hwnd); 537 if (iter != m_topmostWindows.end()) 538 { 539 m_topmostWindows[data->hwnd] = nullptr; 540 } 541 } 542 break; 543 case EVENT_SYSTEM_MINIMIZEEND: 544 { 545 auto iter = m_topmostWindows.find(data->hwnd); 546 if (iter != m_topmostWindows.end()) 547 { 548 // pin border again, in some cases topmost flag stops working: https://github.com/microsoft/PowerToys/issues/17332 549 PinTopmostWindow(data->hwnd); 550 AssignBorder(data->hwnd); 551 } 552 } 553 break; 554 case EVENT_SYSTEM_MOVESIZEEND: 555 { 556 auto iter = m_topmostWindows.find(data->hwnd); 557 if (iter != m_topmostWindows.end()) 558 { 559 const auto& border = iter->second; 560 if (border) 561 { 562 border->UpdateBorderPosition(); 563 } 564 } 565 } 566 break; 567 case EVENT_SYSTEM_FOREGROUND: 568 { 569 if (!is_process_elevated() && IsProcessOfWindowElevated(data->hwnd)) 570 { 571 m_notificationUtil->WarnIfElevationIsRequired(GET_RESOURCE_STRING(IDS_ALWAYSONTOP), 572 GET_RESOURCE_STRING(IDS_SYSTEM_FOREGROUND_ELEVATED), 573 GET_RESOURCE_STRING(IDS_SYSTEM_FOREGROUND_ELEVATED_LEARN_MORE), 574 GET_RESOURCE_STRING(IDS_SYSTEM_FOREGROUND_ELEVATED_DIALOG_DONT_SHOW_AGAIN)); 575 } 576 RefreshBorders(); 577 } 578 break; 579 case EVENT_OBJECT_FOCUS: 580 { 581 for (const auto& [window, border] : m_topmostWindows) 582 { 583 // check if topmost was reset 584 // fixes https://github.com/microsoft/PowerToys/issues/19168 585 if (!IsTopmost(window)) 586 { 587 Logger::trace(L"A window no longer has Topmost set and it should. Setting topmost again."); 588 PinTopmostWindow(window); 589 } 590 } 591 } 592 break; 593 default: 594 break; 595 } 596 } 597 598 void AlwaysOnTop::RefreshBorders() 599 { 600 for (const auto& [window, border] : m_topmostWindows) 601 { 602 if (m_virtualDesktopUtils.IsWindowOnCurrentDesktop(window)) 603 { 604 if (!border) 605 { 606 AssignBorder(window); 607 } 608 } 609 else 610 { 611 if (border) 612 { 613 m_topmostWindows[window] = nullptr; 614 } 615 } 616 } 617 } 618 619 HWND AlwaysOnTop::ResolveTransparencyTargetWindow(HWND window) 620 { 621 if (!window || !IsWindow(window)) 622 { 623 return nullptr; 624 } 625 626 // Only allow transparency changes on pinned windows 627 if (!IsPinned(window)) 628 { 629 return nullptr; 630 } 631 632 return window; 633 } 634 635 636 void AlwaysOnTop::StepWindowTransparency(HWND window, int delta) 637 { 638 HWND targetWindow = ResolveTransparencyTargetWindow(window); 639 if (!targetWindow) 640 { 641 return; 642 } 643 644 int currentTransparency = Settings::maxTransparencyPercentage; 645 LONG exStyle = GetWindowLong(targetWindow, GWL_EXSTYLE); 646 if (exStyle & WS_EX_LAYERED) 647 { 648 BYTE alpha = 255; 649 if (GetLayeredWindowAttributes(targetWindow, nullptr, &alpha, nullptr)) 650 { 651 currentTransparency = (alpha * 100) / 255; 652 } 653 } 654 655 int newTransparency = (std::max)(Settings::minTransparencyPercentage, 656 (std::min)(Settings::maxTransparencyPercentage, currentTransparency + delta)); 657 658 if (newTransparency != currentTransparency) 659 { 660 ApplyWindowAlpha(targetWindow, newTransparency); 661 662 if (AlwaysOnTopSettings::settings().enableSound) 663 { 664 m_sound.Play(delta > 0 ? Sound::Type::IncreaseOpacity : Sound::Type::DecreaseOpacity); 665 } 666 667 Logger::debug(L"Transparency adjusted to {}%", newTransparency); 668 } 669 } 670 671 void AlwaysOnTop::ApplyWindowAlpha(HWND window, int percentage) 672 { 673 if (!window || !IsWindow(window)) 674 { 675 return; 676 } 677 678 percentage = (std::max)(Settings::minTransparencyPercentage, 679 (std::min)(Settings::maxTransparencyPercentage, percentage)); 680 681 LONG exStyle = GetWindowLong(window, GWL_EXSTYLE); 682 bool isCurrentlyLayered = (exStyle & WS_EX_LAYERED) != 0; 683 684 // Cache original state on first transparency application 685 if (m_windowOriginalLayeredState.find(window) == m_windowOriginalLayeredState.end()) 686 { 687 WindowLayeredState state; 688 state.hadLayeredStyle = isCurrentlyLayered; 689 690 if (isCurrentlyLayered) 691 { 692 BYTE alpha = 255; 693 COLORREF colorKey = 0; 694 DWORD flags = 0; 695 if (GetLayeredWindowAttributes(window, &colorKey, &alpha, &flags)) 696 { 697 state.originalAlpha = alpha; 698 state.usedColorKey = (flags & LWA_COLORKEY) != 0; 699 state.colorKey = colorKey; 700 } 701 else 702 { 703 Logger::warn(L"GetLayeredWindowAttributes failed for layered window, skipping"); 704 return; 705 } 706 } 707 m_windowOriginalLayeredState[window] = state; 708 } 709 710 // Clear WS_EX_LAYERED first to ensure SetLayeredWindowAttributes works 711 if (isCurrentlyLayered) 712 { 713 SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED); 714 SetWindowPos(window, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); 715 exStyle = GetWindowLong(window, GWL_EXSTYLE); 716 } 717 718 BYTE alphaValue = static_cast<BYTE>((255 * percentage) / 100); 719 SetWindowLong(window, GWL_EXSTYLE, exStyle | WS_EX_LAYERED); 720 SetLayeredWindowAttributes(window, 0, alphaValue, LWA_ALPHA); 721 } 722 723 void AlwaysOnTop::RestoreWindowAlpha(HWND window) 724 { 725 if (!window || !IsWindow(window)) 726 { 727 return; 728 } 729 730 LONG exStyle = GetWindowLong(window, GWL_EXSTYLE); 731 auto it = m_windowOriginalLayeredState.find(window); 732 733 if (it != m_windowOriginalLayeredState.end()) 734 { 735 const auto& originalState = it->second; 736 737 if (originalState.hadLayeredStyle) 738 { 739 // Window originally had WS_EX_LAYERED - restore original attributes 740 // Clear and re-add to ensure clean state 741 if (exStyle & WS_EX_LAYERED) 742 { 743 SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED); 744 exStyle = GetWindowLong(window, GWL_EXSTYLE); 745 } 746 SetWindowLong(window, GWL_EXSTYLE, exStyle | WS_EX_LAYERED); 747 748 // Restore original alpha and/or color key 749 DWORD flags = LWA_ALPHA; 750 if (originalState.usedColorKey) 751 { 752 flags |= LWA_COLORKEY; 753 } 754 SetLayeredWindowAttributes(window, originalState.colorKey, originalState.originalAlpha, flags); 755 SetWindowPos(window, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); 756 } 757 else 758 { 759 // Window originally didn't have WS_EX_LAYERED - remove it completely 760 if (exStyle & WS_EX_LAYERED) 761 { 762 SetLayeredWindowAttributes(window, 0, 255, LWA_ALPHA); 763 SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED); 764 SetWindowPos(window, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); 765 } 766 } 767 768 m_windowOriginalLayeredState.erase(it); 769 } 770 else 771 { 772 // Fallback: no cached state, just remove layered style 773 if (exStyle & WS_EX_LAYERED) 774 { 775 SetLayeredWindowAttributes(window, 0, 255, LWA_ALPHA); 776 SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED); 777 } 778 } 779 }