Compare commits

...

1 Commits

Author SHA1 Message Date
vanzue
d015b96eea POC for Always On Top window transparency 2026-01-14 10:00:04 +08:00
16 changed files with 349 additions and 89 deletions

View File

@@ -70,6 +70,8 @@ namespace CommonSharedConstants
// Path to the event used by AlwaysOnTop
const wchar_t ALWAYS_ON_TOP_PIN_EVENT[] = L"Local\\AlwaysOnTopPinEvent-892e0aa2-cfa8-4cc4-b196-ddeb32314ce8";
const wchar_t ALWAYS_ON_TOP_TRANSPARENT_PIN_EVENT[] = L"Local\\AlwaysOnTopTransparentPinEvent-a]bc123-4567-89ab-cdef01234567";
const wchar_t ALWAYS_ON_TOP_TERMINATE_EVENT[] = L"Local\\AlwaysOnTopTerminateEvent-cfdf1eae-791f-4953-8021-2f18f3837eae";
// Path to the event used by PowerAccent

View File

@@ -85,3 +85,72 @@ inline T GetWindowParam(HWND window)
{
return reinterpret_cast<T>(GetWindowLongPtrW(window, GWLP_USERDATA));
}
// Structure to store window transparency properties for restoration
struct WindowTransparencyProperties
{
long exstyle = 0;
COLORREF crKey = RGB(0, 0, 0);
DWORD dwFlags = 0;
BYTE alpha = 0;
bool transparencySet = false;
};
// Makes a window transparent by setting layered window attributes
// alphaPercent: transparency level from 0-100 (50 = 50% transparent)
// Returns the saved properties that can be used to restore the window later
inline WindowTransparencyProperties MakeWindowTransparent(HWND window, int alphaPercent = 50)
{
WindowTransparencyProperties props{};
if (!window || alphaPercent < 0 || alphaPercent > 100)
{
return props;
}
props.exstyle = GetWindowLong(window, GWL_EXSTYLE);
// Add WS_EX_LAYERED style to enable transparency
SetWindowLong(window, GWL_EXSTYLE, props.exstyle | WS_EX_LAYERED);
// Get current layered window attributes
if (!GetLayeredWindowAttributes(window, &props.crKey, &props.alpha, &props.dwFlags))
{
return props;
}
// Set new transparency level
BYTE alphaValue = static_cast<BYTE>((255 * alphaPercent) / 100);
if (!SetLayeredWindowAttributes(window, 0, alphaValue, LWA_ALPHA))
{
return props;
}
props.transparencySet = true;
return props;
}
// Restores window transparency to its original state
inline bool RestoreWindowTransparency(HWND window, const WindowTransparencyProperties& props)
{
if (!window || !props.transparencySet)
{
return false;
}
bool success = true;
// Restore original transparency attributes
if (!SetLayeredWindowAttributes(window, props.crKey, props.alpha, props.dwFlags))
{
success = false;
}
// Restore original extended style
if (SetWindowLong(window, GWL_EXSTYLE, props.exstyle) == 0)
{
success = false;
}
return success;
}

View File

