mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-02 08:28:55 +02:00
Compare commits
17 Commits
powerscrip
...
crutkas/al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c5437577f | ||
|
|
d920da1f86 | ||
|
|
44904c1884 | ||
|
|
43ad3b3f4b | ||
|
|
aca897962c | ||
|
|
0004026137 | ||
|
|
1f64356551 | ||
|
|
6800dd2abb | ||
|
|
fefd70ae84 | ||
|
|
aae941e7c6 | ||
|
|
d133113b86 | ||
|
|
a1200a1321 | ||
|
|
6870ad33f3 | ||
|
|
e354ae18fc | ||
|
|
9aa225d009 | ||
|
|
be4a5c250e | ||
|
|
b40fe31f11 |
1
.github/actions/spell-check/allow/code.txt
vendored
1
.github/actions/spell-check/allow/code.txt
vendored
@@ -330,6 +330,7 @@ xes
|
||||
PACKAGEVERSIONNUMBER
|
||||
APPXMANIFESTVERSION
|
||||
PROGMAN
|
||||
ROOTOWNER
|
||||
|
||||
# MRU lists
|
||||
CACHEWRITE
|
||||
|
||||
9
.github/actions/spell-check/expect.txt
vendored
9
.github/actions/spell-check/expect.txt
vendored
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace ManagedCommon
|
||||
public enum ModuleType
|
||||
{
|
||||
AdvancedPaste,
|
||||
AltWindowCycle,
|
||||
AlwaysOnTop,
|
||||
Awake,
|
||||
ColorPicker,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
1574
src/modules/AltWindowCycle/AltWindowCycle.cpp
Normal file
1574
src/modules/AltWindowCycle/AltWindowCycle.cpp
Normal file
File diff suppressed because it is too large
Load Diff
20
src/modules/AltWindowCycle/AltWindowCycle.h
Normal file
20
src/modules/AltWindowCycle/AltWindowCycle.h
Normal 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);
|
||||
40
src/modules/AltWindowCycle/AltWindowCycle.rc
Normal file
40
src/modules/AltWindowCycle/AltWindowCycle.rc
Normal 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
|
||||
130
src/modules/AltWindowCycle/AltWindowCycle.vcxproj
Normal file
130
src/modules/AltWindowCycle/AltWindowCycle.vcxproj
Normal 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>
|
||||
56
src/modules/AltWindowCycle/AltWindowCycle.vcxproj.filters
Normal file
56
src/modules/AltWindowCycle/AltWindowCycle.vcxproj.filters
Normal 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>
|
||||
181
src/modules/AltWindowCycle/AltWindowCycleLogic.h
Normal file
181
src/modules/AltWindowCycle/AltWindowCycleLogic.h
Normal 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 };
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
287
src/modules/AltWindowCycle/dllmain.cpp
Normal file
287
src/modules/AltWindowCycle/dllmain.cpp
Normal 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();
|
||||
}
|
||||
4
src/modules/AltWindowCycle/packages.config
Normal file
4
src/modules/AltWindowCycle/packages.config
Normal 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>
|
||||
1
src/modules/AltWindowCycle/pch.cpp
Normal file
1
src/modules/AltWindowCycle/pch.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
15
src/modules/AltWindowCycle/pch.h
Normal file
15
src/modules/AltWindowCycle/pch.h
Normal 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>
|
||||
13
src/modules/AltWindowCycle/resource.h
Normal file
13
src/modules/AltWindowCycle/resource.h
Normal 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
|
||||
//////////////////////////////
|
||||
33
src/modules/AltWindowCycle/trace.cpp
Normal file
33
src/modules/AltWindowCycle/trace.cpp
Normal 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"));
|
||||
}
|
||||
13
src/modules/AltWindowCycle/trace.h
Normal file
13
src/modules/AltWindowCycle/trace.h
Normal 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;
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 |
@@ -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),
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user