Compare commits

...

2 Commits

Author SHA1 Message Date
vanzue
158ada435d automatically hide other window after aot 2026-02-05 20:56:26 +08:00
vanzue
52bf804f57 dev 2026-02-05 20:29:24 +08:00
7 changed files with 671 additions and 20 deletions

View File

@@ -16,6 +16,11 @@
#include <trace.h>
#include <WinHookEventIDs.h>
#include <dwmapi.h>
#include <ScalingUtils.h>
#include <WindowCornersUtil.h>
#include <cmath>
namespace NonLocalizable
{
@@ -31,6 +36,14 @@ bool isExcluded(HWND window)
return check_excluded_app(window, processPath, AlwaysOnTopSettings::settings().excludedApps);
}
namespace
{
bool TryGetExtendedFrameBounds(HWND window, RECT& rect) noexcept
{
return DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect)) == S_OK;
}
}
AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) :
SettingsObserver({SettingId::FrameEnabled, SettingId::Hotkey, SettingId::ExcludeApps}),
m_hinstance(reinterpret_cast<HINSTANCE>(&__ImageBase)),
@@ -48,6 +61,13 @@ AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) :
AlwaysOnTopSettings::instance().InitFileWatcher();
AlwaysOnTopSettings::instance().LoadSettings();
m_dimOverlay = std::make_unique<DimOverlay>();
if (!m_dimOverlay->Initialize(m_hinstance))
{
Logger::warn("Failed to initialize AlwaysOnTop dim overlay.");
m_dimOverlay.reset();
}
RegisterHotkey();
RegisterLLKH();
@@ -124,6 +144,7 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
iter.second = nullptr;
}
}
UpdateDimOverlay();
}
break;
case SettingId::ExcludeApps:
@@ -142,6 +163,7 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
{
m_topmostWindows.erase(window);
}
UpdateDimOverlay();
}
break;
default:
@@ -216,17 +238,72 @@ void AlwaysOnTop::ProcessCommand(HWND window)
{
soundType = Sound::Type::On;
AssignBorder(window);
MinimizeOtherWindows(window);
Trace::AlwaysOnTop::PinWindow();
}
}
UpdateDimOverlay();
if (AlwaysOnTopSettings::settings().enableSound)
{
m_sound.Play(soundType);
}
}
void AlwaysOnTop::MinimizeOtherWindows(HWND pinnedWindow)
{
struct EnumState
{
AlwaysOnTop* self{};
HWND pinned{};
};
EnumState state{ this, pinnedWindow };
EnumWindows([](HWND window, LPARAM lparam) -> BOOL {
auto* state = reinterpret_cast<EnumState*>(lparam);
if (!state || !state->self)
{
return FALSE;
}
if (window == state->pinned)
{
return TRUE;
}
if (!IsWindowVisible(window) || IsIconic(window))
{
return TRUE;
}
if (window == state->self->m_window)
{
return TRUE;
}
if (state->self->m_dimOverlay && window == state->self->m_dimOverlay->Hwnd())
{
return TRUE;
}
auto exStyle = GetWindowLongPtr(window, GWL_EXSTYLE);
if ((exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW)
{
return TRUE;
}
if (!state->self->m_virtualDesktopUtils.IsWindowOnCurrentDesktop(window))
{
return TRUE;
}
ShowWindow(window, SW_MINIMIZE);
return TRUE;
}, reinterpret_cast<LPARAM>(&state));
}
void AlwaysOnTop::StartTrackingTopmostWindows()
{
using result_t = std::vector<HWND>;
@@ -262,6 +339,7 @@ void AlwaysOnTop::StartTrackingTopmostWindows()
AssignBorder(window);
}
}
}
bool AlwaysOnTop::AssignBorder(HWND window)
@@ -430,11 +508,18 @@ void AlwaysOnTop::UnpinAll()
m_topmostWindows.clear();
m_windowOriginalLayeredState.clear();
UpdateDimOverlay();
}
void AlwaysOnTop::CleanUp()
{
UnpinAll();
if (m_dimOverlay)
{
m_dimOverlay->Terminate();
m_dimOverlay.reset();
}
if (m_window)
{
DestroyWindow(m_window);
@@ -492,11 +577,13 @@ bool AlwaysOnTop::IsTracked(HWND window) const noexcept
void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
{
if (!AlwaysOnTopSettings::settings().enableFrame || !data->hwnd)
if (!data->hwnd)
{
return;
}
const bool frameEnabled = AlwaysOnTopSettings::settings().enableFrame;
std::vector<HWND> toErase{};
for (const auto& [window, border] : m_topmostWindows)
{
@@ -520,23 +607,29 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
{
case EVENT_OBJECT_LOCATIONCHANGE:
{
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
if (frameEnabled)
{
const auto& border = iter->second;
if (border)
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
{
border->UpdateBorderPosition();
const auto& border = iter->second;
if (border)
{
border->UpdateBorderPosition();
}
}
}
}
break;
case EVENT_SYSTEM_MINIMIZESTART:
{
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
if (frameEnabled)
{
m_topmostWindows[data->hwnd] = nullptr;
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
{
m_topmostWindows[data->hwnd] = nullptr;
}
}
}
break;
@@ -546,20 +639,26 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
if (iter != m_topmostWindows.end())
{
// pin border again, in some cases topmost flag stops working: https://github.com/microsoft/PowerToys/issues/17332
PinTopmostWindow(data->hwnd);
AssignBorder(data->hwnd);
PinTopmostWindow(data->hwnd);
if (frameEnabled)
{
AssignBorder(data->hwnd);
}
}
}
break;
case EVENT_SYSTEM_MOVESIZEEND:
{
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
if (frameEnabled)
{
const auto& border = iter->second;
if (border)
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
{
border->UpdateBorderPosition();
const auto& border = iter->second;
if (border)
{
border->UpdateBorderPosition();
}
}
}
}
@@ -573,7 +672,10 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
GET_RESOURCE_STRING(IDS_SYSTEM_FOREGROUND_ELEVATED_LEARN_MORE),
GET_RESOURCE_STRING(IDS_SYSTEM_FOREGROUND_ELEVATED_DIALOG_DONT_SHOW_AGAIN));
}
RefreshBorders();
if (frameEnabled)
{
RefreshBorders();
}
}
break;
case EVENT_OBJECT_FOCUS:
@@ -593,6 +695,8 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
default:
break;
}
UpdateDimOverlay();
}
void AlwaysOnTop::RefreshBorders()
@@ -614,6 +718,97 @@ void AlwaysOnTop::RefreshBorders()
}
}
}
UpdateDimOverlay();
}
void AlwaysOnTop::UpdateDimOverlay()
{
if (!m_dimOverlay)
{
return;
}
struct PinnedEntry
{
HWND window = nullptr;
HWND border = nullptr;
};
std::vector<DimOverlayHole> holes;
std::vector<PinnedEntry> pinnedEntries;
holes.reserve(m_topmostWindows.size());
pinnedEntries.reserve(m_topmostWindows.size());
for (const auto& [window, border] : m_topmostWindows)
{
if (!IsWindow(window) || !IsPinned(window))
{
continue;
}
if (!IsWindowVisible(window) || IsIconic(window))
{
continue;
}
if (!m_virtualDesktopUtils.IsWindowOnCurrentDesktop(window))
{
continue;
}
RECT rect{};
if (!TryGetExtendedFrameBounds(window, rect))
{
if (!GetWindowRect(window, &rect))
{
continue;
}
}
int radius = WindowCornerUtils::CornersRadius(window);
if (radius > 0)
{
const float scale = ScalingUtils::ScalingFactor(window);
radius = static_cast<int>(std::lround(radius * scale));
}
else
{
radius = 0;
}
holes.push_back({ rect, radius });
pinnedEntries.push_back({ window, border ? border->Hwnd() : nullptr });
}
const bool visible = !holes.empty();
m_dimOverlay->Update(std::move(holes), visible);
if (!visible)
{
return;
}
const HWND overlayHwnd = m_dimOverlay->Hwnd();
if (!overlayHwnd)
{
return;
}
SetWindowPos(overlayHwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
for (const auto& entry : pinnedEntries)
{
if (entry.border)
{
SetWindowPos(entry.border, overlayHwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
SetWindowPos(entry.window, entry.border, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
else
{
SetWindowPos(entry.window, overlayHwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
}
}
HWND AlwaysOnTop::ResolveTransparencyTargetWindow(HWND window)
@@ -776,4 +971,4 @@ void AlwaysOnTop::RestoreWindowAlpha(HWND window)
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
}
}
}
}

View File

@@ -1,12 +1,14 @@
#pragma once
#include <map>
#include <memory>
#include <Settings.h>
#include <SettingsObserver.h>
#include <Sound.h>
#include <VirtualDesktopUtils.h>
#include <WindowBorder.h>
#include <DimOverlay.h>
#include <common/hooks/WinHookEvent.h>
#include <common/notifications/NotificationUtil.h>
@@ -60,6 +62,8 @@ private:
COLORREF colorKey = 0;
};
std::map<HWND, WindowLayeredState> m_windowOriginalLayeredState{};
std::unique_ptr<DimOverlay> m_dimOverlay;
HANDLE m_hPinEvent;
HANDLE m_hTerminateEvent;
@@ -80,6 +84,7 @@ private:
void SubscribeToEvents();
void ProcessCommand(HWND window);
void MinimizeOtherWindows(HWND pinnedWindow);
void StartTrackingTopmostWindows();
void UnpinAll();
void CleanUp();
@@ -92,6 +97,7 @@ private:
bool UnpinTopmostWindow(HWND window) const noexcept;
bool AssignBorder(HWND window);
void RefreshBorders();
void UpdateDimOverlay();
// Transparency methods
HWND ResolveTransparencyTargetWindow(HWND window);

View File

@@ -121,6 +121,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="AlwaysOnTop.cpp" />
<ClCompile Include="DimOverlay.cpp" />
<ClCompile Include="FrameDrawer.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
@@ -136,6 +137,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="AlwaysOnTop.h" />
<ClInclude Include="DimOverlay.h" />
<ClInclude Include="FrameDrawer.h" />
<ClInclude Include="Generated Files/resource.h" />
<ClInclude Include="ModuleConstants.h" />
@@ -198,4 +200,4 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>
</Project>

View File

@@ -30,6 +30,9 @@
<ClCompile Include="AlwaysOnTop.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DimOverlay.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FrameDrawer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -79,6 +82,9 @@
<ClInclude Include="AlwaysOnTop.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DimOverlay.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FrameDrawer.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -130,4 +136,4 @@
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>
</Project>

View File

@@ -0,0 +1,383 @@
#include "pch.h"
#include "DimOverlay.h"
#include <common/utils/MsWindowsSettings.h>
#include <DispatcherQueue.h>
#include <dwmapi.h>
#include <windows.ui.composition.interop.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.UI.h>
#include <chrono>
#include <algorithm>
#include <cmath>
namespace winrt
{
using namespace winrt::Windows::System;
using namespace winrt::Windows::UI::Composition;
using namespace winrt::Windows::UI::Composition::Desktop;
}
namespace ABI
{
using namespace ABI::Windows::System;
using namespace ABI::Windows::UI::Composition::Desktop;
}
namespace
{
constexpr wchar_t OverlayWindowClassName[] = L"AlwaysOnTop_DimOverlay";
constexpr int FadeDurationMs = 200;
winrt::Windows::UI::Color GetDefaultDimColor()
{
return winrt::Windows::UI::ColorHelper::FromArgb(160, 32, 32, 32);
}
}
DimOverlay::~DimOverlay()
{
Terminate();
}
bool DimOverlay::Initialize(HINSTANCE hinstance)
{
m_hinstance = hinstance;
return CreateWindowAndVisuals();
}
void DimOverlay::Terminate()
{
if (m_destroyed)
{
return;
}
m_destroyed = true;
if (m_hwnd)
{
DestroyWindow(m_hwnd);
m_hwnd = nullptr;
}
if (m_hwndOwner)
{
DestroyWindow(m_hwndOwner);
m_hwndOwner = nullptr;
}
}
HWND DimOverlay::Hwnd() const noexcept
{
return m_hwnd;
}
void DimOverlay::Update(std::vector<DimOverlayHole> holes, bool visible)
{
if (m_destroyed || !m_dispatcherQueueController)
{
return;
}
auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue();
bool enqueueSucceeded = dispatcherQueue.TryEnqueue([this, holes = std::move(holes), visible]() {
if (m_destroyed || !m_hwnd)
{
return;
}
m_lastHoles = holes;
UpdateWindowBounds();
UpdateRegion(holes);
SetVisible(visible);
});
if (!enqueueSucceeded)
{
Logger::error("Couldn't enqueue message to update the dim overlay.");
}
}
LRESULT CALLBACK DimOverlay::WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) noexcept
{
auto thisRef = reinterpret_cast<DimOverlay*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if ((thisRef == nullptr) && (message == WM_CREATE))
{
const auto createStruct = reinterpret_cast<LPCREATESTRUCT>(lparam);
thisRef = static_cast<DimOverlay*>(createStruct->lpCreateParams);
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(thisRef));
}
return thisRef ? thisRef->MessageHandler(hwnd, message, wparam, lparam) : DefWindowProc(hwnd, message, wparam, lparam);
}
LRESULT DimOverlay::MessageHandler(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) noexcept
{
switch (message)
{
case WM_DISPLAYCHANGE:
UpdateWindowBounds();
UpdateRegion(m_lastHoles);
break;
case WM_NCHITTEST:
return HTTRANSPARENT;
case WM_ERASEBKGND:
return TRUE;
case WM_NCDESTROY:
SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
break;
default:
break;
}
return DefWindowProc(hwnd, message, wparam, lparam);
}
bool DimOverlay::CreateWindowAndVisuals()
{
WNDCLASS wc{};
if (!GetClassInfoW(m_hinstance, OverlayWindowClassName, &wc))
{
wc.lpfnWndProc = DimOverlay::WndProc;
wc.hInstance = m_hinstance;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = static_cast<HBRUSH>(GetStockObject(NULL_BRUSH));
wc.lpszClassName = OverlayWindowClassName;
if (!RegisterClassW(&wc))
{
Logger::error("Failed to register DimOverlay window class. GetLastError={}", GetLastError());
return false;
}
}
m_hwndOwner = CreateWindow(L"static", nullptr, WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, m_hinstance, nullptr);
if (!m_hwndOwner)
{
Logger::error("Failed to create DimOverlay owner window. GetLastError={}", GetLastError());
return false;
}
const DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP |
WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_TOPMOST;
m_hwnd = CreateWindowExW(exStyle,
OverlayWindowClassName,
L"PowerToys Always On Top Dim Overlay",
WS_POPUP,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,
m_hwndOwner,
nullptr,
m_hinstance,
this);
if (!m_hwnd)
{
Logger::error("Failed to create DimOverlay window. GetLastError={}", GetLastError());
return false;
}
BOOL excludeFromPeek = TRUE;
DwmSetWindowAttribute(m_hwnd, DWMWA_EXCLUDED_FROM_PEEK, &excludeFromPeek, sizeof(excludeFromPeek));
UpdateWindowBounds();
if (!EnsureDispatcherQueue())
{
return false;
}
if (!EnsureCompositor())
{
return false;
}
return true;
}
bool DimOverlay::EnsureDispatcherQueue()
{
if (m_dispatcherQueueController)
{
return true;
}
DispatcherQueueOptions options = {
sizeof(DispatcherQueueOptions),
DQTYPE_THREAD_CURRENT,
DQTAT_COM_NONE,
};
ABI::IDispatcherQueueController* controller = nullptr;
const HRESULT hr = CreateDispatcherQueueController(options, &controller);
if (FAILED(hr))
{
Logger::error("Failed to create DispatcherQueueController for DimOverlay. HRESULT={:#x}", hr);
return false;
}
*winrt::put_abi(m_dispatcherQueueController) = controller;
return true;
}
bool DimOverlay::EnsureCompositor()
{
try
{
m_compositor = winrt::Compositor();
ABI::IDesktopWindowTarget* target = nullptr;
winrt::check_hresult(m_compositor.as<ABI::ICompositorDesktopInterop>()->CreateDesktopWindowTarget(m_hwnd, false, &target));
*winrt::put_abi(m_target) = target;
m_root = m_compositor.CreateContainerVisual();
m_root.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_target.Root(m_root);
m_dim = m_compositor.CreateSpriteVisual();
m_dim.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_dimBrush = m_compositor.CreateColorBrush(GetDefaultDimColor());
m_dim.Brush(m_dimBrush);
m_root.Children().InsertAtTop(m_dim);
m_root.Opacity(0.0f);
m_opacityAnimation = m_compositor.CreateScalarKeyFrameAnimation();
m_opacityAnimation.Target(L"Opacity");
m_opacityAnimation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue");
BOOL animationsEnabled = GetAnimationsEnabled();
m_opacityAnimation.Duration(std::chrono::milliseconds{ animationsEnabled ? FadeDurationMs : 1 });
auto implicitAnimations = m_compositor.CreateImplicitAnimationCollection();
implicitAnimations.Insert(L"Opacity", m_opacityAnimation);
m_root.ImplicitAnimations(implicitAnimations);
}
catch (const winrt::hresult_error& e)
{
Logger::error("Failed to create DimOverlay composition resources: {}", winrt::to_string(e.message()));
return false;
}
return true;
}
void DimOverlay::UpdateWindowBounds()
{
if (!m_hwnd)
{
return;
}
RECT newBounds{};
newBounds.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
newBounds.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
newBounds.right = newBounds.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
newBounds.bottom = newBounds.top + GetSystemMetrics(SM_CYVIRTUALSCREEN);
if (EqualRect(&newBounds, &m_virtualBounds))
{
return;
}
m_virtualBounds = newBounds;
const int width = m_virtualBounds.right - m_virtualBounds.left;
const int height = m_virtualBounds.bottom - m_virtualBounds.top;
SetWindowPos(m_hwnd, nullptr, m_virtualBounds.left, m_virtualBounds.top, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
}
void DimOverlay::UpdateRegion(const std::vector<DimOverlayHole>& holes)
{
if (!m_hwnd)
{
return;
}
const int width = m_virtualBounds.right - m_virtualBounds.left;
const int height = m_virtualBounds.bottom - m_virtualBounds.top;
if (width <= 0 || height <= 0)
{
return;
}
HRGN baseRegion = CreateRectRgn(0, 0, width, height);
if (!baseRegion)
{
return;
}
RECT bounds{ 0, 0, width, height };
for (const auto& hole : holes)
{
RECT local = hole.rect;
OffsetRect(&local, -m_virtualBounds.left, -m_virtualBounds.top);
if (!IntersectRect(&local, &local, &bounds))
{
continue;
}
if (local.right <= local.left || local.bottom <= local.top)
{
continue;
}
int radius = (std::max)(0, hole.radius);
const int maxRadius = (std::min)((local.right - local.left) / 2, (local.bottom - local.top) / 2);
radius = (std::min)(radius, maxRadius);
HRGN holeRegion = nullptr;
if (radius > 0)
{
const int diameter = radius * 2;
holeRegion = CreateRoundRectRgn(local.left, local.top, local.right, local.bottom, diameter, diameter);
}
else
{
holeRegion = CreateRectRgn(local.left, local.top, local.right, local.bottom);
}
if (holeRegion)
{
CombineRgn(baseRegion, baseRegion, holeRegion, RGN_DIFF);
DeleteObject(holeRegion);
}
}
if (SetWindowRgn(m_hwnd, baseRegion, TRUE) == 0)
{
DeleteObject(baseRegion);
}
}
void DimOverlay::SetVisible(bool visible)
{
if (m_visible == visible)
{
return;
}
if (!m_root || !m_compositor)
{
return;
}
m_visible = visible;
if (visible)
{
ShowWindow(m_hwnd, SW_SHOWNOACTIVATE);
}
auto batch = m_compositor.CreateScopedBatch(winrt::CompositionBatchTypes::Animation);
m_root.Opacity(visible ? 1.0f : 0.0f);
batch.Completed([hwnd = m_hwnd, visible](auto&&, auto&&) {
if (!visible)
{
ShowWindow(hwnd, SW_HIDE);
}
});
batch.End();
}

View File

@@ -0,0 +1,56 @@
#pragma once
#include <windows.h>
#include <vector>
#include <winrt/Windows.System.h>
#include <winrt/Windows.UI.Composition.h>
#include <winrt/Windows.UI.Composition.Desktop.h>
struct DimOverlayHole
{
RECT rect{};
int radius = 0;
};
class DimOverlay
{
public:
DimOverlay() = default;
~DimOverlay();
bool Initialize(HINSTANCE hinstance);
void Terminate();
void Update(std::vector<DimOverlayHole> holes, bool visible);
HWND Hwnd() const noexcept;
private:
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
LRESULT MessageHandler(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
bool CreateWindowAndVisuals();
bool EnsureDispatcherQueue();
bool EnsureCompositor();
void UpdateWindowBounds();
void UpdateRegion(const std::vector<DimOverlayHole>& holes);
void SetVisible(bool visible);
HINSTANCE m_hinstance{};
HWND m_hwndOwner{};
HWND m_hwnd{};
RECT m_virtualBounds{};
bool m_visible = false;
bool m_destroyed = false;
winrt::Windows::System::DispatcherQueueController m_dispatcherQueueController{ nullptr };
winrt::Windows::UI::Composition::Compositor m_compositor{ nullptr };
winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget m_target{ nullptr };
winrt::Windows::UI::Composition::ContainerVisual m_root{ nullptr };
winrt::Windows::UI::Composition::SpriteVisual m_dim{ nullptr };
winrt::Windows::UI::Composition::CompositionColorBrush m_dimBrush{ nullptr };
winrt::Windows::UI::Composition::ScalarKeyFrameAnimation m_opacityAnimation{ nullptr };
std::vector<DimOverlayHole> m_lastHoles;
};

View File

@@ -2,6 +2,8 @@
#include <SettingsObserver.h>
#include <memory>
class FrameDrawer;
class WindowBorder : public SettingsObserver
@@ -15,6 +17,7 @@ public:
void UpdateBorderPosition() const;
void UpdateBorderProperties() const;
HWND Hwnd() const noexcept { return m_window; }
protected:
static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept