Compare commits

...

17 Commits

Author SHA1 Message Date
Clint Rutkas
2c5437577f AltWindowCycle: address PR review feedback
- Re-add VREDRAW to spell-check allowlist; the module uses CS_VREDRAW
  (and it is referenced across the repo), so removing it would reintroduce
  spell-check failures.
- enable(): set m_enabled from InitializeAltWindowCycle() result and only
  emit the enabled telemetry on success, so a failed init no longer reports
  the module as enabled-but-non-functional.
- EnabledModules: note that AltWindowCycle defaults to off, matching the
  convention used by the surrounding fields.
- ShellPage: use the module-specific AltWindowCycle icon in the nav item
  instead of the generic Windowing & Layouts group icon.
- ShowOverlayWindow(): guard the CreateRoundRectRgn/SetWindowRgn pair so a
  null region is skipped and the HRGN is freed if SetWindowRgn fails.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-26 10:17:34 -07:00
Niels Laute
d920da1f86 AltWindowCycle: use public DWM system-backdrop API for acrylic (#48875)
Replaces the undocumented `SetWindowCompositionAttribute` /
`ACCENT_POLICY` acrylic path with the public, documented
`DWMWA_SYSTEMBACKDROP_TYPE` (`DWMSBT_TRANSIENTWINDOW`) on Windows 11
22H2+.

On older builds (Windows 10, Windows 11 21H2) it falls back to an opaque
tinted fill painted by the thumb host window proc, so no private
composition API is used anywhere.

### Changes
- Remove `ACCENT_POLICY` / `WINDOWCOMPOSITIONATTRIBDATA` /
`SetWindowCompositionAttribute` usage.
- Add `SupportsSystemBackdrop()` OS-version gate (build >= 22621 via
`RtlGetVersion`).
- Add `ApplyBackdrop()` + `ThumbHostProc` for the public acrylic path
and the opaque fallback fill.
- Guard `DWMWA_SYSTEMBACKDROP_TYPE` / `DWMSBT_TRANSIENTWINDOW` for older
SDKs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-25 20:09:09 +02:00
Niels Laute
44904c1884 AltWindowCycle: refine overlay card visuals
Theme-aware card background (dark mode uses a darker, mostly-opaque tile), 1px CardStroke border, header height 48 with vertically centered 16px icon/title, corner radius 10, preview inset by the card stroke so the border stays visible, robust opaque-backing + rounded-bottom punch so the DWM thumbnail reads as rounded at the bottom, and focus-ring contrast shadow that is white in light mode / black in dark mode. Unit-test layout expectations updated accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-25 13:40:26 +02:00
Niels Laute
43ad3b3f4b Merge branch 'main' into crutkas/alt-window-cycle 2026-06-25 13:37:06 +02:00
Niels Laute
aca897962c Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-25 13:36:31 +02:00
Niels Laute
0004026137 Merge branch 'main' into crutkas/alt-window-cycle 2026-06-24 10:03:17 +02:00
copilot-swe-agent[bot]
1f64356551 Fix AltWindowCycle spelling warnings 2026-06-06 22:36:01 +00:00
Clint Rutkas
6800dd2abb AltWindowCycle: production cleanup + UWP/icon fixes
Production-cleanup pass on the shipped module:
- Remove POC-only AWC_* diagnostic env-var code paths.
- Remove the translucent debug backdrop host window and RenderBackdrop().
- Remove the magenta debug brush and unused helpers.
- Cache window titles at overlay-show time instead of re-querying every
  selection repaint; single production acrylic + DWM-thumbnail render path.

Code-review fixes:
- UWP/packaged apps all share ApplicationFrameHost.exe as their owning
  process, so they were grouped together. Add RealProcessImagePath() to
  resolve the hosted Windows.UI.Core.CoreWindow child's real process and
  group each packaged app on its own.
- Legacy 1-bit-mask icons carry no per-pixel alpha, so pure-black opaque
  pixels were dropped as transparent. Render such icons over both black and
  white backgrounds and treat pixels identical on both as opaque, preserving
  black icon detail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 22:34:03 -07:00
Clint Rutkas
fefd70ae84 Clean up AltWindowCycle PowerToys integration
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 18:09:22 -07:00
Clint Rutkas
aae941e7c6 Restore required spelling allow entries
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 17:25:42 -07:00
Clint Rutkas
d133113b86 Fix AltWindowCycle spelling metadata
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 16:45:10 -07:00
Clint Rutkas
a1200a1321 Add AltWindowCycle logic unit tests
Extract pure overlay layout, crop, wrap, and first-hotkey selection logic into a shared helper and cover it with native MSTests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 16:40:55 -07:00
Clint Rutkas
6870ad33f3 Improve AltWindowCycle overlay responsiveness
Show the overlay immediately, avoid stale monitor-size frames, and use an inset live-thumbnail viewport so DWM previews remain fast without exposing bad card corners.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 16:25:50 -07:00
Clint Rutkas
e354ae18fc Use DWM thumbnails for AltWindowCycle overlay
Keep thumbnail previews compositor-owned for visual fidelity and remove the rejected app-callback snapshot path. The overlay still runs on the dedicated module UI thread and preserves the acrylic chrome.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 13:34:34 -07:00
Clint Rutkas
9aa225d009 Add AltWindowCycle acrylic overlay
Port the Alt-Tab-style overlay into the native PowerToys module with a dedicated UI thread, acrylic backdrop, and same-surface preview snapshots for real alpha-masked rounded corners.

Keep the runner hotkey callback lightweight by posting to the module UI thread, and harden snapshot capture with timeout-bounded WM_PRINTCLIENT rendering.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 13:04:40 -07:00
copilot-swe-agent[bot]
be4a5c250e Add ROOTOWNER to spell-check allow list 2026-06-03 18:47:48 +00:00
Clint Rutkas
b40fe31f11 New module: AltWindowCycle
Adds AltWindowCycle, an in-proc module that cycles the top-level windows
of the currently-focused application, mimicking macOS Cmd+.

- Native C++ module DLL (modeled on FindMyMouse) loaded in-proc by the runner
- Two configurable hotkeys: Alt+` (next) and Shift+Alt+` (previous)
- C# Settings page under Windowing & Layouts, Dashboard entry, GPO support
- Settings persisted to AltWindowCycle/settings.json

Note: ships with placeholder icon/module images (copied from AlwaysOnTop)
that still need real assets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-03 10:44:04 -07:00
44 changed files with 3053 additions and 2 deletions

View File

@@ -330,6 +330,7 @@ xes
PACKAGEVERSIONNUMBER
APPXMANIFESTVERSION
PROGMAN
ROOTOWNER
# MRU lists
CACHEWRITE

View File

@@ -10,6 +10,7 @@ ACCESSDENIED
ACCESSTOKEN
acfs
ACIE
ACRYLICBLURBEHIND
acrt
ACTIVATEAPP
ACTIVATEOPTIONS
@@ -93,6 +94,7 @@ ASSOCSTR
ASYNCWINDOWPLACEMENT
ASYNCWINDOWPOS
atl
AWC
ATRIOX
aumid
AUO
@@ -460,6 +462,7 @@ dwm
dwmapi
DWMCOLORIZATIONCOLORCHANGED
DWMCOMPOSITIONCHANGED
DWMSBT
DWMNCRENDERINGCHANGED
Dwmp
DWMSENDICONICLIVEPREVIEWBITMAP
@@ -1378,6 +1381,7 @@ pnid
PNMLINK
Poc
Podcasts
PARGB
POINTERID
POINTERUPDATE
Pokedex
@@ -1842,6 +1846,7 @@ SYSLIB
sysmenu
systemai
SYSTEMAPPS
SYSTEMBACKDROP
SYSTEMMODAL
systemroot
SYSTEMTIME
@@ -1916,6 +1921,7 @@ tracerpt
trackbar
trafficmanager
transicc
TRANSIENTWINDOW
transitioning
TRAYMOUSEMESSAGE
triaging
@@ -2056,7 +2062,7 @@ WANTNUKEWARNING
WANTPALM
WASDK
wbem
Wca
wca
WCE
wcex
WCRAPI
@@ -2082,6 +2088,7 @@ winapi
winappsdk
windir
WINDOWCREATED
WINDOWCOMPOSITIONATTRIBDATA
WINDOWDESTROYED
windowedge
WINDOWINFO

View File

@@ -154,6 +154,10 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/AltWindowCycle/">
<Project Path="src/modules/AltWindowCycle/AltWindowCycle.vcxproj" Id="4d1d41c7-e22b-4a8b-8805-660a100e1d79" />
<Project Path="src/modules/AltWindowCycle/UnitTests/AltWindowCycleUnitTests.vcxproj" Id="9ebe5c3a-4a37-477e-9176-07c7cf0eba6e" />
</Folder>
<Folder Name="/modules/AlwaysOnTop/">
<Project Path="src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj" Id="1dc3be92-ce89-43fb-8110-9c043a2fe7a2" />
<Project Path="src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.vcxproj" Id="48a0a19e-a0be-4256-acf8-cc3b80291af9" />
@@ -1147,4 +1151,3 @@
<Project Path="tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj" Id="64a80062-4d8b-4229-8a38-dfa1d7497749" />
</Solution>

View File

@@ -8,6 +8,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredAlwaysOnTopEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredAltWindowCycleEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredAltWindowCycleEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredAwakeEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredAwakeEnabledValue());

View File

@@ -8,6 +8,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
GPOWrapper() = default;
static GpoRuleConfigured GetConfiguredAlwaysOnTopEnabledValue();
static GpoRuleConfigured GetConfiguredAltWindowCycleEnabledValue();
static GpoRuleConfigured GetConfiguredAwakeEnabledValue();
static GpoRuleConfigured GetConfiguredCmdNotFoundEnabledValue();
static GpoRuleConfigured GetConfiguredCmdPalEnabledValue();

View File

@@ -12,6 +12,7 @@ namespace PowerToys
};
[default_interface] static runtimeclass GPOWrapper {
static GpoRuleConfigured GetConfiguredAlwaysOnTopEnabledValue();
static GpoRuleConfigured GetConfiguredAltWindowCycleEnabledValue();
static GpoRuleConfigured GetConfiguredAwakeEnabledValue();
static GpoRuleConfigured GetConfiguredCmdNotFoundEnabledValue();
static GpoRuleConfigured GetConfiguredCmdPalEnabledValue();

View File

@@ -7,6 +7,7 @@ namespace ManagedCommon
public enum ModuleType
{
AdvancedPaste,
AltWindowCycle,
AlwaysOnTop,
Awake,
ColorPicker,

View File

@@ -72,6 +72,7 @@ namespace powertoys_gpo
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerQOIThumbnails";
const std::wstring POLICY_CONFIGURE_ENABLED_NEWPLUS = L"ConfigureEnabledUtilityNewPlus";
const std::wstring POLICY_CONFIGURE_ENABLED_WORKSPACES = L"ConfigureEnabledUtilityWorkspaces";
const std::wstring POLICY_CONFIGURE_ENABLED_ALT_WINDOW_CYCLE = L"ConfigureEnabledUtilityAltWindowCycle";
// The registry value names for PowerToys installer and update policies.
const std::wstring POLICY_DISABLE_PER_USER_INSTALLATION = L"PerUserInstallationDisabled";
@@ -288,6 +289,11 @@ namespace powertoys_gpo
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_ALWAYS_ON_TOP);
}
inline gpo_rule_configured_t getConfiguredAltWindowCycleEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_ALT_WINDOW_CYCLE);
}
inline gpo_rule_configured_t getConfiguredAwakeEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_AWAKE);

View File

@@ -91,6 +91,16 @@
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityAltWindowCycle" class="Both" displayName="$(string.ConfigureEnabledUtilityAltWindowCycle)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityAltWindowCycle">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_99_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityAwake" class="Both" displayName="$(string.ConfigureEnabledUtilityAwake)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityAwake">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_64_0" />

View File

@@ -244,6 +244,7 @@ If you don't configure this policy, the user will be able to control the setting
<string id="ConfigureAllUtilityGlobalEnabledState">Configure global utility enabled state</string>
<string id="ConfigureEnabledUtilityAdvancedPaste">Advanced Paste: Configure enabled state</string>
<string id="ConfigureEnabledUtilityAlwaysOnTop">Always On Top: Configure enabled state</string>
<string id="ConfigureEnabledUtilityAltWindowCycle">AltWindowCycle: Configure enabled state</string>
<string id="ConfigureEnabledUtilityAwake">Awake: Configure enabled state</string>
<string id="ConfigureEnabledUtilityColorPicker">Color Picker: Configure enabled state</string>
<string id="ConfigureEnabledUtilityCmdNotFound">Command Not Found: Configure enabled state</string>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
#pragma once
// Starts the dedicated UI thread that owns all overlay windows and the Switcher
// state machine. Must be called from the PowerToys module enable() path.
// Safe to call multiple times (idempotent).
bool InitializeAltWindowCycle(HINSTANCE hinst);
// Stops the UI thread and destroys all overlay resources. Safe to call when not
// initialized (idempotent). Called from disable() and destroy().
void ShutdownAltWindowCycle();
// Called from on_hotkey() on the runner thread. Does a cheap window-count check
// and posts to the UI thread. `holdModifiers` is an AltWindowCycleLogic modifier
// mask that controls which modifier release commits the visible cycle.
// Returns false (do not swallow) if the focused app has fewer than 2 cycle
// candidates and the overlay is not already active.
bool HandleAltWindowCycleHotkey(bool forward, unsigned int holdModifiers);
// Instant (no-overlay) cycle helper, kept for internal use.
void CycleForegroundAppWindows(bool forward);

View File

