mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-05 19:00:07 +01:00
Compare commits
2 Commits
main
...
dev/vanzue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
158ada435d | ||
|
|
52bf804f57 |
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
383
src/modules/alwaysontop/AlwaysOnTop/DimOverlay.cpp
Normal file
383
src/modules/alwaysontop/AlwaysOnTop/DimOverlay.cpp
Normal 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();
|
||||
}
|
||||
56
src/modules/alwaysontop/AlwaysOnTop/DimOverlay.h
Normal file
56
src/modules/alwaysontop/AlwaysOnTop/DimOverlay.h
Normal 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;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user