mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-18 18:17:43 +01:00
Compare commits
10 Commits
shawn/impr
...
dev/vanzue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d015b96eea | ||
|
|
fd88fa18d4 | ||
|
|
a6b8cea7cd | ||
|
|
5f61057b38 | ||
|
|
0314a709f5 | ||
|
|
a246789719 | ||
|
|
af401dd6e9 | ||
|
|
6c2a99dfd6 | ||
|
|
7cf32bf204 | ||
|
|
ae9ba62a40 |
50
.github/prompts/create-commit-title.prompt.md
vendored
50
.github/prompts/create-commit-title.prompt.md
vendored
@@ -6,13 +6,45 @@ description: 'Generate an 80-character git commit title for the local diff'
|
||||
|
||||
# Generate Commit Title
|
||||
|
||||
**Goal:** Provide a ready-to-paste git commit title (<= 80 characters) that captures the most important local changes since `HEAD`.
|
||||
## Purpose
|
||||
Provide a single-line, ready-to-paste git commit title (<= 80 characters) that reflects the most important local changes since `HEAD`.
|
||||
|
||||
**Workflow:**
|
||||
1. Run a single command to view the local diff since the last commit:
|
||||
```@terminal
|
||||
git diff HEAD
|
||||
```
|
||||
2. From that diff, identify the dominant area (reference key paths like `src/modules/*`, `doc/devdocs/**`, etc.), the type of change (bug fix, docs update, config tweak), and any notable impact.
|
||||
3. Draft a concise, imperative commit title summarizing the dominant change. Keep it plain ASCII, <= 80 characters, and avoid trailing punctuation. Mention the primary component when obvious (for example `FancyZones:` or `Docs:`).
|
||||
4. Respond with only the final commit title on a single line so it can be pasted directly into `git commit`.
|
||||
## Input to collect
|
||||
- Run exactly one command to view the local diff:
|
||||
```@terminal
|
||||
git diff HEAD
|
||||
```
|
||||
|
||||
## How to decide the title
|
||||
1. From the diff, find the dominant area (e.g., `src/modules/*`, `doc/devdocs/**`) and the change type (bug fix, docs update, config tweak).
|
||||
2. Draft an imperative, plain-ASCII title that:
|
||||
- Mentions the primary component when obvious (e.g., `FancyZones:` or `Docs:`)
|
||||
- Stays within 80 characters and has no trailing punctuation
|
||||
|
||||
## Final output
|
||||
- Reply with only the commit title on a single line—no extra text.
|
||||
|
||||
## PR title convention (when asked)
|
||||
Use Conventional Commits style:
|
||||
|
||||
`<type>(<scope>): <summary>`
|
||||
|
||||
**Allowed types**
|
||||
- feat, fix, docs, refactor, perf, test, build, ci, chore
|
||||
|
||||
**Scope rules**
|
||||
- Use a short, PowerToys-focused scope (one word preferred). Common scopes:
|
||||
- Core: `runner`, `settings-ui`, `common`, `docs`, `build`, `ci`, `installer`, `gpo`, `dsc`
|
||||
- Modules: `fancyzones`, `powerrename`, `awake`, `colorpicker`, `imageresizer`, `keyboardmanager`, `mouseutils`, `peek`, `hosts`, `file-locksmith`, `screen-ruler`, `text-extractor`, `cropandlock`, `paste`, `powerlauncher`
|
||||
- If unclear, pick the closest module or subsystem; omit only if unavoidable
|
||||
|
||||
**Summary rules**
|
||||
- Imperative, present tense (“add”, “update”, “remove”, “fix”)
|
||||
- Keep it <= 72 characters when possible; be specific, avoid “misc changes”
|
||||
|
||||
**Examples**
|
||||
- `feat(fancyzones): add canvas template duplication`
|
||||
- `fix(mouseutils): guard crosshair toggle when dpi info missing`
|
||||
- `docs(runner): document tray icon states`
|
||||
- `build(installer): align wix v5 suffix flag`
|
||||
- `ci(ci): cache pipeline artifacts for x64`
|
||||
|
||||
1
.github/prompts/create-pr-summary.prompt.md
vendored
1
.github/prompts/create-pr-summary.prompt.md
vendored
@@ -22,3 +22,4 @@ description: 'Generate a PowerToys-ready pull request description from the local
|
||||
5. Confirm validation: list tests executed with results or state why tests were skipped in line with repo guidance.
|
||||
6. Load `.github/pull_request_template.md`, mirror its section order, and populate it with the gathered facts. Include only relevant checklist entries, marking them `[x]/[ ]` and noting any intentional omissions as "N/A".
|
||||
7. Present the filled template inside a fenced ```markdown code block with no extra commentary so it is ready to paste into a PR, clearly flagging any placeholders that still need user input.
|
||||
8. Prepend the PR title above the filled template, applying the Conventional Commit type/scope rules from `.github/prompts/create-commit-title.prompt.md`; pick the dominant component from the diff and keep the title concise and imperative.
|
||||
|
||||
9
.github/prompts/fix-spelling.prompt.md
vendored
9
.github/prompts/fix-spelling.prompt.md
vendored
@@ -10,8 +10,8 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR'
|
||||
|
||||
**Guardrails:**
|
||||
- Update only discussion threads authored by `github-actions` or `github-actions[bot]` that mention `Code scanning results / check-spelling`.
|
||||
- Resolve findings solely by editing `.github/actions/spell-check/expect.txt`; reuse existing entries.
|
||||
- Leave all other files and topics untouched.
|
||||
- Prefer improving the wording in the originally flagged file when it clarifies intent without changing meaning; if the wording is already clear/standard for the context, handle it via `.github/actions/spell-check/expect.txt` and reuse existing entries.
|
||||
- Limit edits to the flagged text and `.github/actions/spell-check/expect.txt`; leave all other files and topics untouched.
|
||||
|
||||
**Prerequisites:**
|
||||
- Install GitHub CLI if it is not present: `winget install GitHub.cli`.
|
||||
@@ -20,5 +20,6 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR'
|
||||
**Workflow:**
|
||||
1. Determine the active pull request with a single `gh pr view --json number` call (default to the current branch).
|
||||
2. Fetch all PR discussion data once via `gh pr view --json comments,reviews` and filter to check-spelling comments authored by `github-actions` or `github-actions[bot]` that are not minimized; when several remain, process only the most recent comment body.
|
||||
3. For each flagged token, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
|
||||
4. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
|
||||
3. For each flagged token, first consider tightening or rephrasing the original text to avoid the false positive while keeping the meaning intact; if the existing wording is already normal and professional for the context, proceed to allowlisting instead of changing it.
|
||||
4. When allowlisting, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
|
||||
5. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
|
||||
17
.vscode/settings.json
vendored
Normal file
17
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"github.copilot.chat.reviewSelection.instructions": [
|
||||
{
|
||||
"file": ".github/prompts/review-pr.prompt.md"
|
||||
}
|
||||
],
|
||||
"github.copilot.chat.commitMessageGeneration.instructions": [
|
||||
{
|
||||
"file": ".github/prompts/create-commit-title.prompt.md"
|
||||
}
|
||||
],
|
||||
"github.copilot.chat.pullRequestDescriptionGeneration.instructions": [
|
||||
{
|
||||
"file": ".github/prompts/create-pr-summary.prompt.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -66,5 +66,10 @@ namespace PowerToys.GPOWrapperProjection
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredWorkspacesEnabledValue();
|
||||
}
|
||||
|
||||
public static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue()
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredLightSwitchEnabledValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,5 +11,7 @@ enum class SettingId
|
||||
BlockInGameMode,
|
||||
ExcludeApps,
|
||||
FrameAccentColor,
|
||||
RoundCornersEnabled
|
||||
RoundCornersEnabled,
|
||||
TransparencyHotkey,
|
||||
TransparencyPercentage
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed partial class BuiltInsCommandProvider : CommandProvider
|
||||
public override ICommandItem[] TopLevelCommands() =>
|
||||
[
|
||||
new CommandItem(openSettings) { },
|
||||
new CommandItem(_newExtension) { Title = _newExtension.Title, Subtitle = Properties.Resources.builtin_new_extension_subtitle },
|
||||
new CommandItem(_newExtension) { Title = _newExtension.Title },
|
||||
];
|
||||
|
||||
public override IFallbackCommandItem[] FallbackCommands() =>
|
||||
|
||||
@@ -90,20 +90,5 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests
|
||||
// Assert
|
||||
Assert.IsFalse(string.IsNullOrEmpty(displayName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetTranslatedPluginDescriptionTest()
|
||||
{
|
||||
// Setup
|
||||
var provider = new TimeDateCommandsProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.TopLevelCommands();
|
||||
var subtitle = commands[0].Subtitle;
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(string.IsNullOrEmpty(subtitle));
|
||||
Assert.IsTrue(subtitle.Contains("Show time and date values in different formats"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2025</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>0</VersionMajor>
|
||||
<VersionMinor>7</VersionMinor>
|
||||
<VersionMinor>8</VersionMinor>
|
||||
<VersionInfoProductName>Microsoft Command Palette</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -15,7 +15,6 @@ public partial class CalculatorCommandProvider : CommandProvider
|
||||
private static ISettingsInterface settings = new SettingsManager();
|
||||
private readonly ListItem _listItem = new(new CalculatorListPage(settings))
|
||||
{
|
||||
Subtitle = Resources.calculator_top_level_subtitle,
|
||||
MoreCommands = [new CommandContextItem(((SettingsManager)settings).Settings.SettingsPage)],
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ public partial class ClipboardHistoryCommandsProvider : CommandProvider
|
||||
_clipboardHistoryListItem = new ListItem(new ClipboardHistoryListPage(_settingsManager))
|
||||
{
|
||||
Title = Properties.Resources.list_item_title,
|
||||
Subtitle = Properties.Resources.list_item_subtitle,
|
||||
Icon = Icons.ClipboardListIcon,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(_settingsManager.Settings.SettingsPage),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -41,15 +42,19 @@ internal sealed partial class FancyZonesMonitorListItem : ListItem
|
||||
public static Details BuildMonitorDetails(FancyZonesMonitorDescriptor monitor)
|
||||
{
|
||||
var currentVirtualDesktop = FancyZonesVirtualDesktop.GetCurrentVirtualDesktopIdString();
|
||||
|
||||
// Calculate physical resolution from logical pixels and DPI
|
||||
var scaleFactor = monitor.Data.Dpi > 0 ? monitor.Data.Dpi / 96.0 : 1.0;
|
||||
var physicalWidth = (int)Math.Round(monitor.Data.MonitorWidth * scaleFactor);
|
||||
var physicalHeight = (int)Math.Round(monitor.Data.MonitorHeight * scaleFactor);
|
||||
var resolution = $"{physicalWidth}\u00D7{physicalHeight}";
|
||||
|
||||
var tags = new List<IDetailsElement>
|
||||
{
|
||||
DetailTag(Resources.FancyZones_Monitor, monitor.Data.Monitor),
|
||||
DetailTag(Resources.FancyZones_Instance, monitor.Data.MonitorInstanceId),
|
||||
DetailTag(Resources.FancyZones_Serial, monitor.Data.MonitorSerialNumber),
|
||||
DetailTag(Resources.FancyZones_Number, monitor.Data.MonitorNumber.ToString(CultureInfo.InvariantCulture)),
|
||||
DetailTag(Resources.FancyZones_VirtualDesktop, currentVirtualDesktop),
|
||||
DetailTag(Resources.FancyZones_WorkArea, $"{monitor.Data.LeftCoordinate},{monitor.Data.TopCoordinate} {monitor.Data.WorkAreaWidth}\u00D7{monitor.Data.WorkAreaHeight}"),
|
||||
DetailTag(Resources.FancyZones_Resolution, $"{monitor.Data.MonitorWidth}\u00D7{monitor.Data.MonitorHeight}"),
|
||||
DetailTag(Resources.FancyZones_Resolution, resolution),
|
||||
DetailTag(Resources.FancyZones_DPI, monitor.Data.Dpi.ToString(CultureInfo.InvariantCulture)),
|
||||
};
|
||||
|
||||
|
||||
@@ -19,8 +19,12 @@ internal readonly record struct FancyZonesMonitorDescriptor(
|
||||
{
|
||||
get
|
||||
{
|
||||
var size = $"{Data.MonitorWidth}×{Data.MonitorHeight}";
|
||||
var scaling = Data.Dpi > 0 ? string.Format(CultureInfo.InvariantCulture, "{0}%", (int)Math.Round(Data.Dpi * 100 / 96.0)) : "n/a";
|
||||
// MonitorWidth/Height are logical (DPI-scaled) pixels, calculate physical resolution
|
||||
var scaleFactor = Data.Dpi > 0 ? Data.Dpi / 96.0 : 1.0;
|
||||
var physicalWidth = (int)Math.Round(Data.MonitorWidth * scaleFactor);
|
||||
var physicalHeight = (int)Math.Round(Data.MonitorHeight * scaleFactor);
|
||||
var size = $"{physicalWidth}×{physicalHeight}";
|
||||
var scaling = Data.Dpi > 0 ? string.Format(CultureInfo.InvariantCulture, "{0}%", (int)Math.Round(scaleFactor * 100)) : "n/a";
|
||||
return $"{size} \u2022 {scaling}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,12 +485,21 @@ internal static class FancyZonesThumbnailRenderer
|
||||
|
||||
private static List<NormalizedRect> GetFocusRects(int zoneCount)
|
||||
{
|
||||
// Focus layout parameters from FancyZonesEditor CanvasLayoutModel:
|
||||
// - DefaultOffset = 100px from top-left (normalized: ~0.05 for typical screen)
|
||||
// - OffsetShift = 50px per zone (normalized: ~0.025)
|
||||
// - ZoneSizeMultiplier = 0.4 (zones are 40% of screen)
|
||||
zoneCount = Math.Clamp(zoneCount, 1, 8);
|
||||
var rects = new List<NormalizedRect>(zoneCount);
|
||||
|
||||
const float defaultOffset = 0.05f; // ~100px on 1920px screen
|
||||
const float offsetShift = 0.025f; // ~50px on 1920px screen
|
||||
const float zoneSize = 0.4f; // 40% of screen
|
||||
|
||||
for (var i = 0; i < zoneCount; i++)
|
||||
{
|
||||
var offset = i * 0.06f;
|
||||
rects.Add(new NormalizedRect(0.1f + offset, 0.1f + offset, 0.8f, 0.8f));
|
||||
var offset = i * offsetShift;
|
||||
rects.Add(new NormalizedRect(defaultOffset + offset, defaultOffset + offset, zoneSize, zoneSize));
|
||||
}
|
||||
|
||||
return rects;
|
||||
|
||||
@@ -31,7 +31,6 @@ public partial class RemoteDesktopCommandProvider : CommandProvider
|
||||
|
||||
listPageCommand = new CommandItem(listPage)
|
||||
{
|
||||
Subtitle = Resources.remotedesktop_subtitle,
|
||||
Icon = Icons.RDPIcon,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(settingsManager.Settings.SettingsPage),
|
||||
|
||||
@@ -39,7 +39,6 @@ public partial class ShellCommandsProvider : CommandProvider
|
||||
{
|
||||
Icon = Icons.RunV2Icon,
|
||||
Title = Resources.shell_command_name,
|
||||
Subtitle = Resources.cmd_plugin_description,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(Settings.SettingsPage),
|
||||
],
|
||||
|
||||
@@ -28,7 +28,6 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
|
||||
{
|
||||
Icon = _timeDateExtensionPage.Icon,
|
||||
Title = Resources.Microsoft_plugin_timedate_plugin_name,
|
||||
Subtitle = GetTranslatedPluginDescription(),
|
||||
MoreCommands = [new CommandContextItem(_settingsManager.Settings.SettingsPage)],
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ public partial class WindowWalkerCommandsProvider : CommandProvider
|
||||
_windowWalkerPageItem = new CommandItem(new WindowWalkerListPage())
|
||||
{
|
||||
Title = Resources.window_walker_top_level_command_title,
|
||||
Subtitle = Resources.windowwalker_name,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(Settings.SettingsPage),
|
||||
],
|
||||
|
||||
@@ -30,7 +30,6 @@ public sealed partial class WindowsSettingsCommandsProvider : CommandProvider
|
||||
_searchSettingsListItem = new CommandItem(new WindowsSettingsListPage(_windowsSettings))
|
||||
{
|
||||
Title = Resources.settings_title,
|
||||
Subtitle = Resources.settings_subtitle,
|
||||
};
|
||||
_fallback = new(_windowsSettings);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
|
||||
@@ -23,8 +24,10 @@ namespace FancyZonesEditorCommon.Data
|
||||
{
|
||||
public struct CanvasZoneWrapper
|
||||
{
|
||||
[JsonPropertyName("X")]
|
||||
public int X { get; set; }
|
||||
|
||||
[JsonPropertyName("Y")]
|
||||
public int Y { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
@@ -191,27 +191,22 @@ bool EditorParameters::Save(const WorkAreaConfiguration& configuration, OnThread
|
||||
|
||||
monitorJson.dpi = dpi;
|
||||
|
||||
MONITORINFOEX monitorInfo{};
|
||||
// Get DPI-unaware values for dimensions (virtual coordinates for WPF sizing)
|
||||
MONITORINFOEX monitorInfoUnaware{};
|
||||
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
|
||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||
if (!GetMonitorInfo(monitor, &monitorInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
monitorInfoUnaware.cbSize = sizeof(monitorInfoUnaware);
|
||||
GetMonitorInfo(monitor, &monitorInfoUnaware);
|
||||
} }).wait();
|
||||
|
||||
float width = static_cast<float>(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left);
|
||||
float height = static_cast<float>(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top);
|
||||
DPIAware::Convert(monitor, width, height);
|
||||
// Dimensions in virtual coordinates (from DPI-unaware thread)
|
||||
monitorJson.monitorWidth = monitorInfoUnaware.rcMonitor.right - monitorInfoUnaware.rcMonitor.left;
|
||||
monitorJson.monitorHeight = monitorInfoUnaware.rcMonitor.bottom - monitorInfoUnaware.rcMonitor.top;
|
||||
monitorJson.workAreaWidth = monitorInfoUnaware.rcWork.right - monitorInfoUnaware.rcWork.left;
|
||||
monitorJson.workAreaHeight = monitorInfoUnaware.rcWork.bottom - monitorInfoUnaware.rcWork.top;
|
||||
|
||||
monitorJson.monitorWidth = static_cast<int>(std::roundf(width));
|
||||
monitorJson.monitorHeight = static_cast<int>(std::roundf(height));
|
||||
|
||||
// use dpi-unaware values
|
||||
monitorJson.top = monitorInfo.rcWork.top;
|
||||
monitorJson.left = monitorInfo.rcWork.left;
|
||||
monitorJson.workAreaWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
|
||||
monitorJson.workAreaHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
|
||||
// Position in virtual coordinates (matched by DPI-unaware context in WPF editor)
|
||||
monitorJson.left = monitorInfoUnaware.rcWork.left;
|
||||
monitorJson.top = monitorInfoUnaware.rcWork.top;
|
||||
|
||||
argsJson.monitors.emplace_back(std::move(monitorJson));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
45
src/modules/fancyzones/FancyZonesLib/WindowDrag.h
Normal file
45
src/modules/fancyzones/FancyZonesLib/WindowDrag.h
Normal 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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -67,10 +67,18 @@ namespace FancyZonesEditor.Models
|
||||
Window.KeyUp += ((App)Application.Current).App_KeyUp;
|
||||
Window.KeyDown += ((App)Application.Current).App_KeyDown;
|
||||
|
||||
// Store for DPI-unaware positioning
|
||||
_virtualWorkArea = workArea;
|
||||
|
||||
// Set initial WPF properties
|
||||
Window.Left = workArea.X;
|
||||
Window.Top = workArea.Y;
|
||||
Window.Width = workArea.Width;
|
||||
Window.Height = workArea.Height;
|
||||
|
||||
// After HWND is created, reposition using DPI-unaware context
|
||||
// This matches the C++ backend which uses a DPI-unaware thread
|
||||
Window.SourceInitialized += OnWindowSourceInitialized;
|
||||
}
|
||||
|
||||
public Monitor(string monitorName, string monitorInstanceId, string monitorSerialNumber, string virtualDesktop, int dpi, Rect workArea, Size monitorSize)
|
||||
@@ -80,16 +88,33 @@ namespace FancyZonesEditor.Models
|
||||
}
|
||||
|
||||
private LayoutSettings _settings;
|
||||
private Rect _virtualWorkArea;
|
||||
|
||||
private void OnWindowSourceInitialized(object sender, EventArgs e)
|
||||
{
|
||||
// Reposition window using DPI-unaware context to match the virtual coordinates
|
||||
// from the FancyZones C++ backend (which uses a DPI-unaware thread)
|
||||
Utils.NativeMethods.SetWindowPositionDpiUnaware(
|
||||
Window,
|
||||
(int)_virtualWorkArea.X,
|
||||
(int)_virtualWorkArea.Y,
|
||||
(int)_virtualWorkArea.Width,
|
||||
(int)_virtualWorkArea.Height);
|
||||
}
|
||||
|
||||
public void Scale(double scaleFactor)
|
||||
{
|
||||
Device.Scale(scaleFactor);
|
||||
|
||||
var workArea = Device.WorkAreaRect;
|
||||
Window.Left = workArea.X;
|
||||
Window.Top = workArea.Y;
|
||||
Window.Width = workArea.Width;
|
||||
Window.Height = workArea.Height;
|
||||
_virtualWorkArea = Device.WorkAreaRect;
|
||||
|
||||
// Use DPI-unaware positioning
|
||||
Utils.NativeMethods.SetWindowPositionDpiUnaware(
|
||||
Window,
|
||||
(int)_virtualWorkArea.X,
|
||||
(int)_virtualWorkArea.Y,
|
||||
(int)_virtualWorkArea.Width,
|
||||
(int)_virtualWorkArea.Height);
|
||||
}
|
||||
|
||||
public void SetLayoutSettings(LayoutModel model)
|
||||
|
||||
@@ -69,7 +69,11 @@ namespace FancyZonesEditor.Utils
|
||||
}
|
||||
else
|
||||
{
|
||||
return ScreenBoundsWidth + " × " + ScreenBoundsHeight;
|
||||
// Convert virtual coordinates to physical resolution by applying DPI scale
|
||||
double scale = DPI / 96.0;
|
||||
int physicalWidth = (int)Math.Round(ScreenBoundsWidth * scale);
|
||||
int physicalHeight = (int)Math.Round(ScreenBoundsHeight * scale);
|
||||
return physicalWidth + " × " + physicalHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
@@ -17,14 +17,48 @@ namespace FancyZonesEditor.Utils
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
|
||||
|
||||
private const int GWL_EX_STYLE = -20;
|
||||
private const int WS_EX_APPWINDOW = 0x00040000;
|
||||
private const int WS_EX_TOOLWINDOW = 0x00000080;
|
||||
private const uint SWP_NOZORDER = 0x0004;
|
||||
private const uint SWP_NOACTIVATE = 0x0010;
|
||||
|
||||
private static readonly IntPtr DPI_AWARENESS_CONTEXT_UNAWARE = new IntPtr(-1);
|
||||
|
||||
public static void SetWindowStyleToolWindow(Window hwnd)
|
||||
{
|
||||
var helper = new WindowInteropHelper(hwnd).Handle;
|
||||
_ = SetWindowLong(helper, GWL_EX_STYLE, (GetWindowLong(helper, GWL_EX_STYLE) | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Positions a WPF window using DPI-unaware context to match the virtual coordinates
|
||||
/// from the FancyZones C++ backend (which uses a DPI-unaware thread).
|
||||
/// This fixes overlay positioning on mixed-DPI multi-monitor setups.
|
||||
/// </summary>
|
||||
public static void SetWindowPositionDpiUnaware(Window window, int x, int y, int width, int height)
|
||||
{
|
||||
var helper = new WindowInteropHelper(window).Handle;
|
||||
if (helper != IntPtr.Zero)
|
||||
{
|
||||
// Temporarily switch to DPI-unaware context to position window.
|
||||
// This matches how the C++ backend gets coordinates via dpiUnawareThread.
|
||||
IntPtr oldContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
|
||||
try
|
||||
{
|
||||
SetWindowPos(helper, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetThreadDpiAwarenessContext(oldContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,13 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
eventHandle.Set();
|
||||
}
|
||||
|
||||
return true;
|
||||
case ModuleType.LightSwitch:
|
||||
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.LightSwitchToggleEvent()))
|
||||
{
|
||||
eventHandle.Set();
|
||||
}
|
||||
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
AddFlyoutMenuItem(ModuleType.EnvironmentVariables);
|
||||
AddFlyoutMenuItem(ModuleType.FancyZones);
|
||||
AddFlyoutMenuItem(ModuleType.Hosts);
|
||||
AddFlyoutMenuItem(ModuleType.LightSwitch);
|
||||
AddFlyoutMenuItem(ModuleType.PowerLauncher);
|
||||
AddFlyoutMenuItem(ModuleType.PowerOCR);
|
||||
AddFlyoutMenuItem(ModuleType.RegistryPreview);
|
||||
@@ -120,6 +121,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
{
|
||||
ModuleType.ColorPicker => SettingsRepository<ColorPickerSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
|
||||
ModuleType.FancyZones => SettingsRepository<FancyZonesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.ToString(),
|
||||
ModuleType.LightSwitch => SettingsRepository<LightSwitchSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ToggleThemeHotkey.Value.ToString(),
|
||||
ModuleType.PowerLauncher => SettingsRepository<PowerLauncherSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.OpenPowerLauncher.ToString(),
|
||||
ModuleType.PowerOCR => SettingsRepository<PowerOcrSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
|
||||
ModuleType.Workspaces => SettingsRepository<WorkspacesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.Hotkey.Value.ToString(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -11,7 +11,7 @@ using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Settings.UI.Library
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class LightSwitchSettings : BasePTModuleSettings, ISettingsConfig, ICloneable, IHotkeyConfig
|
||||
{
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonSerializable(typeof(HostsSettings))]
|
||||
[JsonSerializable(typeof(ImageResizerSettings))]
|
||||
[JsonSerializable(typeof(KeyboardManagerSettings))]
|
||||
[JsonSerializable(typeof(SettingsUILibrary.LightSwitchSettings))]
|
||||
[JsonSerializable(typeof(LightSwitchSettings))]
|
||||
[JsonSerializable(typeof(MeasureToolSettings))]
|
||||
[JsonSerializable(typeof(MouseHighlighterSettings))]
|
||||
[JsonSerializable(typeof(MouseJumpSettings))]
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
[JsonSerializable(typeof(FileLocksmithSettings))]
|
||||
[JsonSerializable(typeof(FindMyMouseSettings))]
|
||||
[JsonSerializable(typeof(IList<PowerToysReleaseInfo>))]
|
||||
[JsonSerializable(typeof(SettingsUILibrary.LightSwitchSettings))]
|
||||
[JsonSerializable(typeof(LightSwitchSettings))]
|
||||
[JsonSerializable(typeof(MeasureToolSettings))]
|
||||
[JsonSerializable(typeof(MouseHighlighterSettings))]
|
||||
[JsonSerializable(typeof(MouseJumpSettings))]
|
||||
|
||||
@@ -38,6 +38,22 @@
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AlwaysOnTopTransparencyActivationShortcut"
|
||||
x:Uid="AlwaysOnTop_TransparencyActivationShortcut"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<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=}">
|
||||
<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}">
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
var darkSettings = this.moduleSettingsRepository.SettingsConfig;
|
||||
|
||||
// Pass them into the ViewModel
|
||||
this.ViewModel = new LightSwitchViewModel(darkSettings, this.sendConfigMsg);
|
||||
this.ViewModel = new LightSwitchViewModel(this.generalSettingsRepository, darkSettings, ShellPage.SendDefaultIPCMessage);
|
||||
this.ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
|
||||
this.LoadSettings(this.generalSettingsRepository, this.moduleSettingsRepository);
|
||||
@@ -185,7 +185,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
// need to save the values
|
||||
this.ViewModel.Latitude = latitude.ToString(CultureInfo.InvariantCulture);
|
||||
this.ViewModel.Longitude = longitude.ToString(CultureInfo.InvariantCulture);
|
||||
this.ViewModel.SyncButtonInformation = $"{this.ViewModel.Latitude}<EFBFBD>, {this.ViewModel.Longitude}<EFBFBD>";
|
||||
this.ViewModel.SyncButtonInformation = $"{this.ViewModel.Latitude}°, {this.ViewModel.Longitude}°";
|
||||
|
||||
var result = SunCalc.CalculateSunriseSunset(latitude, longitude, DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
|
||||
|
||||
@@ -293,18 +293,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void UpdateEnabledState(bool recommendedState)
|
||||
{
|
||||
var enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredLightSwitchEnabledValue();
|
||||
|
||||
if (enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
||||
{
|
||||
// Get the enabled state from GPO.
|
||||
this.ViewModel.IsEnabledGpoConfigured = true;
|
||||
this.ViewModel.EnabledGPOConfiguration = enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ViewModel.IsEnabled = recommendedState;
|
||||
}
|
||||
ViewModel.RefreshEnabledState();
|
||||
}
|
||||
|
||||
private async void SyncLocationButton_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PowerToys.GPOWrapper;
|
||||
using Settings.UI.Library;
|
||||
using Settings.UI.Library.Helpers;
|
||||
|
||||
@@ -27,10 +29,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
|
||||
public ObservableCollection<SearchLocation> SearchLocations { get; } = new();
|
||||
|
||||
public LightSwitchViewModel(LightSwitchSettings initialSettings = null, Func<string, int> ipcMSGCallBackFunc = null)
|
||||
public LightSwitchViewModel(ISettingsRepository<GeneralSettings> settingsRepository, LightSwitchSettings initialSettings = null, Func<string, int> ipcMSGCallBackFunc = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settingsRepository);
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
InitializeEnabledValue();
|
||||
|
||||
_moduleSettings = initialSettings ?? new LightSwitchSettings();
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
|
||||
@@ -58,6 +66,21 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return hotkeysDict;
|
||||
}
|
||||
|
||||
private void InitializeEnabledValue()
|
||||
{
|
||||
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredLightSwitchEnabledValue();
|
||||
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
||||
{
|
||||
// Get the enabled state from GPO.
|
||||
_enabledStateIsGPOConfigured = true;
|
||||
_isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isEnabled = GeneralSettingsConfig.Enabled.LightSwitch;
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceLightNow()
|
||||
{
|
||||
Logger.LogInfo("Sending custom action: forceLight");
|
||||
@@ -93,33 +116,26 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_enabledStateIsGPOConfigured)
|
||||
{
|
||||
return _enabledGPOConfiguration;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _isEnabled;
|
||||
}
|
||||
}
|
||||
get => _isEnabled;
|
||||
|
||||
set
|
||||
{
|
||||
if (_isEnabled != value)
|
||||
if (_enabledStateIsGPOConfigured)
|
||||
{
|
||||
if (_enabledStateIsGPOConfigured)
|
||||
{
|
||||
// If it's GPO configured, shouldn't be able to change this state.
|
||||
return;
|
||||
}
|
||||
// If it's GPO configured, shouldn't be able to change this state.
|
||||
return;
|
||||
}
|
||||
|
||||
if (value != _isEnabled)
|
||||
{
|
||||
_isEnabled = value;
|
||||
|
||||
RefreshEnabledState();
|
||||
// Set the status in the general settings configuration
|
||||
GeneralSettingsConfig.Enabled.LightSwitch = value;
|
||||
OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
|
||||
NotifyPropertyChanged();
|
||||
SendConfigMSG(snd.ToString());
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,24 +143,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
public bool IsEnabledGpoConfigured
|
||||
{
|
||||
get => _enabledStateIsGPOConfigured;
|
||||
set
|
||||
{
|
||||
if (_enabledStateIsGPOConfigured != value)
|
||||
{
|
||||
_enabledStateIsGPOConfigured = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnabledGPOConfiguration
|
||||
public GpoRuleConfigured EnabledGPOConfiguration
|
||||
{
|
||||
get => _enabledGPOConfiguration;
|
||||
get => _enabledGpoRuleConfiguration;
|
||||
set
|
||||
{
|
||||
if (_enabledGPOConfiguration != value)
|
||||
if (_enabledGpoRuleConfiguration != value)
|
||||
{
|
||||
_enabledGPOConfiguration = value;
|
||||
_enabledGpoRuleConfiguration = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
@@ -575,7 +583,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
|
||||
private bool _enabledStateIsGPOConfigured;
|
||||
private bool _enabledGPOConfiguration;
|
||||
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
||||
private LightSwitchSettings _moduleSettings;
|
||||
private bool _isEnabled;
|
||||
private HotkeySettings _toggleThemeHotkey;
|
||||
|
||||
Reference in New Issue
Block a user