@@ -0,0 +1,40 @@
#include <windows.h>
#include "resource.h"
#include "../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{4d1d41c7-e22b-4a8b-8805-660a100e1d79}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>AltWindowCycle</RootNamespace>
<ProjectName>AltWindowCycle</ProjectName>
</PropertyGroup>
<Import Project="$(RepoRoot)deps\spdlog.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\</OutDir>
<TargetName>PowerToys.AltWindowCycle</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalDependencies>dwmapi.lib;gdi32.lib;gdiplus.lib;shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalDependencies>dwmapi.lib;gdi32.lib;gdiplus.lib;shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(RepoRoot)src\;$(RepoRoot)src\modules;$(RepoRoot)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="AltWindowCycle.h" />
<ClInclude Include="AltWindowCycleLogic.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="AltWindowCycle.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="AltWindowCycle.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(RepoRoot)src\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AltWindowCycle.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AltWindowCycle.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AltWindowCycleLogic.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="AltWindowCycle.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,181 @@
#pragma once
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <algorithm>
namespace AltWindowCycleLogic
{
constexpr int DefaultMaxColumns = 6;
constexpr unsigned int ModifierAlt = 1u << 0;
constexpr unsigned int ModifierCtrl = 1u << 1;
constexpr unsigned int ModifierShift = 1u << 2;
constexpr unsigned int ModifierWin = 1u << 3;
constexpr unsigned int AllModifiers = ModifierAlt | ModifierCtrl | ModifierShift | ModifierWin;
struct OverlayLayout
{
double scale = 1.0;
int pad = 0;
int gap = 0;
int tileW = 0;
int tileH = 0;
int headerH = 0;
int previewH = 0;
int inner = 0;
int radius = 0;
int cardTrimBottom = 0;
int iconSize = 0;
int cols = 1;
int rows = 0;
int panelX = 0;
int panelY = 0;
int panelW = 0;
int panelH = 0;
};
enum class FirstHotkeyAction
{
Ignore,
ShowOverlay,
};
struct FirstHotkeyResult
{
int selected = 0;
FirstHotkeyAction action = FirstHotkeyAction::Ignore;
};
constexpr int ScaledValue(double scale, int value)
{
return static_cast<int>(value * scale + 0.5);
}
constexpr unsigned int StableHoldModifiers(unsigned int configuredModifiers)
{
const unsigned int sanitized = configuredModifiers & AllModifiers;
const unsigned int nonShiftModifiers = sanitized & ~ModifierShift;
return nonShiftModifiers != 0 ? nonShiftModifiers : sanitized;
}
constexpr bool AreRequiredModifiersDown(unsigned int requiredModifiers, unsigned int downModifiers)
{
return requiredModifiers != 0 && (downModifiers & requiredModifiers) == requiredModifiers;
}
inline int WrapIndex(int index, int count)
{
if (count <= 0)
{
return 0;
}
return ((index % count) + count) % count;
}
inline FirstHotkeyResult BeginCycle(int currentIndex, int windowCount, bool forward)
{
if (windowCount < 2)
{
return {};
}
const int normalizedCurrentIndex = currentIndex < 0 ? 0 : currentIndex;
return {
WrapIndex(forward ? normalizedCurrentIndex + 1 : normalizedCurrentIndex - 1, windowCount),
FirstHotkeyAction::ShowOverlay
};
}
inline OverlayLayout ComputeOverlayLayout(const RECT& work, int windowCount, double scale, int maxColumns = DefaultMaxColumns)
{
OverlayLayout layout;
layout.scale = scale;
layout.pad = ScaledValue(scale, 32);
layout.gap = ScaledValue(scale, 26);
layout.tileW = ScaledValue(scale, 270);
layout.headerH = ScaledValue(scale, 48);
layout.previewH = ScaledValue(scale, 142);
layout.inner = ScaledValue(scale, 6);
layout.radius = ScaledValue(scale, 10);
layout.cardTrimBottom = 0;
layout.iconSize = ScaledValue(scale, 16);
layout.tileH = layout.headerH + layout.inner + layout.previewH + layout.inner;
const int workW = work.right - work.left;
const int workH = work.bottom - work.top;
const int safeWindowCount = (std::max)(0, windowCount);
const int safeMaxColumns = (std::max)(1, maxColumns);
const int columnsFromWork = (std::max)(1, (workW - 2 * layout.pad + layout.gap) / (layout.tileW + layout.gap));
layout.cols = (std::min)(safeWindowCount, (std::min)(safeMaxColumns, columnsFromWork));
if (layout.cols < 1)
{
layout.cols = 1;
}
layout.rows = (safeWindowCount + layout.cols - 1) / layout.cols;
layout.panelW = 2 * layout.pad + layout.cols * layout.tileW + (layout.cols - 1) * layout.gap;
layout.panelH = 2 * layout.pad + layout.rows * layout.tileH + (layout.rows - 1) * layout.gap;
layout.panelX = work.left + (workW - layout.panelW) / 2;
layout.panelY = work.top + (workH - layout.panelH) / 2;
return layout;
}
inline RECT TileRect(const OverlayLayout& layout, int index)
{
const int col = index % layout.cols;
const int row = index / layout.cols;
const int left = layout.pad + col * (layout.tileW + layout.gap);
const int top = layout.pad + row * (layout.tileH + layout.gap);
return { left, top, left + layout.tileW, top + layout.tileH - layout.cardTrimBottom };
}
inline RECT PreviewRect(const OverlayLayout& layout, const RECT& tile)
{
// Sits directly below the header band, inset by the 1px card stroke on the
// left/right/bottom so the card border stays visible around the image.
const int stroke = ScaledValue(layout.scale, 1);
return {
tile.left + stroke,
tile.top + layout.headerH,
tile.right - stroke,
tile.bottom - stroke
};
}
inline RECT HeaderRect(const OverlayLayout& layout, const RECT& tile)
{
const int margin = ScaledValue(layout.scale, 12);
return { tile.left + margin, tile.top, tile.right - margin, tile.top + layout.headerH };
}
inline RECT CoverSource(const RECT& dest, const RECT& avail)
{
const int aw = avail.right - avail.left;
const int ah = avail.bottom - avail.top;
const int dw = dest.right - dest.left;
const int dh = dest.bottom - dest.top;
if (aw <= 0 || ah <= 0 || dw <= 0 || dh <= 0)
{
return avail;
}
const double destA = static_cast<double>(dw) / dh;
const double srcA = static_cast<double>(aw) / ah;
if (srcA > destA)
{
const int cw = (std::max)(1, static_cast<int>(ah * destA + 0.5));
const int x = avail.left + (aw - cw) / 2;
return { x, avail.top, x + cw, avail.bottom };
}
const int ch = (std::max)(1, static_cast<int>(aw / destA + 0.5));
const int y = avail.top + (ah - ch) / 2;
return { avail.left, y, avail.right, y + ch };
}
}

View File