@@ -32,7 +32,7 @@ bool isExcluded(HWND window)
}
AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) :
SettingsObserver({SettingId::FrameEnabled, SettingId::Hotkey, SettingId::ExcludeApps}),
SettingsObserver({SettingId::FrameEnabled, SettingId::Hotkey, SettingId::TransparencyHotkey, SettingId::ExcludeApps}),
m_hinstance(reinterpret_cast<HINSTANCE>(&__ImageBase)),
m_useCentralizedLLKH(useLLKH),
m_mainThreadId(mainThreadId),
@@ -105,6 +105,11 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
RegisterHotkey();
}
break;
case SettingId::TransparencyHotkey:
{
RegisterHotkey();
}
break;
case SettingId::FrameEnabled:
{
if (AlwaysOnTopSettings::settings().enableFrame)
@@ -153,9 +158,17 @@ LRESULT AlwaysOnTop::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lp
{
if (message == WM_HOTKEY)
{
int hotkeyId = static_cast<int>(wparam);
if (HWND fw{ GetForegroundWindow() })
{
ProcessCommand(fw);
if (hotkeyId == static_cast<int>(HotkeyId::Pin))
{
ProcessCommand(fw, false);
}
else if (hotkeyId == static_cast<int>(HotkeyId::TransparentPin))
{
ProcessCommand(fw, true);
}
}
}
else if (message == WM_PRIV_SETTINGS_CHANGED)
@@ -166,7 +179,7 @@ LRESULT AlwaysOnTop::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lp
return 0;
}
void AlwaysOnTop::ProcessCommand(HWND window)
void AlwaysOnTop::ProcessCommand(HWND window, bool transparent)
{
bool gameMode = detect_game_mode();
if (AlwaysOnTopSettings::settings().blockInGameMode && gameMode)
@@ -191,6 +204,16 @@ void AlwaysOnTop::ProcessCommand(HWND window)
m_topmostWindows.erase(iter);
}
// Restore transparency if the window has layered style
if (transparent)
{
LONG exStyle = GetWindowLong(window, GWL_EXSTYLE);
if (exStyle & WS_EX_LAYERED)
{
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
}
}
Trace::AlwaysOnTop::UnpinWindow();
}
}
@@ -200,6 +223,12 @@ void AlwaysOnTop::ProcessCommand(HWND window)
{
soundType = Sound::Type::On;
AssignBorder(window);
if (transparent)
{
::MakeWindowTransparent(window, AlwaysOnTopSettings::settings().transparencyPercentage);
}
Trace::AlwaysOnTop::PinWindow();
}
}
@@ -274,6 +303,9 @@ void AlwaysOnTop::RegisterHotkey() const
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::Pin));
RegisterHotKey(m_window, static_cast<int>(HotkeyId::Pin), AlwaysOnTopSettings::settings().hotkey.get_modifiers(), AlwaysOnTopSettings::settings().hotkey.get_code());
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::TransparentPin));
RegisterHotKey(m_window, static_cast<int>(HotkeyId::TransparentPin), AlwaysOnTopSettings::settings().transparencyHotkey.get_modifiers(), AlwaysOnTopSettings::settings().transparencyHotkey.get_code());
}
void AlwaysOnTop::RegisterLLKH()
@@ -284,6 +316,7 @@ void AlwaysOnTop::RegisterLLKH()
}
m_hPinEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT);
m_hTransparentPinEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_TRANSPARENT_PIN_EVENT);
m_hTerminateEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_TERMINATE_EVENT);
if (!m_hPinEvent)
@@ -292,20 +325,27 @@ void AlwaysOnTop::RegisterLLKH()
return;
}
if (!m_hTransparentPinEvent)
{
Logger::warn(L"Failed to create transparentPinEvent. {}", get_last_error_or_default(GetLastError()));
return;
}
if (!m_hTerminateEvent)
{
Logger::warn(L"Failed to create terminateEvent. {}", get_last_error_or_default(GetLastError()));
return;
}
HANDLE handles[2] = { m_hPinEvent,
HANDLE handles[3] = { m_hPinEvent,
m_hTransparentPinEvent,
m_hTerminateEvent };
m_thread = std::thread([this, handles]() {
MSG msg;
while (m_running)
{
DWORD dwEvt = MsgWaitForMultipleObjects(2, handles, false, INFINITE, QS_ALLINPUT);
DWORD dwEvt = MsgWaitForMultipleObjects(3, handles, false, INFINITE, QS_ALLINPUT);
if (!m_running)
{
break;
@@ -315,13 +355,19 @@ void AlwaysOnTop::RegisterLLKH()
case WAIT_OBJECT_0:
if (HWND fw{ GetForegroundWindow() })
{
ProcessCommand(fw);
ProcessCommand(fw, false);
}
break;
case WAIT_OBJECT_0 + 1:
PostThreadMessage(m_mainThreadId, WM_QUIT, 0, 0);
if (HWND fw{ GetForegroundWindow() })
{
ProcessCommand(fw, true);
}
break;
case WAIT_OBJECT_0 + 2:
PostThreadMessage(m_mainThreadId, WM_QUIT, 0, 0);
break;
case WAIT_OBJECT_0 + 3:
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);

View File

@@ -10,6 +10,7 @@
#include <common/hooks/WinHookEvent.h>
#include <common/notifications/NotificationUtil.h>
#include <common/utils/window.h>
class AlwaysOnTop : public SettingsObserver
{
@@ -38,6 +39,7 @@ private:
enum class HotkeyId : int
{
Pin = 1,
TransparentPin = 2,
};
static inline AlwaysOnTop* s_instance = nullptr;
@@ -49,6 +51,7 @@ private:
HINSTANCE m_hinstance;
std::map<HWND, std::unique_ptr<WindowBorder>> m_topmostWindows{};
HANDLE m_hPinEvent;
HANDLE m_hTransparentPinEvent;
HANDLE m_hTerminateEvent;
DWORD m_mainThreadId;
std::thread m_thread;
@@ -64,7 +67,7 @@ private:
void RegisterLLKH();
void SubscribeToEvents();
void ProcessCommand(HWND window);
void ProcessCommand(HWND window, bool transparent = false);
void StartTrackingTopmostWindows();
void UnpinAll();
void CleanUp();

View File

@@ -13,6 +13,7 @@ namespace NonLocalizable
const static wchar_t* SettingsFileName = L"settings.json";
const static wchar_t* HotkeyID = L"hotkey";
const static wchar_t* TransparencyHotkeyID = L"transparency-hotkey";
const static wchar_t* SoundEnabledID = L"sound-enabled";
const static wchar_t* FrameEnabledID = L"frame-enabled";
const static wchar_t* FrameThicknessID = L"frame-thickness";
@@ -22,6 +23,7 @@ namespace NonLocalizable
const static wchar_t* ExcludedAppsID = L"excluded-apps";
const static wchar_t* FrameAccentColor = L"frame-accent-color";
const static wchar_t* RoundCornersEnabledID = L"round-corners-enabled";
const static wchar_t* TransparencyPercentageID = L"transparency-percentage";
}
// TODO: move to common utils
@@ -104,6 +106,26 @@ void AlwaysOnTopSettings::LoadSettings()
NotifyObservers(SettingId::Hotkey);
}
}
if (const auto jsonVal = values.get_json(NonLocalizable::TransparencyHotkeyID))
{
auto val = PowerToysSettings::HotkeyObject::from_json(*jsonVal);
if (m_settings.transparencyHotkey.get_modifiers() != val.get_modifiers() || m_settings.transparencyHotkey.get_key() != val.get_key() || m_settings.transparencyHotkey.get_code() != val.get_code())
{
m_settings.transparencyHotkey = val;
NotifyObservers(SettingId::TransparencyHotkey);
}
}
if (const auto jsonVal = values.get_int_value(NonLocalizable::TransparencyPercentageID))
{
auto val = *jsonVal;
if (m_settings.transparencyPercentage != val)
{
m_settings.transparencyPercentage = val;
NotifyObservers(SettingId::TransparencyPercentage);
}
}
if (const auto jsonVal = values.get_bool_value(NonLocalizable::SoundEnabledID))
{

View File

@@ -15,6 +15,8 @@ class SettingsObserver;
struct Settings
{
PowerToysSettings::HotkeyObject hotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, 84); // win + ctrl + T
PowerToysSettings::HotkeyObject transparencyHotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, true, 84); // win + ctrl + shift + T
int transparencyPercentage = 50; // 0-100, 0 = fully transparent, 100 = opaque
bool enableFrame = true;
bool enableSound = true;
bool roundCornersEnabled = true;

View File

@@ -11,5 +11,7 @@ enum class SettingId
BlockInGameMode,
ExcludeApps,
FrameAccentColor,
RoundCornersEnabled
RoundCornersEnabled,
TransparencyHotkey,
TransparencyPercentage
};

View File

@@ -27,6 +27,7 @@ namespace
const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_HOTKEY[] = L"hotkey";
const wchar_t JSON_KEY_TRANSPARENCY_HOTKEY[] = L"transparency-hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
}
@@ -105,17 +106,24 @@ public:
}
}
virtual bool on_hotkey(size_t /*hotkeyId*/) override
virtual bool on_hotkey(size_t hotkeyId) override
{
if (m_enabled)
{
Logger::trace(L"AlwaysOnTop hotkey pressed");
Logger::trace(L"AlwaysOnTop hotkey pressed, id={}", hotkeyId);
if (!is_process_running())
{
Enable();
}
SetEvent(m_hPinEvent);
if (hotkeyId == 0)
{
SetEvent(m_hPinEvent);
}
else if (hotkeyId == 1)
{
SetEvent(m_hTransparentPinEvent);
}
return true;
}
@@ -125,19 +133,32 @@ public:
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
size_t count = 0;
if (m_hotkey.key)
{
if (hotkeys && buffer_size >= 1)
if (hotkeys && buffer_size > count)
{
hotkeys[0] = m_hotkey;
hotkeys[count] = m_hotkey;
Logger::trace(L"AlwaysOnTop hotkey[0]: win={}, ctrl={}, shift={}, alt={}, key={}",
m_hotkey.win, m_hotkey.ctrl, m_hotkey.shift, m_hotkey.alt, m_hotkey.key);
}
return 1;
count++;
}
else
if (m_transparencyHotkey.key)
{
return 0;
if (hotkeys && buffer_size > count)
{
hotkeys[count] = m_transparencyHotkey;
Logger::trace(L"AlwaysOnTop hotkey[1]: win={}, ctrl={}, shift={}, alt={}, key={}",
m_transparencyHotkey.win, m_transparencyHotkey.ctrl, m_transparencyHotkey.shift, m_transparencyHotkey.alt, m_transparencyHotkey.key);
}
count++;
}
Logger::trace(L"AlwaysOnTop get_hotkeys returning count={}", count);
return count;
}
// Enable the powertoy
@@ -174,6 +195,7 @@ public:
app_name = L"AlwaysOnTop"; //TODO: localize
app_key = NonLocalizable::ModuleKey;
m_hPinEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT);
m_hTransparentPinEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_TRANSPARENT_PIN_EVENT);
m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_TERMINATE_EVENT);
init_settings();
}
@@ -190,6 +212,7 @@ private:
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
ResetEvent(m_hPinEvent);
ResetEvent(m_hTransparentPinEvent);
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
@@ -215,6 +238,7 @@ private:
{
m_enabled = false;
ResetEvent(m_hPinEvent);
ResetEvent(m_hTransparentPinEvent);
// Log telemetry
if (traceEvent)
@@ -253,6 +277,26 @@ private:
{
Logger::error("Failed to initialize AlwaysOnTop start shortcut");
}
try
{
auto jsonTransparencyHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_TRANSPARENCY_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
m_transparencyHotkey.win = jsonTransparencyHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
m_transparencyHotkey.alt = jsonTransparencyHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
m_transparencyHotkey.shift = jsonTransparencyHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
m_transparencyHotkey.ctrl = jsonTransparencyHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
m_transparencyHotkey.key = static_cast<unsigned char>(jsonTransparencyHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
}
catch (...)
{
Logger::info("AlwaysOnTop transparency shortcut not configured, using default");
// Set default: Win + Ctrl + Shift + T
m_transparencyHotkey.win = true;
m_transparencyHotkey.ctrl = true;
m_transparencyHotkey.shift = true;
m_transparencyHotkey.alt = false;
m_transparencyHotkey.key = 0x54; // T
}
}
else
{
@@ -288,9 +332,11 @@ private:
bool m_enabled = false;
HANDLE m_hProcess = nullptr;
Hotkey m_hotkey;
Hotkey m_transparencyHotkey;
// Handle to event used to pin/unpin windows
HANDLE m_hPinEvent;
HANDLE m_hTransparentPinEvent;
HANDLE m_hTerminateEvent;
};

View File

@@ -12,6 +12,7 @@
#include <FancyZonesLib/trace.h>
#include <common/utils/elevation.h>
#include <common/utils/window.h>
WindowDrag::WindowDrag(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas) :
m_window(window),
@@ -199,43 +200,15 @@ void WindowDrag::SetWindowTransparency()
{
if (FancyZonesSettings::settings().makeDraggedWindowTransparent)
{
m_windowProperties.exstyle = GetWindowLong(m_window, GWL_EXSTYLE);
SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle | WS_EX_LAYERED);
if (!GetLayeredWindowAttributes(m_window, &m_windowProperties.crKey, &m_windowProperties.alpha, &m_windowProperties.dwFlags))
{
Logger::error(L"Window transparency: GetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
return;
}
if (!SetLayeredWindowAttributes(m_window, 0, (255 * 50) / 100, LWA_ALPHA))
{
Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
return;
}
m_windowProperties.transparencySet = true;
m_windowProperties.transparency = ::MakeWindowTransparent(m_window, 50);
}
}
void WindowDrag::ResetWindowTransparency()
{
if (FancyZonesSettings::settings().makeDraggedWindowTransparent && m_windowProperties.transparencySet)
if (FancyZonesSettings::settings().makeDraggedWindowTransparent && m_windowProperties.transparency.transparencySet)
{
bool reset = true;
if (!SetLayeredWindowAttributes(m_window, m_windowProperties.crKey, m_windowProperties.alpha, m_windowProperties.dwFlags))
{
Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
reset = false;
}
if (SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle) == 0)
{
Logger::error(L"Window transparency: SetWindowLong failed, {}", get_last_error_or_default(GetLastError()));
reset = false;
}
m_windowProperties.transparencySet = !reset;
::RestoreWindowTransparency(m_window, m_windowProperties.transparency);
m_windowProperties.transparency.transparencySet = false;
}
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include <FancyZonesLib/HighlightedZones.h>
#include <common/utils/window.h>
class WorkArea;
class WindowDrag
{
WindowDrag(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas);
public:
static std::unique_ptr<WindowDrag> Create(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas);
~WindowDrag();
bool MoveSizeStart(HMONITOR monitor, bool isSnapping);
void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, bool isSnapping, bool isSelectManyZonesState);
void MoveSizeEnd();
private:
void SwitchSnappingMode(bool isSnapping);
void SetWindowTransparency();
void ResetWindowTransparency();
struct WindowProperties
{
// True if the window is a top-level window that does not have a visible owner
bool hasNoVisibleOwner = false;
// True if the window is a standard window
bool isStandardWindow = false;
// Transparency properties for restoration
WindowTransparencyProperties transparency;
};
const HWND m_window;
WindowProperties m_windowProperties; // MoveSizeWindowInfo of the window at the moment when dragging started
const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& m_activeWorkAreas; // all WorkAreas on current virtual desktop, mapped with monitors
WorkArea* m_currentWorkArea; // "Active" WorkArea, where the move/size is happening. Will update as drag moves between monitors.
bool m_snappingMode{ false };
HighlightedZones m_highlightedZones;
};

