mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +01:00
Compare commits
4 Commits
dev/vanzue
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1986611c1 | ||
|
|
71d55ac50f | ||
|
|
2be4c4eb46 | ||
|
|
731532fdd8 |
@@ -6,14 +6,15 @@
|
||||
|
||||
// Get URL parameters:
|
||||
// `code` contains the code of the file in base64 encoded
|
||||
// `theme` can be "vs" for light theme or "vs-dark" for dark theme
|
||||
// `theme` can be "vs" for light theme, "vs-dark" for dark theme,
|
||||
// "hc-black" for dark high contrast, or "hc-light" for light high contrast
|
||||
// `lang` is the language of the file
|
||||
// `wrap` if the editor is wrapping or not
|
||||
// `minimap` if the minimap is shown
|
||||
// `contextMenu` whether to use the Monaco context menu. The built-in context menu
|
||||
// doesn't work in Peek, so we set this to false and create a custom one
|
||||
|
||||
var theme = ("[[PT_THEME]]" == "dark") ? "vs-dark" : "vs";
|
||||
var theme = "[[PT_THEME]]";
|
||||
var wrap = [[PT_WRAP]];
|
||||
var minimap = [[PT_MINIMAP]];
|
||||
var stickyScroll = [[PT_STICKY_SCROLL]];
|
||||
@@ -87,13 +88,20 @@
|
||||
require(['vs/editor/editor.main'], async function () {
|
||||
await registerAdditionalLanguages(monaco)
|
||||
|
||||
// Creates a theme to handle custom tokens
|
||||
monaco.editor.defineTheme('theme', {
|
||||
base: theme, // Sets the base theme to "vs" or "vs-dark" depending on the user's preference
|
||||
inherit: true,
|
||||
rules: customTokenThemeRules,
|
||||
colors: {} // `colors` is a required attribute
|
||||
});
|
||||
// For high contrast themes, use Monaco's built-in themes directly
|
||||
// For normal themes, create a custom theme to handle custom tokens
|
||||
var editorTheme = theme;
|
||||
if (theme === 'vs' || theme === 'vs-dark') {
|
||||
// Creates a theme to handle custom tokens
|
||||
monaco.editor.defineTheme('custom-theme', {
|
||||
base: theme, // Sets the base theme to "vs" or "vs-dark" depending on the user's preference
|
||||
inherit: true,
|
||||
rules: customTokenThemeRules,
|
||||
colors: {} // `colors` is a required attribute
|
||||
});
|
||||
editorTheme = 'custom-theme';
|
||||
}
|
||||
// For hc-black and hc-light, use the built-in Monaco themes which have proper contrast
|
||||
|
||||
// Creates the editor
|
||||
// For all parameters: https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html
|
||||
@@ -101,7 +109,7 @@
|
||||
value: code, // Sets content of the editor
|
||||
language: lang, // Sets language of the code
|
||||
readOnly: true, // Sets to readonly
|
||||
theme: 'theme', // Sets editor theme
|
||||
theme: editorTheme, // Sets editor theme
|
||||
minimap: { enabled: minimap }, // Controls if minimap is shown
|
||||
lineNumbersMinChars: 3, // Width of the line numbers
|
||||
contextmenu: contextMenu,
|
||||
|
||||
@@ -113,6 +113,26 @@ namespace Common.UI
|
||||
return ControlzEx.Theming.WindowsThemeHelper.GetWindowsBaseColor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Monaco theme name based on Windows theme and high contrast mode.
|
||||
/// </summary>
|
||||
/// <returns>Monaco theme name: "hc-black", "hc-light", "vs-dark", or "vs".</returns>
|
||||
public static string GetMonacoTheme()
|
||||
{
|
||||
if (ControlzEx.Theming.WindowsThemeHelper.IsHighContrastEnabled())
|
||||
{
|
||||
// In high contrast mode, check if it's a dark or light high contrast theme
|
||||
string baseColor = ControlzEx.Theming.WindowsThemeHelper.GetWindowsBaseColor();
|
||||
return baseColor.Equals("Dark", StringComparison.OrdinalIgnoreCase) ? "hc-black" : "hc-light";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal mode: use standard themes
|
||||
string baseColor = ControlzEx.Theming.WindowsThemeHelper.GetWindowsBaseColor();
|
||||
return baseColor.Equals("Dark", StringComparison.OrdinalIgnoreCase) ? "vs-dark" : "vs";
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeTheme(Theme theme, bool fromSettings = false)
|
||||
{
|
||||
if (fromSettings)
|
||||
|
||||
@@ -163,8 +163,22 @@ void CursorWrapCore::UpdateMonitorInfo()
|
||||
Logger::info(L"======= UPDATE MONITOR INFO END =======");
|
||||
}
|
||||
|
||||
POINT CursorWrapCore::HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode)
|
||||
POINT CursorWrapCore::HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode, bool disableOnSingleMonitor)
|
||||
{
|
||||
// Check if wrapping should be disabled on single monitor
|
||||
if (disableOnSingleMonitor && m_monitors.size() <= 1)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
static bool loggedOnce = false;
|
||||
if (!loggedOnce)
|
||||
{
|
||||
OutputDebugStringW(L"[CursorWrap] Single monitor detected - cursor wrapping disabled\n");
|
||||
loggedOnce = true;
|
||||
}
|
||||
#endif
|
||||
return currentPos;
|
||||
}
|
||||
|
||||
// Check if wrapping should be disabled during drag
|
||||
if (disableWrapDuringDrag && (GetAsyncKeyState(VK_LBUTTON) & 0x8000))
|
||||
{
|
||||
|
||||
@@ -18,9 +18,11 @@ public:
|
||||
|
||||
// Handle mouse move with wrap mode filtering
|
||||
// wrapMode: 0=Both, 1=VerticalOnly, 2=HorizontalOnly
|
||||
POINT HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode);
|
||||
// disableOnSingleMonitor: if true, cursor wrapping is disabled when only one monitor is connected
|
||||
POINT HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode, bool disableOnSingleMonitor);
|
||||
|
||||
const std::vector<MonitorInfo>& GetMonitors() const { return m_monitors; }
|
||||
size_t GetMonitorCount() const { return m_monitors.size(); }
|
||||
const MonitorTopology& GetTopology() const { return m_topology; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace
|
||||
const wchar_t JSON_KEY_AUTO_ACTIVATE[] = L"auto_activate";
|
||||
const wchar_t JSON_KEY_DISABLE_WRAP_DURING_DRAG[] = L"disable_wrap_during_drag";
|
||||
const wchar_t JSON_KEY_WRAP_MODE[] = L"wrap_mode";
|
||||
const wchar_t JSON_KEY_DISABLE_ON_SINGLE_MONITOR[] = L"disable_cursor_wrap_on_single_monitor";
|
||||
}
|
||||
|
||||
// The PowerToy name that will be shown in the settings.
|
||||
@@ -80,6 +81,7 @@ private:
|
||||
bool m_enabled = false;
|
||||
bool m_autoActivate = false;
|
||||
bool m_disableWrapDuringDrag = true; // Default to true to prevent wrap during drag
|
||||
bool m_disableOnSingleMonitor = false; // Default to false
|
||||
int m_wrapMode = 0; // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly
|
||||
|
||||
// Mouse hook
|
||||
@@ -196,6 +198,10 @@ public:
|
||||
// Start listening for external trigger event so we can invoke the same logic as the activation hotkey.
|
||||
m_triggerEventHandle = CreateEventW(nullptr, false, false, CommonSharedConstants::CURSOR_WRAP_TRIGGER_EVENT);
|
||||
m_terminateEventHandle = CreateEventW(nullptr, false, false, nullptr);
|
||||
if (m_triggerEventHandle)
|
||||
{
|
||||
ResetEvent(m_triggerEventHandle);
|
||||
}
|
||||
if (m_triggerEventHandle && m_terminateEventHandle)
|
||||
{
|
||||
m_listening = true;
|
||||
@@ -210,8 +216,16 @@ public:
|
||||
// Create message window for display change notifications
|
||||
RegisterForDisplayChanges();
|
||||
|
||||
StartMouseHook();
|
||||
Logger::info("CursorWrap enabled - mouse hook started");
|
||||
// Only start the mouse hook automatically if auto-activate is enabled
|
||||
if (m_autoActivate)
|
||||
{
|
||||
StartMouseHook();
|
||||
Logger::info("CursorWrap enabled - mouse hook started (auto-activate on)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info("CursorWrap enabled - waiting for activation hotkey (auto-activate off)");
|
||||
}
|
||||
|
||||
while (m_listening)
|
||||
{
|
||||
@@ -415,6 +429,21 @@ private:
|
||||
{
|
||||
Logger::warn("Failed to initialize CursorWrap wrap mode from settings. Will use default value (0=Both)");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Parse disable on single monitor
|
||||
auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
|
||||
if (propertiesObject.HasKey(JSON_KEY_DISABLE_ON_SINGLE_MONITOR))
|
||||
{
|
||||
auto disableOnSingleMonitorObject = propertiesObject.GetNamedObject(JSON_KEY_DISABLE_ON_SINGLE_MONITOR);
|
||||
m_disableOnSingleMonitor = disableOnSingleMonitorObject.GetNamedBoolean(JSON_KEY_VALUE);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::warn("Failed to initialize CursorWrap disable on single monitor from settings. Will use default value (false)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -646,7 +675,8 @@ private:
|
||||
POINT newPos = g_cursorWrapInstance->m_core.HandleMouseMove(
|
||||
currentPos,
|
||||
g_cursorWrapInstance->m_disableWrapDuringDrag,
|
||||
g_cursorWrapInstance->m_wrapMode);
|
||||
g_cursorWrapInstance->m_wrapMode,
|
||||
g_cursorWrapInstance->m_disableOnSingleMonitor);
|
||||
|
||||
if (newPos.x != currentPos.x || newPos.y != currentPos.y)
|
||||
{
|
||||
|
||||
@@ -16,11 +16,6 @@
|
||||
#include <trace.h>
|
||||
#include <WinHookEventIDs.h>
|
||||
|
||||
#include <dwmapi.h>
|
||||
#include <ScalingUtils.h>
|
||||
#include <WindowCornersUtil.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace NonLocalizable
|
||||
{
|
||||
@@ -36,14 +31,6 @@ 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)),
|
||||
@@ -61,13 +48,6 @@ 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();
|
||||
|
||||
@@ -144,7 +124,6 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
|
||||
iter.second = nullptr;
|
||||
}
|
||||
}
|
||||
UpdateDimOverlay();
|
||||
}
|
||||
break;
|
||||
case SettingId::ExcludeApps:
|
||||
@@ -163,7 +142,6 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
|
||||
{
|
||||
m_topmostWindows.erase(window);
|
||||
}
|
||||
UpdateDimOverlay();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -238,72 +216,17 @@ 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>;
|
||||
@@ -339,7 +262,6 @@ void AlwaysOnTop::StartTrackingTopmostWindows()
|
||||
AssignBorder(window);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool AlwaysOnTop::AssignBorder(HWND window)
|
||||
@@ -508,18 +430,11 @@ 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);
|
||||
@@ -577,13 +492,11 @@ bool AlwaysOnTop::IsTracked(HWND window) const noexcept
|
||||
|
||||
void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
|
||||
{
|
||||
if (!data->hwnd)
|
||||
if (!AlwaysOnTopSettings::settings().enableFrame || !data->hwnd)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool frameEnabled = AlwaysOnTopSettings::settings().enableFrame;
|
||||
|
||||
std::vector<HWND> toErase{};
|
||||
for (const auto& [window, border] : m_topmostWindows)
|
||||
{
|
||||
@@ -607,29 +520,23 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
|
||||
{
|
||||
case EVENT_OBJECT_LOCATIONCHANGE:
|
||||
{
|
||||
if (frameEnabled)
|
||||
auto iter = m_topmostWindows.find(data->hwnd);
|
||||
if (iter != m_topmostWindows.end())
|
||||
{
|
||||
auto iter = m_topmostWindows.find(data->hwnd);
|
||||
if (iter != m_topmostWindows.end())
|
||||
const auto& border = iter->second;
|
||||
if (border)
|
||||
{
|
||||
const auto& border = iter->second;
|
||||
if (border)
|
||||
{
|
||||
border->UpdateBorderPosition();
|
||||
}
|
||||
border->UpdateBorderPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EVENT_SYSTEM_MINIMIZESTART:
|
||||
{
|
||||
if (frameEnabled)
|
||||
auto iter = m_topmostWindows.find(data->hwnd);
|
||||
if (iter != m_topmostWindows.end())
|
||||
{
|
||||
auto iter = m_topmostWindows.find(data->hwnd);
|
||||
if (iter != m_topmostWindows.end())
|
||||
{
|
||||
m_topmostWindows[data->hwnd] = nullptr;
|
||||
}
|
||||
m_topmostWindows[data->hwnd] = nullptr;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -639,26 +546,20 @@ 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);
|
||||
if (frameEnabled)
|
||||
{
|
||||
AssignBorder(data->hwnd);
|
||||
}
|
||||
PinTopmostWindow(data->hwnd);
|
||||
AssignBorder(data->hwnd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EVENT_SYSTEM_MOVESIZEEND:
|
||||
{
|
||||
if (frameEnabled)
|
||||
auto iter = m_topmostWindows.find(data->hwnd);
|
||||
if (iter != m_topmostWindows.end())
|
||||
{
|
||||
auto iter = m_topmostWindows.find(data->hwnd);
|
||||
if (iter != m_topmostWindows.end())
|
||||
const auto& border = iter->second;
|
||||
if (border)
|
||||
{
|
||||
const auto& border = iter->second;
|
||||
if (border)
|
||||
{
|
||||
border->UpdateBorderPosition();
|
||||
}
|
||||
border->UpdateBorderPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -672,10 +573,7 @@ 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));
|
||||
}
|
||||
if (frameEnabled)
|
||||
{
|
||||
RefreshBorders();
|
||||
}
|
||||
RefreshBorders();
|
||||
}
|
||||
break;
|
||||
case EVENT_OBJECT_FOCUS:
|
||||
@@ -695,8 +593,6 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateDimOverlay();
|
||||
}
|
||||
|
||||
void AlwaysOnTop::RefreshBorders()
|
||||
@@ -718,97 +614,6 @@ 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)
|
||||
@@ -971,4 +776,4 @@ void AlwaysOnTop::RestoreWindowAlpha(HWND window)
|
||||
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
#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>
|
||||
@@ -62,8 +60,6 @@ private:
|
||||
COLORREF colorKey = 0;
|
||||
};
|
||||
std::map<HWND, WindowLayeredState> m_windowOriginalLayeredState{};
|
||||
|
||||
std::unique_ptr<DimOverlay> m_dimOverlay;
|
||||
|
||||
HANDLE m_hPinEvent;
|
||||
HANDLE m_hTerminateEvent;
|
||||
@@ -84,7 +80,6 @@ private:
|
||||
void SubscribeToEvents();
|
||||
|
||||
void ProcessCommand(HWND window);
|
||||
void MinimizeOtherWindows(HWND pinnedWindow);
|
||||
void StartTrackingTopmostWindows();
|
||||
void UnpinAll();
|
||||
void CleanUp();
|
||||
@@ -97,7 +92,6 @@ private:
|
||||
bool UnpinTopmostWindow(HWND window) const noexcept;
|
||||
bool AssignBorder(HWND window);
|
||||
void RefreshBorders();
|
||||
void UpdateDimOverlay();
|
||||
|
||||
// Transparency methods
|
||||
HWND ResolveTransparencyTargetWindow(HWND window);
|
||||
|
||||
@@ -121,7 +121,6 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AlwaysOnTop.cpp" />
|
||||
<ClCompile Include="DimOverlay.cpp" />
|
||||
<ClCompile Include="FrameDrawer.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
@@ -137,7 +136,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AlwaysOnTop.h" />
|
||||
<ClInclude Include="DimOverlay.h" />
|
||||
<ClInclude Include="FrameDrawer.h" />
|
||||
<ClInclude Include="Generated Files/resource.h" />
|
||||
<ClInclude Include="ModuleConstants.h" />
|
||||
@@ -200,4 +198,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,9 +30,6 @@
|
||||
<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>
|
||||
@@ -82,9 +79,6 @@
|
||||
<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>
|
||||
@@ -136,4 +130,4 @@
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -1,383 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#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,8 +2,6 @@
|
||||
|
||||
#include <SettingsObserver.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class FrameDrawer;
|
||||
|
||||
class WindowBorder : public SettingsObserver
|
||||
@@ -17,7 +15,6 @@ 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
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Peek.FilePreviewer.Previewers
|
||||
}
|
||||
|
||||
string base64FileCode = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(fileContent));
|
||||
string theme = ThemeManager.GetWindowsBaseColor().ToLowerInvariant();
|
||||
string theme = ThemeManager.GetMonacoTheme();
|
||||
|
||||
// prepping index html to load in
|
||||
string html = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.ReadIndexHtml();
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco
|
||||
/// <returns>Theme that should be used.</returns>
|
||||
public static string GetTheme()
|
||||
{
|
||||
return Common.UI.ThemeManager.GetWindowsBaseColor().ToLowerInvariant();
|
||||
return Common.UI.ThemeManager.GetMonacoTheme();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using Microsoft.Web.WebView2.Core;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.System;
|
||||
using Windows.UI;
|
||||
using Windows.UI.ViewManagement;
|
||||
|
||||
namespace RegistryPreviewUILib
|
||||
{
|
||||
@@ -148,10 +149,35 @@ namespace RegistryPreviewUILib
|
||||
|
||||
private async Task SetThemeAsync()
|
||||
{
|
||||
var theme = Application.Current.RequestedTheme == ApplicationTheme.Light ? "vs" : "vs-dark";
|
||||
var theme = GetMonacoTheme();
|
||||
await Browser.CoreWebView2.ExecuteScriptAsync($"monaco.editor.setTheme('{theme}')");
|
||||
}
|
||||
|
||||
private static string GetMonacoTheme()
|
||||
{
|
||||
var uiSettings = new UISettings();
|
||||
var highContrast = uiSettings.HighContrast;
|
||||
|
||||
if (highContrast)
|
||||
{
|
||||
// In high contrast mode, check if it's a dark or light high contrast theme
|
||||
var foreground = uiSettings.GetColorValue(UIColorType.Foreground);
|
||||
var background = uiSettings.GetColorValue(UIColorType.Background);
|
||||
|
||||
// Determine if it's a dark theme by comparing luminance
|
||||
// Dark themes have light foreground and dark background
|
||||
var foregroundLuminance = (0.299 * foreground.R + 0.587 * foreground.G + 0.114 * foreground.B) / 255;
|
||||
var backgroundLuminance = (0.299 * background.R + 0.587 * background.G + 0.114 * background.B) / 255;
|
||||
|
||||
return backgroundLuminance < foregroundLuminance ? "hc-black" : "hc-light";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal mode: use standard themes based on app theme
|
||||
return Application.Current.RequestedTheme == ApplicationTheme.Light ? "vs" : "vs-dark";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTextChangedThrottleElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (_textChangedThrottled)
|
||||
|
||||
@@ -25,12 +25,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonPropertyName("wrap_mode")]
|
||||
public IntProperty WrapMode { get; set; }
|
||||
|
||||
[JsonPropertyName("disable_cursor_wrap_on_single_monitor")]
|
||||
public BoolProperty DisableCursorWrapOnSingleMonitor { get; set; }
|
||||
|
||||
public CursorWrapProperties()
|
||||
{
|
||||
ActivationShortcut = DefaultActivationShortcut;
|
||||
AutoActivate = new BoolProperty(false);
|
||||
DisableWrapDuringDrag = new BoolProperty(true);
|
||||
WrapMode = new IntProperty(0); // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly
|
||||
DisableCursorWrapOnSingleMonitor = new BoolProperty(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
settingsUpgraded = true;
|
||||
}
|
||||
|
||||
// Add DisableCursorWrapOnSingleMonitor property if it doesn't exist (for users upgrading from older versions)
|
||||
if (Properties.DisableCursorWrapOnSingleMonitor == null)
|
||||
{
|
||||
Properties.DisableCursorWrapOnSingleMonitor = new BoolProperty(false); // Default to false
|
||||
settingsUpgraded = true;
|
||||
}
|
||||
|
||||
return settingsUpgraded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@
|
||||
<ComboBoxItem x:Uid="MouseUtils_CursorWrap_WrapMode_HorizontalOnly" />
|
||||
</ComboBox>
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
|
||||
<CheckBox x:Uid="MouseUtils_CursorWrap_DisableOnSingleMonitor" IsChecked="{x:Bind ViewModel.CursorWrapDisableOnSingleMonitor, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
@@ -2726,6 +2726,9 @@ From there, simply click on one of the supported files in the File Explorer and
|
||||
<data name="MouseUtils_CursorWrap_DisableWrapDuringDrag.Content" xml:space="preserve">
|
||||
<value>Disable wrapping while dragging</value>
|
||||
</data>
|
||||
<data name="MouseUtils_CursorWrap_DisableOnSingleMonitor.Content" xml:space="preserve">
|
||||
<value>Disable wrapping when using a single monitor</value>
|
||||
</data>
|
||||
<data name="MouseUtils_CursorWrap_AutoActivate.Header" xml:space="preserve">
|
||||
<value>Auto-activate on startup</value>
|
||||
</data>
|
||||
|
||||
@@ -116,6 +116,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
// Null-safe access in case property wasn't upgraded yet - default to 0 (Both)
|
||||
_cursorWrapWrapMode = CursorWrapSettingsConfig.Properties.WrapMode?.Value ?? 0;
|
||||
|
||||
// Null-safe access in case property wasn't upgraded yet - default to false
|
||||
_cursorWrapDisableOnSingleMonitor = CursorWrapSettingsConfig.Properties.DisableCursorWrapOnSingleMonitor?.Value ?? false;
|
||||
|
||||
int isEnabled = 0;
|
||||
|
||||
Utilities.NativeMethods.SystemParametersInfo(Utilities.NativeMethods.SPI_GETCLIENTAREAANIMATION, 0, ref isEnabled, 0);
|
||||
@@ -1003,13 +1006,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
GeneralSettingsConfig.Enabled.CursorWrap = value;
|
||||
OnPropertyChanged(nameof(IsCursorWrapEnabled));
|
||||
|
||||
// Auto-enable the AutoActivate setting when CursorWrap is enabled
|
||||
// This ensures cursor wrapping is active immediately after enabling
|
||||
if (value && !_cursorWrapAutoActivate)
|
||||
{
|
||||
CursorWrapAutoActivate = true;
|
||||
}
|
||||
|
||||
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
SendConfigMSG(outgoing.ToString());
|
||||
|
||||
@@ -1114,6 +1110,34 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool CursorWrapDisableOnSingleMonitor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cursorWrapDisableOnSingleMonitor;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _cursorWrapDisableOnSingleMonitor)
|
||||
{
|
||||
_cursorWrapDisableOnSingleMonitor = value;
|
||||
|
||||
// Ensure the property exists before setting value
|
||||
if (CursorWrapSettingsConfig.Properties.DisableCursorWrapOnSingleMonitor == null)
|
||||
{
|
||||
CursorWrapSettingsConfig.Properties.DisableCursorWrapOnSingleMonitor = new BoolProperty(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
CursorWrapSettingsConfig.Properties.DisableCursorWrapOnSingleMonitor.Value = value;
|
||||
}
|
||||
|
||||
NotifyCursorWrapPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyCursorWrapPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
@@ -1186,5 +1210,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
private bool _cursorWrapAutoActivate;
|
||||
private bool _cursorWrapDisableWrapDuringDrag; // Will be initialized in constructor from settings
|
||||
private int _cursorWrapWrapMode; // 0=Both, 1=VerticalOnly, 2=HorizontalOnly
|
||||
private bool _cursorWrapDisableOnSingleMonitor; // Disable cursor wrap when only one monitor is connected
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user