@@ -0,0 +1,168 @@
#pragma warning(push)
#pragma warning(disable : 26466)
#include "CppUnitTest.h"
#pragma warning(pop)
#include "..\AltWindowCycleLogic.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace AltWindowCycleUnitTests
{
namespace
{
void AssertRectEqual(const RECT& expected, const RECT& actual)
{
Assert::AreEqual(expected.left, actual.left, L"left");
Assert::AreEqual(expected.top, actual.top, L"top");
Assert::AreEqual(expected.right, actual.right, L"right");
Assert::AreEqual(expected.bottom, actual.bottom, L"bottom");
}
}
TEST_CLASS(AltWindowCycleLogicTests)
{
public:
TEST_METHOD(ComputeOverlayLayoutUsesExpectedUnscaledGeometry)
{
const RECT work = { 0, 0, 1920, 1080 };
const auto layout = AltWindowCycleLogic::ComputeOverlayLayout(work, 4, 1.0);
Assert::AreEqual(32, layout.pad);
Assert::AreEqual(26, layout.gap);
Assert::AreEqual(270, layout.tileW);
Assert::AreEqual(48, layout.headerH);
Assert::AreEqual(142, layout.previewH);
Assert::AreEqual(6, layout.inner);
Assert::AreEqual(10, layout.radius);
Assert::AreEqual(16, layout.iconSize);
Assert::AreEqual(202, layout.tileH);
Assert::AreEqual(4, layout.cols);
Assert::AreEqual(1, layout.rows);
Assert::AreEqual(1222, layout.panelW);
Assert::AreEqual(266, layout.panelH);
Assert::AreEqual(349, layout.panelX);
Assert::AreEqual(407, layout.panelY);
}
TEST_METHOD(ComputeOverlayLayoutUsesRoundedScaledGeometry)
{
const RECT work = { 0, 0, 1920, 1080 };
const auto layout = AltWindowCycleLogic::ComputeOverlayLayout(work, 6, 1.5);
Assert::AreEqual(48, layout.pad);
Assert::AreEqual(39, layout.gap);
Assert::AreEqual(405, layout.tileW);
Assert::AreEqual(72, layout.headerH);
Assert::AreEqual(213, layout.previewH);
Assert::AreEqual(9, layout.inner);
Assert::AreEqual(15, layout.radius);
Assert::AreEqual(24, layout.iconSize);
Assert::AreEqual(303, layout.tileH);
Assert::AreEqual(4, layout.cols);
Assert::AreEqual(2, layout.rows);
Assert::AreEqual(1833, layout.panelW);
Assert::AreEqual(741, layout.panelH);
Assert::AreEqual(43, layout.panelX);
Assert::AreEqual(169, layout.panelY);
}
TEST_METHOD(ComputeOverlayLayoutHandlesEmptyWindowCount)
{
const RECT work = { 10, 20, 810, 620 };
const auto layout = AltWindowCycleLogic::ComputeOverlayLayout(work, 0, 1.0);
Assert::AreEqual(1, layout.cols);
Assert::AreEqual(0, layout.rows);
}
TEST_METHOD(TilePreviewAndHeaderRectsUsePanelRelativeInsetViewport)
{
const RECT work = { 0, 0, 1920, 1080 };
const auto layout = AltWindowCycleLogic::ComputeOverlayLayout(work, 4, 1.0);
const RECT tile = AltWindowCycleLogic::TileRect(layout, 0);
AssertRectEqual({ 32, 32, 302, 234 }, tile);
const RECT preview = AltWindowCycleLogic::PreviewRect(layout, tile);
AssertRectEqual({ 33, 80, 301, 233 }, preview);
const RECT header = AltWindowCycleLogic::HeaderRect(layout, tile);
AssertRectEqual({ 44, 32, 290, 80 }, header);
}
TEST_METHOD(CoverSourceCropsWideSourceToDestinationAspectRatio)
{
const RECT dest = { 0, 0, 100, 100 };
const RECT avail = { 0, 0, 400, 200 };
const RECT source = AltWindowCycleLogic::CoverSource(dest, avail);
AssertRectEqual({ 100, 0, 300, 200 }, source);
}
TEST_METHOD(CoverSourceCropsTallSourceToDestinationAspectRatio)
{
const RECT dest = { 0, 0, 100, 100 };
const RECT avail = { 0, 0, 200, 400 };
const RECT source = AltWindowCycleLogic::CoverSource(dest, avail);
AssertRectEqual({ 0, 100, 200, 300 }, source);
}
TEST_METHOD(CoverSourceReturnsAvailableRegionForInvalidInputs)
{
const RECT dest = { 0, 0, 0, 100 };
const RECT avail = { 1, 2, 3, 4 };
const RECT source = AltWindowCycleLogic::CoverSource(dest, avail);
AssertRectEqual(avail, source);
}
TEST_METHOD(WrapIndexWrapsForwardAndBackward)
{
Assert::AreEqual(0, AltWindowCycleLogic::WrapIndex(3, 3));
Assert::AreEqual(2, AltWindowCycleLogic::WrapIndex(-1, 3));
Assert::AreEqual(1, AltWindowCycleLogic::WrapIndex(4, 3));
Assert::AreEqual(0, AltWindowCycleLogic::WrapIndex(4, 0));
}
TEST_METHOD(StableHoldModifiersPreferNonShiftModifiers)
{
Assert::AreEqual(AltWindowCycleLogic::ModifierAlt, AltWindowCycleLogic::StableHoldModifiers(AltWindowCycleLogic::ModifierAlt | AltWindowCycleLogic::ModifierShift));
Assert::AreEqual(AltWindowCycleLogic::ModifierCtrl, AltWindowCycleLogic::StableHoldModifiers(AltWindowCycleLogic::ModifierCtrl | AltWindowCycleLogic::ModifierShift));
Assert::AreEqual(AltWindowCycleLogic::ModifierWin, AltWindowCycleLogic::StableHoldModifiers(AltWindowCycleLogic::ModifierWin));
Assert::AreEqual(AltWindowCycleLogic::ModifierShift, AltWindowCycleLogic::StableHoldModifiers(AltWindowCycleLogic::ModifierShift));
Assert::AreEqual(0u, AltWindowCycleLogic::StableHoldModifiers(0));
}
TEST_METHOD(AreRequiredModifiersDownRequiresEveryHeldModifier)
{
Assert::IsFalse(AltWindowCycleLogic::AreRequiredModifiersDown(0, AltWindowCycleLogic::ModifierAlt));
Assert::IsFalse(AltWindowCycleLogic::AreRequiredModifiersDown(AltWindowCycleLogic::ModifierAlt, 0));
Assert::IsFalse(AltWindowCycleLogic::AreRequiredModifiersDown(AltWindowCycleLogic::ModifierAlt | AltWindowCycleLogic::ModifierCtrl, AltWindowCycleLogic::ModifierAlt));
Assert::IsTrue(AltWindowCycleLogic::AreRequiredModifiersDown(AltWindowCycleLogic::ModifierAlt, AltWindowCycleLogic::ModifierAlt | AltWindowCycleLogic::ModifierShift));
Assert::IsTrue(AltWindowCycleLogic::AreRequiredModifiersDown(AltWindowCycleLogic::ModifierAlt | AltWindowCycleLogic::ModifierCtrl, AltWindowCycleLogic::ModifierAlt | AltWindowCycleLogic::ModifierCtrl | AltWindowCycleLogic::ModifierShift));
}
TEST_METHOD(BeginCycleSelectsNextOrPreviousAndShowsOverlay)
{
const auto forward = AltWindowCycleLogic::BeginCycle(0, 3, true);
Assert::AreEqual(1, forward.selected);
Assert::IsTrue(forward.action == AltWindowCycleLogic::FirstHotkeyAction::ShowOverlay);
const auto backward = AltWindowCycleLogic::BeginCycle(0, 3, false);
Assert::AreEqual(2, backward.selected);
Assert::IsTrue(backward.action == AltWindowCycleLogic::FirstHotkeyAction::ShowOverlay);
const auto ignored = AltWindowCycleLogic::BeginCycle(0, 1, true);
Assert::AreEqual(0, ignored.selected);
Assert::IsTrue(ignored.action == AltWindowCycleLogic::FirstHotkeyAction::Ignore);
}
};
}

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<ProjectGuid>{9EBE5C3A-4A37-477E-9176-07C7CF0EBA6E}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>AltWindowCycleUnitTests</RootNamespace>
<ProjectSubType>NativeUnitTestProject</ProjectSubType>
<ProjectName>AltWindowCycle.UnitTests</ProjectName>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseOfMfc>false</UseOfMfc>
<UsePrecompiledHeaders>false</UsePrecompiledHeaders>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\tests\AltWindowCycle\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(RepoRoot)src\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="AltWindowCycleLogicTests.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\AltWindowCycleLogic.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{1345DADF-2D71-454C-A6D9-B9698E920E1A}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{8439D5F4-E4A2-4A72-A6D7-579E2A8D1260}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="AltWindowCycleLogicTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\AltWindowCycleLogic.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,287 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/utils/gpo.h>
#include "AltWindowCycle.h"
#include "AltWindowCycleLogic.h"
#include "trace.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Trace::RegisterProvider();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_WIN[] = L"win";
const wchar_t JSON_KEY_ALT[] = L"alt";
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_NEXT_WINDOW_SHORTCUT[] = L"next_window_shortcut";
const wchar_t JSON_KEY_PREVIOUS_WINDOW_SHORTCUT[] = L"previous_window_shortcut";
// ` ~ key (VK_OEM_3) on US layouts.
const unsigned char DEFAULT_BACKTICK_VK = 0xC0;
// Hotkey ids, matching the order returned by get_hotkeys() (and the order in
// the Settings UI). on_hotkey() receives these indices.
enum HotkeyId : size_t
{
HotkeyNext = 0,
HotkeyPrevious = 1
};
static unsigned int ModifierMaskFromHotkey(const PowertoyModuleIface::Hotkey& hotkey)
{
unsigned int modifiers = 0;
if (hotkey.alt)
{
modifiers |= AltWindowCycleLogic::ModifierAlt;
}
if (hotkey.ctrl)
{
modifiers |= AltWindowCycleLogic::ModifierCtrl;
}
if (hotkey.shift)
{
modifiers |= AltWindowCycleLogic::ModifierShift;
}
if (hotkey.win)
{
modifiers |= AltWindowCycleLogic::ModifierWin;
}
return AltWindowCycleLogic::StableHoldModifiers(modifiers);
}
}
// Implement the PowerToy Module Interface and all the required methods.
class AltWindowCycle : public PowertoyModuleIface
{
private:
bool m_enabled = false;
// Cycle to the next window of the focused app.
PowertoyModuleIface::Hotkey m_nextHotkey;
// Cycle to the previous window of the focused app.
PowertoyModuleIface::Hotkey m_previousHotkey;
void init_settings();
void parse_settings(PowerToysSettings::PowerToyValues& settings);
static void parse_hotkey(const winrt::Windows::Data::Json::JsonObject& properties,
const wchar_t* key,
PowertoyModuleIface::Hotkey& hotkey)
{
try
{
auto jsonHotkeyObject = properties.GetNamedObject(key);
hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
}
catch (...)
{
Logger::error("Failed to initialize AltWindowCycle shortcut from settings");
}
}
public:
AltWindowCycle()
{
LoggerHelpers::init_logger(L"AltWindowCycle", L"ModuleInterface", "AltWindowCycle");
init_settings();
}
virtual void destroy() override
{
ShutdownAltWindowCycle(); // idempotent
delete this;
}
virtual const wchar_t* get_name() override
{
return L"AltWindowCycle";
}
virtual const wchar_t* get_key() override
{
return L"AltWindowCycle";
}
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredAltWindowCycleEnabledValue();
}
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(L"Cycle between the windows of the currently focused application.");
return settings.serialize_to_buffer(buffer, buffer_size);
}
virtual void call_custom_action(const wchar_t* /*action*/) override
{
}
virtual void set_config(const wchar_t* config) override
{
try
{
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
parse_settings(values);
values.save_to_settings_file();
}
catch (std::exception&)
{
// Improper JSON.
}
}
virtual void enable() override
{
m_enabled = InitializeAltWindowCycle(reinterpret_cast<HINSTANCE>(&__ImageBase));
if (m_enabled)
{
Trace::EnableAltWindowCycle(true);
}
}
virtual void disable() override
{
m_enabled = false;
Trace::EnableAltWindowCycle(false);
ShutdownAltWindowCycle();
}
virtual bool is_enabled() override
{
return m_enabled;
}
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
if (hotkeys && buffer_size >= 2)
{
hotkeys[HotkeyNext] = m_nextHotkey;
hotkeys[HotkeyPrevious] = m_previousHotkey;
}
return 2;
}
virtual bool on_hotkey(size_t hotkeyId) override
{
if (!m_enabled)
{
return false;
}
if (hotkeyId == HotkeyNext)
{
Logger::trace(L"AltWindowCycle next-window hotkey pressed");
Trace::CycleWindow(true);
return HandleAltWindowCycleHotkey(true, ModifierMaskFromHotkey(m_nextHotkey));
}
if (hotkeyId == HotkeyPrevious)
{
Logger::trace(L"AltWindowCycle previous-window hotkey pressed");
Trace::CycleWindow(false);
return HandleAltWindowCycleHotkey(false, ModifierMaskFromHotkey(m_previousHotkey));
}
return false;
}
};
void AltWindowCycle::init_settings()
{
try
{
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(AltWindowCycle::get_key());
parse_settings(settings);
}
catch (std::exception&)
{
// Error while loading from the settings file. Let default values stay as they are.
}
}
void AltWindowCycle::parse_settings(PowerToysSettings::PowerToyValues& settings)
{
// Reset to defaults before parsing so removed/invalid values fall back cleanly.
m_nextHotkey = PowertoyModuleIface::Hotkey{};
m_previousHotkey = PowertoyModuleIface::Hotkey{};
auto settingsObject = settings.get_raw_json();
if (settingsObject.GetView().Size() && settingsObject.HasKey(JSON_KEY_PROPERTIES))
{
auto properties = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
if (properties.HasKey(JSON_KEY_NEXT_WINDOW_SHORTCUT))
{
parse_hotkey(properties, JSON_KEY_NEXT_WINDOW_SHORTCUT, m_nextHotkey);
}
if (properties.HasKey(JSON_KEY_PREVIOUS_WINDOW_SHORTCUT))
{
parse_hotkey(properties, JSON_KEY_PREVIOUS_WINDOW_SHORTCUT, m_previousHotkey);
}
}
else
{
Logger::info("AltWindowCycle settings are empty");
}
// Default: Alt+` cycles to the next window of the focused app.
if (!m_nextHotkey.key)
{
m_nextHotkey.win = false;
m_nextHotkey.ctrl = false;
m_nextHotkey.shift = false;
m_nextHotkey.alt = true;
m_nextHotkey.key = DEFAULT_BACKTICK_VK;
}
// Default: Shift+Alt+` cycles to the previous window of the focused app.
if (!m_previousHotkey.key)
{
m_previousHotkey.win = false;
m_previousHotkey.ctrl = false;
m_previousHotkey.shift = true;
m_previousHotkey.alt = true;
m_previousHotkey.key = DEFAULT_BACKTICK_VK;
}
}
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new AltWindowCycle();
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
</packages>