View File

@@ -200,43 +200,15 @@ void WindowMouseSnap::SetWindowTransparency()
{
if (FancyZonesSettings::settings().makeDraggedWindowTransparent)
{
m_windowProperties.exstyle = GetWindowLong(m_window, GWL_EXSTYLE);
SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle | WS_EX_LAYERED);
if (!GetLayeredWindowAttributes(m_window, &m_windowProperties.crKey, &m_windowProperties.alpha, &m_windowProperties.dwFlags))
{
Logger::error(L"Window transparency: GetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
return;
}
if (!SetLayeredWindowAttributes(m_window, 0, (255 * 50) / 100, LWA_ALPHA))
{
Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
return;
}
m_windowProperties.transparencySet = true;
m_windowProperties.transparency = ::MakeWindowTransparent(m_window, 50);
}
}
void WindowMouseSnap::ResetWindowTransparency()
{
if (FancyZonesSettings::settings().makeDraggedWindowTransparent && m_windowProperties.transparencySet)
if (FancyZonesSettings::settings().makeDraggedWindowTransparent && m_windowProperties.transparency.transparencySet)
{
bool reset = true;
if (!SetLayeredWindowAttributes(m_window, m_windowProperties.crKey, m_windowProperties.alpha, m_windowProperties.dwFlags))
{
Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
reset = false;
}
if (SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle) == 0)
{
Logger::error(L"Window transparency: SetWindowLong failed, {}", get_last_error_or_default(GetLastError()));
reset = false;
}
m_windowProperties.transparencySet = !reset;
::RestoreWindowTransparency(m_window, m_windowProperties.transparency);
m_windowProperties.transparency.transparencySet = false;
}
}