View File

@@ -0,0 +1 @@
#include "pch.h"

View File

@@ -0,0 +1,15 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dwmapi.h>
#include <algorithm>
#include <string>
#include <vector>
#include <winrt/Windows.Data.Json.h>
#include <common/logger/logger.h>
#include <common/utils/logger_helper.h>
#include <common/SettingsAPI/settings_helpers.h>

View File

@@ -0,0 +1,13 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by AltWindowCycle.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys AltWindowCycle"
#define INTERNAL_NAME "AltWindowCycle"
#define ORIGINAL_FILENAME "PowerToys.AltWindowCycle.dll"
// Non-localizable
//////////////////////////////

View File

@@ -0,0 +1,33 @@
#include "pch.h"
#include "trace.h"
#include <common/Telemetry/TraceBase.h>
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
// Log if the user has AltWindowCycle enabled or disabled
void Trace::EnableAltWindowCycle(const bool enabled) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"AltWindowCycle_EnableAltWindowCycle",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
// Log that the user invoked the module to cycle a window
void Trace::CycleWindow(const bool forward) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"AltWindowCycle_CycleWindow",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(forward, "Forward"));
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include <common/Telemetry/TraceBase.h>
class Trace : public telemetry::TraceBase
{
public:
// Log if the user has AltWindowCycle enabled or disabled
static void EnableAltWindowCycle(const bool enabled) noexcept;
// Log that the user invoked the module to cycle a window
static void CycleWindow(const bool forward) noexcept;
};

View File

@@ -288,6 +288,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
L"PowerToys.LightSwitchModuleInterface.dll",
L"PowerToys.PowerDisplayModuleInterface.dll",
L"PowerToys.GrabAndMoveModuleInterface.dll",
L"PowerToys.AltWindowCycle.dll",
};
for (auto moduleSubdir : knownModules)

View File

@@ -25,6 +25,7 @@ internal static class ModuleGpoHelper
ModuleType.FancyZones => GPOWrapper.GetConfiguredFancyZonesEnabledValue(),
ModuleType.FileLocksmith => GPOWrapper.GetConfiguredFileLocksmithEnabledValue(),
ModuleType.FindMyMouse => GPOWrapper.GetConfiguredFindMyMouseEnabledValue(),
ModuleType.AltWindowCycle => GPOWrapper.GetConfiguredAltWindowCycleEnabledValue(),
ModuleType.Hosts => GPOWrapper.GetConfiguredHostsFileEditorEnabledValue(),
ModuleType.ImageResizer => GPOWrapper.GetConfiguredImageResizerEnabledValue(),
ModuleType.KeyboardManager => GPOWrapper.GetConfiguredKeyboardManagerEnabledValue(),

View File

@@ -0,0 +1,35 @@
// 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.
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class AltWindowCycleProperties
{
// Alt+` cycles to the next window of the focused app.
[JsonIgnore]
[CmdConfigureIgnore]
public HotkeySettings DefaultNextWindowShortcut => new HotkeySettings(false, false, true, false, 0xC0);
// Shift+Alt+` cycles to the previous window of the focused app.
[JsonIgnore]
[CmdConfigureIgnore]
public HotkeySettings DefaultPreviousWindowShortcut => new HotkeySettings(false, false, true, true, 0xC0);
[JsonPropertyName("next_window_shortcut")]
public HotkeySettings NextWindowShortcut { get; set; }
[JsonPropertyName("previous_window_shortcut")]
public HotkeySettings PreviousWindowShortcut { get; set; }
public AltWindowCycleProperties()
{
NextWindowShortcut = DefaultNextWindowShortcut;
PreviousWindowShortcut = DefaultPreviousWindowShortcut;
}
}
}

View File

@@ -0,0 +1,57 @@
// 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.
using System.Collections.Generic;
using System.Text.Json.Serialization;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class AltWindowCycleSettings : BasePTModuleSettings, ISettingsConfig, IHotkeyConfig
{
public const string ModuleName = "AltWindowCycle";
[JsonPropertyName("properties")]
public AltWindowCycleProperties Properties { get; set; }
public AltWindowCycleSettings()
{
Name = ModuleName;
Properties = new AltWindowCycleProperties();
Version = "1.0";
}
public string GetModuleName()
{
return Name;
}
public ModuleType GetModuleType() => ModuleType.AltWindowCycle;
public HotkeyAccessor[] GetAllHotkeyAccessors()
{
var hotkeyAccessors = new List<HotkeyAccessor>
{
new HotkeyAccessor(
() => Properties.NextWindowShortcut,
value => Properties.NextWindowShortcut = value ?? Properties.DefaultNextWindowShortcut,
"AltWindowCycle_NextWindowShortcut"),
new HotkeyAccessor(
() => Properties.PreviousWindowShortcut,
value => Properties.PreviousWindowShortcut = value ?? Properties.DefaultPreviousWindowShortcut,
"AltWindowCycle_PreviousWindowShortcut"),
};
return hotkeyAccessors.ToArray();
}
// This can be utilized in the future if the settings.json file is to be modified/deleted.
public bool UpgradeSettingsConfiguration()
{
return false;
}
}
}

View File

@@ -218,6 +218,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool altWindowCycle; // defaulting to off
[JsonPropertyName("AltWindowCycle")]
public bool AltWindowCycle
{
get => altWindowCycle;
set
{
if (altWindowCycle != value)
{
LogTelemetryEvent(value);
altWindowCycle = value;
}
}
}
private bool mouseHighlighter = true;
[JsonPropertyName("MouseHighlighter")]