View File

@@ -2,6 +2,7 @@
#include <FancyZonesLib/HighlightedZones.h>
#include <common/notifications/NotificationUtil.h>
#include <common/utils/window.h>
class WorkArea;
@@ -27,12 +28,8 @@ private:
{
// True if the window is a top-level window that does not have a visible owner
bool hasNoVisibleOwner = false;
// Properties to restore after dragging
long exstyle = 0;
COLORREF crKey = RGB(0, 0, 0);
DWORD dwFlags = 0;
BYTE alpha = 0;
bool transparencySet{false};
// Transparency properties for restoration
WindowTransparencyProperties transparency;
};
const HWND m_window;

View File

@@ -11,6 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class AlwaysOnTopProperties
{
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, true, false, false, 0x54);
public static readonly HotkeySettings DefaultTransparencyHotkeyValue = new HotkeySettings(true, true, false, true, 0x54);
public const bool DefaultFrameEnabled = true;
public const int DefaultFrameThickness = 15;
public const string DefaultFrameColor = "#0099cc";
@@ -19,10 +20,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public const bool DefaultSoundEnabled = true;
public const bool DefaultDoNotActivateOnGameMode = true;
public const bool DefaultRoundCornersEnabled = true;
public const int DefaultTransparencyPercentage = 50;
public AlwaysOnTopProperties()
{
Hotkey = new KeyboardKeysProperty(DefaultHotkeyValue);
TransparencyHotkey = new KeyboardKeysProperty(DefaultTransparencyHotkeyValue);
FrameEnabled = new BoolProperty(DefaultFrameEnabled);
FrameThickness = new IntProperty(DefaultFrameThickness);
FrameColor = new StringProperty(DefaultFrameColor);
@@ -31,12 +34,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
SoundEnabled = new BoolProperty(DefaultSoundEnabled);
DoNotActivateOnGameMode = new BoolProperty(DefaultDoNotActivateOnGameMode);
RoundCornersEnabled = new BoolProperty(DefaultRoundCornersEnabled);
TransparencyPercentage = new IntProperty(DefaultTransparencyPercentage);
ExcludedApps = new StringProperty();
}
[JsonPropertyName("hotkey")]
public KeyboardKeysProperty Hotkey { get; set; }
[JsonPropertyName("transparency-hotkey")]
public KeyboardKeysProperty TransparencyHotkey { get; set; }
[JsonPropertyName("frame-enabled")]
public BoolProperty FrameEnabled { get; set; }
@@ -64,6 +71,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("round-corners-enabled")]
public BoolProperty RoundCornersEnabled { get; set; }
[JsonPropertyName("transparency-percentage")]
public IntProperty TransparencyPercentage { get; set; }
public string ToJsonString()
{
return JsonSerializer.Serialize(this);

View File

@@ -38,6 +38,22 @@
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
<tkcontrols:SettingsCard
Name="AlwaysOnTopTransparencyActivationShortcut"
x:Uid="AlwaysOnTop_TransparencyActivationShortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.TransparencyHotkey, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
Name="AlwaysOnTopTransparencyPercentage"
x:Uid="AlwaysOnTop_TransparencyPercentage"
HeaderIcon="{ui:FontIcon Glyph=&#xE790;}">
<Slider
MinWidth="{StaticResource SettingActionControlMinWidth}"
Maximum="100"
Minimum="10"
Value="{x:Bind ViewModel.TransparencyPercentage, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="AlwaysOnTop_Behavior_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">

View File

@@ -3224,6 +3224,18 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="AlwaysOnTop_ActivationShortcut.Description" xml:space="preserve">
<value>Customize the shortcut to pin or unpin an app window</value>
</data>
<data name="AlwaysOnTop_TransparencyActivationShortcut.Header" xml:space="preserve">
<value>Transparent pin shortcut</value>
</data>
<data name="AlwaysOnTop_TransparencyActivationShortcut.Description" xml:space="preserve">
<value>Customize the shortcut to pin an app window with transparency</value>
</data>
<data name="AlwaysOnTop_TransparencyPercentage.Header" xml:space="preserve">
<value>Window opacity</value>
</data>
<data name="AlwaysOnTop_TransparencyPercentage.Description" xml:space="preserve">
<value>Set the opacity level for transparent pinned windows (10-100%)</value>
</data>
<data name="Oobe_AlwaysOnTop.Title" xml:space="preserve">
<value>Always On Top</value>
<comment>{Locked}</comment>

View File

@@ -49,6 +49,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
Settings = moduleSettingsRepository.SettingsConfig;
_hotkey = Settings.Properties.Hotkey.Value;
_transparencyHotkey = Settings.Properties.TransparencyHotkey.Value;
_frameEnabled = Settings.Properties.FrameEnabled.Value;
_frameThickness = Settings.Properties.FrameThickness.Value;
_frameColor = Settings.Properties.FrameColor.Value;
@@ -57,6 +58,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_soundEnabled = Settings.Properties.SoundEnabled.Value;
_doNotActivateOnGameMode = Settings.Properties.DoNotActivateOnGameMode.Value;
_roundCornersEnabled = Settings.Properties.RoundCornersEnabled.Value;
_transparencyPercentage = Settings.Properties.TransparencyPercentage.Value;
_excludedApps = Settings.Properties.ExcludedApps.Value;
_windows11 = OSVersionHelper.IsWindows11();
@@ -83,7 +85,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
var hotkeysDict = new Dictionary<string, HotkeySettings[]>
{
[ModuleName] = [Hotkey],
[ModuleName] = [Hotkey, TransparencyHotkey],
};
return hotkeysDict;
@@ -144,6 +146,30 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public HotkeySettings TransparencyHotkey
{
get => _transparencyHotkey;
set
{
if (value != _transparencyHotkey)
{
_transparencyHotkey = value ?? AlwaysOnTopProperties.DefaultTransparencyHotkeyValue;
Settings.Properties.TransparencyHotkey.Value = _transparencyHotkey;
NotifyPropertyChanged();
// Using InvariantCulture as this is an IPC message
SendConfigMSG(
string.Format(
CultureInfo.InvariantCulture,
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
AlwaysOnTopSettings.ModuleName,
JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.AlwaysOnTopSettings)));
}
}
}
public bool FrameEnabled
{
get => _frameEnabled;
@@ -204,6 +230,21 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public int TransparencyPercentage
{
get => _transparencyPercentage;
set
{
if (value != _transparencyPercentage)
{
_transparencyPercentage = value;
Settings.Properties.TransparencyPercentage.Value = value;
NotifyPropertyChanged();
}
}
}
public bool SoundEnabled
{
get => _soundEnabled;
@@ -305,6 +346,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private bool _enabledStateIsGPOConfigured;
private bool _isEnabled;
private HotkeySettings _hotkey;
private HotkeySettings _transparencyHotkey;
private bool _frameEnabled;
private int _frameThickness;
private string _frameColor;
@@ -313,6 +355,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private bool _soundEnabled;
private bool _doNotActivateOnGameMode;
private bool _roundCornersEnabled;
private int _transparencyPercentage;
private string _excludedApps;
private bool _windows11;
}