View File

@@ -56,6 +56,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
ModuleType.FancyZones => generalSettingsConfig.Enabled.FancyZones,
ModuleType.FileLocksmith => generalSettingsConfig.Enabled.FileLocksmith,
ModuleType.FindMyMouse => generalSettingsConfig.Enabled.FindMyMouse,
ModuleType.AltWindowCycle => generalSettingsConfig.Enabled.AltWindowCycle,
ModuleType.Hosts => generalSettingsConfig.Enabled.Hosts,
ModuleType.ImageResizer => generalSettingsConfig.Enabled.ImageResizer,
ModuleType.KeyboardManager => generalSettingsConfig.Enabled.KeyboardManager,
@@ -97,6 +98,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
case ModuleType.FancyZones: generalSettingsConfig.Enabled.FancyZones = isEnabled; break;
case ModuleType.FileLocksmith: generalSettingsConfig.Enabled.FileLocksmith = isEnabled; break;
case ModuleType.FindMyMouse: generalSettingsConfig.Enabled.FindMyMouse = isEnabled; break;
case ModuleType.AltWindowCycle: generalSettingsConfig.Enabled.AltWindowCycle = isEnabled; break;
case ModuleType.Hosts: generalSettingsConfig.Enabled.Hosts = isEnabled; break;
case ModuleType.ImageResizer: generalSettingsConfig.Enabled.ImageResizer = isEnabled; break;
case ModuleType.KeyboardManager: generalSettingsConfig.Enabled.KeyboardManager = isEnabled; break;
@@ -141,6 +143,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
ModuleType.FancyZones => FancyZonesSettings.ModuleName,
ModuleType.FileLocksmith => FileLocksmithSettings.ModuleName,
ModuleType.FindMyMouse => FindMyMouseSettings.ModuleName,
ModuleType.AltWindowCycle => AltWindowCycleSettings.ModuleName,
ModuleType.Hosts => HostsSettings.ModuleName,
ModuleType.ImageResizer => ImageResizerSettings.ModuleName,
ModuleType.KeyboardManager => KeyboardManagerSettings.ModuleName,

View File

@@ -44,6 +44,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonSerializable(typeof(GeneralSettings))]
[JsonSerializable(typeof(OutGoingGeneralSettings))]
[JsonSerializable(typeof(AdvancedPasteSettings))]
[JsonSerializable(typeof(AltWindowCycleSettings))]
[JsonSerializable(typeof(AlwaysOnTopSettings))]
[JsonSerializable(typeof(AwakeSettings))]
[JsonSerializable(typeof(CmdNotFoundSettings))]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -27,6 +27,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
case ModuleType.FancyZones: return GPOWrapper.GetConfiguredFancyZonesEnabledValue();
case ModuleType.FileLocksmith: return GPOWrapper.GetConfiguredFileLocksmithEnabledValue();
case ModuleType.FindMyMouse: return GPOWrapper.GetConfiguredFindMyMouseEnabledValue();
case ModuleType.AltWindowCycle: return GPOWrapper.GetConfiguredAltWindowCycleEnabledValue();
case ModuleType.Hosts: return GPOWrapper.GetConfiguredHostsFileEditorEnabledValue();
case ModuleType.ImageResizer: return GPOWrapper.GetConfiguredImageResizerEnabledValue();
case ModuleType.KeyboardManager: return GPOWrapper.GetConfiguredKeyboardManagerEnabledValue();
@@ -68,6 +69,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
ModuleType.FancyZones => typeof(FancyZonesPage),
ModuleType.FileLocksmith => typeof(FileLocksmithPage),
ModuleType.FindMyMouse => typeof(MouseUtilsPage),
ModuleType.AltWindowCycle => typeof(AltWindowCyclePage),
ModuleType.GeneralSettings => typeof(GeneralPage),
ModuleType.Hosts => typeof(HostsPage),
ModuleType.ImageResizer => typeof(ImageResizerPage),

View File

@@ -22,6 +22,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
[JsonSerializable(typeof(Dictionary<string, List<string>>))]
[JsonSerializable(typeof(FileLocksmithSettings))]
[JsonSerializable(typeof(FindMyMouseSettings))]
[JsonSerializable(typeof(AltWindowCycleSettings))]
[JsonSerializable(typeof(IList<PowerToysReleaseInfo>))]
[JsonSerializable(typeof(KeyboardManagerSettings))]
[JsonSerializable(typeof(LightSwitchSettings))]

View File

@@ -416,6 +416,7 @@ namespace Microsoft.PowerToys.Settings.UI
case "Dashboard": return typeof(DashboardPage);
case "Overview": return typeof(GeneralPage);
case "AdvancedPaste": return typeof(AdvancedPastePage);
case "AltWindowCycle": return typeof(AltWindowCyclePage);
case "AlwaysOnTop": return typeof(AlwaysOnTopPage);
case "Awake": return typeof(AwakePage);
case "CmdNotFound": return typeof(CmdNotFoundPage);

View File

@@ -0,0 +1,39 @@
<local:NavigablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.AltWindowCyclePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<local:NavigablePage.Resources />
<controls:SettingsPageControl
x:Uid="AltWindowCycle"
IsTabStop="False"
ModuleImageSource="ms-appx:///Assets/Settings/Modules/AltWindowCycle.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
Name="AltWindowCycleEnableToggleControlHeaderText"
x:Uid="AltWindowCycle_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AltWindowCycle.png}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="AltWindowCycle_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard x:Uid="AltWindowCycle_NextWindowShortcut">
<controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.NextWindowShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="AltWindowCycle_PreviousWindowShortcut">
<controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.PreviousWindowShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
</StackPanel>
</controls:SettingsPageControl.ModuleContent>
</controls:SettingsPageControl>
</local:NavigablePage>

View File

@@ -0,0 +1,31 @@
// 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.
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class AltWindowCyclePage : NavigablePage, IRefreshablePage
{
private AltWindowCycleViewModel ViewModel { get; set; }
public AltWindowCyclePage()
{
var settingsUtils = SettingsUtils.Default;
ViewModel = new AltWindowCycleViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<AltWindowCycleSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()
{
ViewModel.RefreshEnabledState();
}
}
}

View File

@@ -264,6 +264,12 @@
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/WindowingAndLayouts.png}"
SelectsOnInvoked="False">
<NavigationViewItem.MenuItems>
<NavigationViewItem
x:Name="AltWindowCycleNavigationItem"
x:Uid="Shell_AltWindowCycle"
helpers:NavHelper.NavigateTo="views:AltWindowCyclePage"
AutomationProperties.AutomationId="AltWindowCycleNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AltWindowCycle.png}" />
<NavigationViewItem
x:Name="AlwaysOnTopNavigationItem"
x:Uid="Shell_AlwaysOnTop"

View File

@@ -3004,6 +3004,34 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="Oobe_CropAndLock_HowToUse_Screenshot.Text" xml:space="preserve">
<value>to crop an application's window into a screenshot window. The screenshot won't update with the original window's content.</value>
</data>
<data name="AltWindowCycle.ModuleDescription" xml:space="preserve">
<value>Cycle between the windows of the currently focused application.</value>
</data>
<data name="AltWindowCycle.ModuleTitle" xml:space="preserve">
<value>Alt Window Cycle</value>
</data>
<data name="AltWindowCycle_Activation_GroupSettings.Header" xml:space="preserve">
<value>Activation</value>
</data>
<data name="AltWindowCycle_EnableToggleControl_HeaderText.Header" xml:space="preserve">
<value>Enable Alt Window Cycle</value>
</data>
<data name="AltWindowCycle_NextWindowShortcut.Description" xml:space="preserve">
<value>Cycle to the next window of the focused app</value>
</data>
<data name="AltWindowCycle_NextWindowShortcut.Header" xml:space="preserve">
<value>Next window</value>
</data>
<data name="AltWindowCycle_PreviousWindowShortcut.Description" xml:space="preserve">
<value>Cycle to the previous window of the focused app</value>
</data>
<data name="AltWindowCycle_PreviousWindowShortcut.Header" xml:space="preserve">
<value>Previous window</value>
</data>
<data name="Shell_AltWindowCycle.Content" xml:space="preserve">
<value>Alt Window Cycle</value>
<comment>{Locked}</comment>
</data>
<data name="AlwaysOnTop.ModuleDescription" xml:space="preserve">
<value>Always On Top is a quick and easy way to pin windows on top.</value>
</data>

View File

@@ -0,0 +1,174 @@
// 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.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text.Json;
using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Microsoft.PowerToys.Settings.UI.SerializationContext;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public partial class AltWindowCycleViewModel : PageViewModelBase
{
protected override string ModuleName => AltWindowCycleSettings.ModuleName;
private SettingsUtils SettingsUtils { get; set; }
private GeneralSettings GeneralSettingsConfig { get; set; }
private AltWindowCycleSettings Settings { get; set; }
private Func<string, int> SendConfigMSG { get; }
public AltWindowCycleViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<AltWindowCycleSettings> moduleSettingsRepository, Func<string, int> ipcMSGCallBackFunc)
{
ArgumentNullException.ThrowIfNull(settingsUtils);
SettingsUtils = settingsUtils;
// To obtain the general settings configurations of PowerToys Settings.
ArgumentNullException.ThrowIfNull(settingsRepository);
GeneralSettingsConfig = settingsRepository.SettingsConfig;
InitializeEnabledValue();
// To obtain the settings configurations of AltWindowCycle.
ArgumentNullException.ThrowIfNull(moduleSettingsRepository);
Settings = moduleSettingsRepository.SettingsConfig;
_nextWindowShortcut = Settings.Properties.NextWindowShortcut;
_previousWindowShortcut = Settings.Properties.PreviousWindowShortcut;
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
}
private void InitializeEnabledValue()
{
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredAltWindowCycleEnabledValue();
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
{
// Get the enabled state from GPO.
_enabledStateIsGPOConfigured = true;
_isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
}
else
{
_isEnabled = GeneralSettingsConfig.Enabled.AltWindowCycle;
}
}
public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
{
var hotkeysDict = new Dictionary<string, HotkeySettings[]>
{
[ModuleName] = [NextWindowShortcut, PreviousWindowShortcut],
};
return hotkeysDict;
}
public bool IsEnabled
{
get => _isEnabled;
set
{
if (_enabledStateIsGPOConfigured)
{
// If it's GPO configured, shouldn't be able to change this state.
return;
}
if (value != _isEnabled)
{
_isEnabled = value;
// Set the status in the general settings configuration
GeneralSettingsConfig.Enabled.AltWindowCycle = value;
OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(snd.ToString());
OnPropertyChanged(nameof(IsEnabled));
}
}
}
public bool IsEnabledGpoConfigured
{
get => _enabledStateIsGPOConfigured;
}
public HotkeySettings NextWindowShortcut
{
get => _nextWindowShortcut;
set
{
if (value != _nextWindowShortcut)
{
_nextWindowShortcut = value ?? Settings.Properties.DefaultNextWindowShortcut;
Settings.Properties.NextWindowShortcut = _nextWindowShortcut;
NotifyPropertyChanged();
SendSettingsConfigMessage();
}
}
}
public HotkeySettings PreviousWindowShortcut
{
get => _previousWindowShortcut;
set
{
if (value != _previousWindowShortcut)
{
_previousWindowShortcut = value ?? Settings.Properties.DefaultPreviousWindowShortcut;
Settings.Properties.PreviousWindowShortcut = _previousWindowShortcut;
NotifyPropertyChanged();
SendSettingsConfigMessage();
}
}
}
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
SettingsUtils.SaveSettings(Settings.ToJsonString(), AltWindowCycleSettings.ModuleName);
}
public void RefreshEnabledState()
{
InitializeEnabledValue();
OnPropertyChanged(nameof(IsEnabled));
}
private void SendSettingsConfigMessage()
{
SendConfigMSG(
string.Format(
CultureInfo.InvariantCulture,
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
AltWindowCycleSettings.ModuleName,
JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.AltWindowCycleSettings)));
}
private GpoRuleConfigured _enabledGpoRuleConfiguration;
private bool _enabledStateIsGPOConfigured;
private bool _isEnabled;
private HotkeySettings _nextWindowShortcut;
private HotkeySettings _previousWindowShortcut;
}
}

View File

@@ -493,6 +493,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
ModuleType.EnvironmentVariables => GetModuleItemsEnvironmentVariables(),
ModuleType.FancyZones => GetModuleItemsFancyZones(),
ModuleType.FindMyMouse => GetModuleItemsFindMyMouse(),
ModuleType.AltWindowCycle => GetModuleItemsAltWindowCycle(),
ModuleType.Hosts => GetModuleItemsHosts(),
ModuleType.KeyboardManager => GetModuleItemsKeyboardManager(),
ModuleType.LightSwitch => GetModuleItemsLightSwitch(),
@@ -626,6 +627,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
return new ObservableCollection<DashboardModuleItem>(list);
}
private ObservableCollection<DashboardModuleItem> GetModuleItemsAltWindowCycle()
{
ISettingsRepository<AltWindowCycleSettings> moduleSettingsRepository = SettingsRepository<AltWindowCycleSettings>.GetInstance(SettingsUtils.Default);
var settings = moduleSettingsRepository.SettingsConfig;
var list = new List<DashboardModuleItem>
{
new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("AltWindowCycle_NextWindowShortcut/Header"), Shortcut = settings.Properties.NextWindowShortcut.GetKeysList() },
new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("AltWindowCycle_PreviousWindowShortcut/Header"), Shortcut = settings.Properties.PreviousWindowShortcut.GetKeysList() },
};
return new ObservableCollection<DashboardModuleItem>(list);
}
private ObservableCollection<DashboardModuleItem> GetModuleItemsHosts()
{
var list = new List<DashboardModuleItem>